mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-29 22:17:47 +00:00
Compare commits
7 Commits
3029
...
base_sessi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2191e3a28 | ||
|
|
72e3e8593d | ||
|
|
67a8b4915d | ||
|
|
468d4a7150 | ||
|
|
2a01e29fec | ||
|
|
90a64d30dc | ||
|
|
31905d4f63 |
13
Makefile
13
Makefile
@@ -2,7 +2,6 @@ BUILD_DIR = build
|
||||
CLEANCSS = ./node_modules/.bin/cleancss
|
||||
DEPLOY_DIR = libs
|
||||
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
|
||||
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
|
||||
NODE_SASS = ./node_modules/.bin/node-sass
|
||||
NPM = npm
|
||||
OUTPUT_DIR = .
|
||||
@@ -20,7 +19,7 @@ compile:
|
||||
clean:
|
||||
rm -fr $(BUILD_DIR)
|
||||
|
||||
deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
|
||||
deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-css deploy-local
|
||||
|
||||
deploy-init:
|
||||
rm -fr $(DEPLOY_DIR)
|
||||
@@ -34,8 +33,6 @@ deploy-appbundle:
|
||||
$(BUILD_DIR)/do_external_connect.min.map \
|
||||
$(BUILD_DIR)/external_api.min.js \
|
||||
$(BUILD_DIR)/external_api.min.map \
|
||||
$(BUILD_DIR)/flacEncodeWorker.min.js \
|
||||
$(BUILD_DIR)/flacEncodeWorker.min.map \
|
||||
$(BUILD_DIR)/device_selection_popup_bundle.min.js \
|
||||
$(BUILD_DIR)/device_selection_popup_bundle.min.map \
|
||||
$(BUILD_DIR)/dial_in_info_bundle.min.js \
|
||||
@@ -53,12 +50,6 @@ deploy-lib-jitsi-meet:
|
||||
$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-libflac:
|
||||
cp \
|
||||
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js \
|
||||
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js.mem \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-css:
|
||||
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
|
||||
$(CLEANCSS) $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
|
||||
@@ -67,7 +58,7 @@ deploy-css:
|
||||
deploy-local:
|
||||
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
|
||||
|
||||
dev: deploy-init deploy-css deploy-lib-jitsi-meet deploy-libflac
|
||||
dev: deploy-init deploy-css deploy-lib-jitsi-meet
|
||||
$(WEBPACK_DEV_SERVER)
|
||||
|
||||
source-package:
|
||||
|
||||
@@ -41,15 +41,6 @@ android {
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:appcompat-v7:22.2.0'
|
||||
compile 'com.google.android.gms:play-services-auth:15.0.0'
|
||||
|
||||
implementation project(':sdk')
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
|
||||
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
|
||||
}
|
||||
|
||||
if (project.file('google-services.json').exists()) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:name=".MainApplication"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
|
||||
@@ -27,9 +27,11 @@ import org.jitsi.meet.sdk.invite.AddPeopleControllerListener;
|
||||
import org.jitsi.meet.sdk.invite.InviteController;
|
||||
import org.jitsi.meet.sdk.invite.InviteControllerListener;
|
||||
|
||||
import com.calendarevents.CalendarEventsPackage;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -227,4 +229,20 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
addPeopleController.endAddPeople();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode,
|
||||
String[] permissions,
|
||||
int[] grantResults) {
|
||||
CalendarEventsPackage.onRequestPermissionsResult(
|
||||
requestCode,
|
||||
permissions,
|
||||
grantResults);
|
||||
|
||||
super.onRequestPermissionsResult(
|
||||
requestCode,
|
||||
permissions,
|
||||
grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
|
||||
/**
|
||||
* Simple {@link Application} for hooking up LeakCanary:
|
||||
* https://github.com/square/leakcanary
|
||||
*/
|
||||
public class MainApplication extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
if (!LeakCanary.isInAnalyzerProcess(this)) {
|
||||
LeakCanary.install(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
classpath 'com.google.gms:google-services:3.2.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files.
|
||||
@@ -17,7 +16,6 @@ buildscript {
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven { url "https://maven.google.com" }
|
||||
google()
|
||||
jcenter()
|
||||
maven { url "$rootDir/../node_modules/jsc-android/dist" }
|
||||
|
||||
@@ -26,9 +26,6 @@ dependencies {
|
||||
|
||||
compile project(':react-native-background-timer')
|
||||
compile project(':react-native-fast-image')
|
||||
compile(project(":react-native-google-signin")) {
|
||||
exclude group: 'com.google.android.gms'
|
||||
}
|
||||
compile project(':react-native-immersive')
|
||||
compile project(':react-native-keep-awake')
|
||||
compile project(':react-native-linear-gradient')
|
||||
|
||||
@@ -27,7 +27,6 @@ import android.view.KeyEvent;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
@@ -43,7 +42,7 @@ import java.net.URL;
|
||||
* {@code JitsiMeetView} static methods.
|
||||
*/
|
||||
public class JitsiMeetActivity
|
||||
extends AppCompatActivity implements JitsiMeetActivityInterface {
|
||||
extends AppCompatActivity {
|
||||
|
||||
/**
|
||||
* The request code identifying requests for the permission to draw on top
|
||||
@@ -175,12 +174,7 @@ public class JitsiMeetActivity
|
||||
if (Settings.canDrawOverlays(this)) {
|
||||
initializeContentView();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ReactActivityLifecycleCallbacks.onActivityResult(
|
||||
this, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -266,15 +260,6 @@ public class JitsiMeetActivity
|
||||
ReactActivityLifecycleCallbacks.onNewIntent(intent);
|
||||
}
|
||||
|
||||
// https://developer.android.com/reference/android/support/v4/app/ActivityCompat.OnRequestPermissionsResultCallback
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
final int requestCode,
|
||||
final String[] permissions,
|
||||
final int[] grantResults) {
|
||||
ReactActivityLifecycleCallbacks.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
@@ -298,14 +283,6 @@ public class JitsiMeetActivity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the {@code PermissionAwareActivity} interface.
|
||||
*/
|
||||
@Override
|
||||
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
|
||||
ReactActivityLifecycleCallbacks.requestPermissions(this, permissions, requestCode, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setDefaultURL(URL)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
|
||||
import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||
|
||||
/**
|
||||
* This interface serves as the umbrella interface that applications not using
|
||||
* {@code JitsiMeetActivity} must implement in order to ensure full
|
||||
* functionality.
|
||||
*/
|
||||
public interface JitsiMeetActivityInterface
|
||||
extends ActivityCompat.OnRequestPermissionsResultCallback,
|
||||
PermissionAwareActivity {
|
||||
}
|
||||
@@ -16,16 +16,11 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import com.calendarevents.CalendarEventsPackage;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
/**
|
||||
* Helper class to encapsulate the work which needs to be done on
|
||||
@@ -33,37 +28,6 @@ import com.facebook.react.modules.core.PermissionListener;
|
||||
* it.
|
||||
*/
|
||||
public class ReactActivityLifecycleCallbacks {
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onActivityResult} so we are notified about results of external intents
|
||||
* started/finished.
|
||||
*
|
||||
* @param activity {@code Activity} activity from where the result comes from.
|
||||
* @param requestCode {@code int} code of the request.
|
||||
* @param resultCode {@code int} code of the result.
|
||||
* @param data {@code Intent} the intent of the activity.
|
||||
*/
|
||||
public static void onActivityResult(
|
||||
Activity activity,
|
||||
int requestCode,
|
||||
int resultCode,
|
||||
Intent data) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onActivityResult(activity, requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed for making sure this class working with the "PermissionsAndroid"
|
||||
* React Native module.
|
||||
*/
|
||||
private static PermissionListener permissionListener;
|
||||
private static Callback permissionsCallback;
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@link Activity#onBackPressed} so we can do the required internal
|
||||
@@ -143,11 +107,6 @@ public class ReactActivityLifecycleCallbacks {
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
|
||||
}
|
||||
|
||||
if (permissionsCallback != null) {
|
||||
permissionsCallback.invoke();
|
||||
permissionsCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,29 +126,4 @@ public class ReactActivityLifecycleCallbacks {
|
||||
reactInstanceManager.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onRequestPermissionsResult(
|
||||
final int requestCode,
|
||||
final String[] permissions,
|
||||
final int[] grantResults) {
|
||||
CalendarEventsPackage.onRequestPermissionsResult(
|
||||
requestCode,
|
||||
permissions,
|
||||
grantResults);
|
||||
permissionsCallback = new Callback() {
|
||||
@Override
|
||||
public void invoke(Object... args) {
|
||||
if (permissionListener != null
|
||||
&& permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
|
||||
permissionListener = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public static void requestPermissions(Activity activity, String[] permissions, int requestCode, PermissionListener listener) {
|
||||
permissionListener = listener;
|
||||
activity.requestPermissions(permissions, requestCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,6 @@ class ReactInstanceManagerHolder {
|
||||
.setApplication(application)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.addPackage(new co.apptailor.googlesignin.RNGoogleSigninPackage())
|
||||
.addPackage(new com.BV.LinearGradient.LinearGradientPackage())
|
||||
.addPackage(new com.calendarevents.CalendarEventsPackage())
|
||||
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
|
||||
|
||||
@@ -5,8 +5,6 @@ include ':react-native-background-timer'
|
||||
project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/android')
|
||||
include ':react-native-fast-image'
|
||||
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fast-image/android')
|
||||
include ':react-native-google-signin'
|
||||
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-google-signin/android')
|
||||
include ':react-native-immersive'
|
||||
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
|
||||
include ':react-native-keep-awake'
|
||||
|
||||
130
conference.js
130
conference.js
@@ -11,7 +11,6 @@ import * as RemoteControlEvents
|
||||
from './service/remotecontrol/RemoteControlEvents';
|
||||
import UIEvents from './service/UI/UIEvents';
|
||||
import UIUtil from './modules/UI/util/UIUtil';
|
||||
import { createTaskQueue } from './modules/util/helpers';
|
||||
import * as JitsiMeetConferenceEvents from './ConferenceEvents';
|
||||
|
||||
import {
|
||||
@@ -31,16 +30,14 @@ import EventEmitter from 'events';
|
||||
import {
|
||||
AVATAR_ID_COMMAND,
|
||||
AVATAR_URL_COMMAND,
|
||||
EMAIL_COMMAND,
|
||||
authStatusChanged,
|
||||
commonUserJoinedHandling,
|
||||
commonUserLeftHandling,
|
||||
conferenceFailed,
|
||||
conferenceJoined,
|
||||
conferenceLeft,
|
||||
conferenceWillJoin,
|
||||
conferenceWillLeave,
|
||||
dataChannelOpened,
|
||||
EMAIL_COMMAND,
|
||||
lockStateChanged,
|
||||
onStartMutedPolicyChanged,
|
||||
p2pStatusChanged,
|
||||
@@ -77,10 +74,14 @@ import {
|
||||
getAvatarURLByParticipantId,
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
hiddenParticipantJoined,
|
||||
hiddenParticipantLeft,
|
||||
localParticipantConnectionStatusChanged,
|
||||
localParticipantRoleChanged,
|
||||
MAX_DISPLAY_NAME_LENGTH,
|
||||
participantConnectionStatusChanged,
|
||||
participantJoined,
|
||||
participantLeft,
|
||||
participantPresenceChanged,
|
||||
participantRoleChanged,
|
||||
participantUpdated
|
||||
@@ -273,27 +274,6 @@ function redirectToStaticPage(pathname) {
|
||||
windowLocation.pathname = newPathname;
|
||||
}
|
||||
|
||||
/**
|
||||
* A queue for the async replaceLocalTrack action so that multiple audio
|
||||
* replacements cannot happen simultaneously. This solves the issue where
|
||||
* replaceLocalTrack is called multiple times with an oldTrack of null, causing
|
||||
* multiple local tracks of the same type to be used.
|
||||
*
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
const _replaceLocalAudioTrackQueue = createTaskQueue();
|
||||
|
||||
/**
|
||||
* A task queue for replacement local video tracks. This separate queue exists
|
||||
* so video replacement is not blocked by audio replacement tasks in the queue
|
||||
* {@link _replaceLocalAudioTrackQueue}.
|
||||
*
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
const _replaceLocalVideoTrackQueue = createTaskQueue();
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -724,7 +704,7 @@ export default {
|
||||
track.mute();
|
||||
}
|
||||
});
|
||||
logger.log(`initialized with ${tracks.length} local tracks`);
|
||||
logger.log('initialized with %s local tracks', tracks.length);
|
||||
this._localTracksInitialized = true;
|
||||
con.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
@@ -876,6 +856,9 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME it is possible to queue this task twice, but it's not causing
|
||||
// any issues. Specifically this can happen when the previous
|
||||
// get user media call is blocked on "ask user for permissions" dialog.
|
||||
if (!this.localVideo && !mute) {
|
||||
const maybeShowErrorDialog = error => {
|
||||
showUI && APP.UI.showCameraErrorNotification(error);
|
||||
@@ -1278,23 +1261,16 @@ export default {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
useVideoStream(newStream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
|
||||
APP.store.dispatch(
|
||||
replaceLocalTrack(this.localVideo, newStream, room))
|
||||
.then(() => {
|
||||
this.localVideo = newStream;
|
||||
this._setSharingScreen(newStream);
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
}
|
||||
this.setVideoMuteStatus(this.isLocalVideoMuted());
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.then(onFinish);
|
||||
return APP.store.dispatch(
|
||||
replaceLocalTrack(this.localVideo, newStream, room))
|
||||
.then(() => {
|
||||
this.localVideo = newStream;
|
||||
this._setSharingScreen(newStream);
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
}
|
||||
this.setVideoMuteStatus(this.isLocalVideoMuted());
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1324,22 +1300,15 @@ export default {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
useAudioStream(newStream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
_replaceLocalAudioTrackQueue.enqueue(onFinish => {
|
||||
APP.store.dispatch(
|
||||
replaceLocalTrack(this.localAudio, newStream, room))
|
||||
.then(() => {
|
||||
this.localAudio = newStream;
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
}
|
||||
this.setAudioMuteStatus(this.isLocalAudioMuted());
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.then(onFinish);
|
||||
return APP.store.dispatch(
|
||||
replaceLocalTrack(this.localAudio, newStream, room))
|
||||
.then(() => {
|
||||
this.localAudio = newStream;
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
}
|
||||
this.setAudioMuteStatus(this.isLocalAudioMuted());
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1692,16 +1661,24 @@ export default {
|
||||
room.on(JitsiConferenceEvents.PARTCIPANT_FEATURES_CHANGED,
|
||||
user => APP.UI.onUserFeaturesChanged(user));
|
||||
room.on(JitsiConferenceEvents.USER_JOINED, (id, user) => {
|
||||
// The logic shared between RN and web.
|
||||
commonUserJoinedHandling(APP.store, room, user);
|
||||
const displayName = user.getDisplayName();
|
||||
|
||||
if (user.isHidden()) {
|
||||
APP.store.dispatch(hiddenParticipantJoined(id, displayName));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const displayName = user.getDisplayName();
|
||||
APP.store.dispatch(participantJoined({
|
||||
botType: user.getBotType(),
|
||||
conference: room,
|
||||
id,
|
||||
name: displayName,
|
||||
presence: user.getStatus(),
|
||||
role: user.getRole()
|
||||
}));
|
||||
|
||||
logger.log(`USER ${id} connnected:`, user);
|
||||
logger.log('USER %s connnected', id, user);
|
||||
APP.API.notifyUserJoined(id, {
|
||||
displayName,
|
||||
formattedDisplayName: appendSuffix(
|
||||
@@ -1714,14 +1691,14 @@ export default {
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
|
||||
// The logic shared between RN and web.
|
||||
commonUserLeftHandling(APP.store, room, user);
|
||||
|
||||
if (user.isHidden()) {
|
||||
APP.store.dispatch(hiddenParticipantLeft(id));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log(`USER ${id} LEFT:`, user);
|
||||
APP.store.dispatch(participantLeft(id, room));
|
||||
logger.log('USER %s LEFT', id, user);
|
||||
APP.API.notifyUserLeft(id);
|
||||
APP.UI.messageHandler.participantNotification(
|
||||
user.getDisplayName(),
|
||||
@@ -2398,24 +2375,11 @@ export default {
|
||||
createLocalTracksF,
|
||||
newDevices.videoinput,
|
||||
newDevices.audioinput)
|
||||
.then(tracks => {
|
||||
// If audio or video muted before, or we unplugged current
|
||||
// device and selected new one, then mute new track.
|
||||
const muteSyncPromises = tracks.map(track => {
|
||||
if ((track.isVideoTrack() && videoWasMuted)
|
||||
|| (track.isAudioTrack() && audioWasMuted)) {
|
||||
return track.mute();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
return Promise.all(muteSyncPromises)
|
||||
.then(() => Promise.all(
|
||||
this._setLocalAudioVideoStreams(tracks)));
|
||||
})
|
||||
.then(tracks =>
|
||||
Promise.all(this._setLocalAudioVideoStreams(tracks)))
|
||||
.then(() => {
|
||||
// Log and sync known mute state.
|
||||
// If audio was muted before, or we unplugged current device
|
||||
// and selected new one, then mute new audio track.
|
||||
if (audioWasMuted) {
|
||||
sendAnalytics(createTrackMutedEvent(
|
||||
'audio',
|
||||
@@ -2424,6 +2388,8 @@ export default {
|
||||
muteLocalAudio(true);
|
||||
}
|
||||
|
||||
// If video was muted before, or we unplugged current device
|
||||
// and selected new one, then mute new video track.
|
||||
if (!this.isSharingScreen && videoWasMuted) {
|
||||
sendAnalytics(createTrackMutedEvent(
|
||||
'video',
|
||||
|
||||
37
config.js
37
config.js
@@ -256,10 +256,6 @@ var config = {
|
||||
// maintenance at 01:00 AM GMT,
|
||||
// noticeMessage: '',
|
||||
|
||||
// Enables calendar integration, depends on googleApiApplicationClientID
|
||||
// and microsoftApiApplicationClientID
|
||||
// enableCalendarIntegration: false,
|
||||
|
||||
// Stats
|
||||
//
|
||||
|
||||
@@ -351,36 +347,6 @@ var config = {
|
||||
// userRegion: "asia"
|
||||
}
|
||||
|
||||
// Local Recording
|
||||
//
|
||||
|
||||
// localRecording: {
|
||||
// Enables local recording.
|
||||
// Additionally, 'localrecording' (all lowercase) needs to be added to
|
||||
// TOOLBAR_BUTTONS in interface_config.js for the Local Recording
|
||||
// button to show up on the toolbar.
|
||||
//
|
||||
// enabled: true,
|
||||
//
|
||||
|
||||
// The recording format, can be one of 'ogg', 'flac' or 'wav'.
|
||||
// format: 'flac'
|
||||
//
|
||||
|
||||
// }
|
||||
|
||||
// Options related to end-to-end (participant to participant) ping.
|
||||
// e2eping: {
|
||||
// // The interval in milliseconds at which pings will be sent.
|
||||
// // Defaults to 10000, set to <= 0 to disable.
|
||||
// pingInterval: 10000,
|
||||
//
|
||||
// // The interval in milliseconds at which analytics events
|
||||
// // with the measured RTT will be sent. Defaults to 60000, set
|
||||
// // to <= 0 to disable.
|
||||
// analyticsInterval: 60000,
|
||||
// }
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
_immediateReloadThreshold
|
||||
@@ -400,10 +366,8 @@ var config = {
|
||||
externalConnectUrl
|
||||
firefox_fake_device
|
||||
googleApiApplicationClientID
|
||||
googleApiIOSClientID
|
||||
iAmRecorder
|
||||
iAmSipGateway
|
||||
microsoftApiApplicationClientID
|
||||
peopleSearchQueryTypes
|
||||
peopleSearchUrl
|
||||
requireDisplayName
|
||||
@@ -432,7 +396,6 @@ var config = {
|
||||
nick
|
||||
startBitrate
|
||||
*/
|
||||
|
||||
};
|
||||
|
||||
/* eslint-enable no-unused-vars, no-var */
|
||||
|
||||
@@ -10,31 +10,3 @@
|
||||
-ms-transform: translateX(0) translateY(100%) translateY(16px) !important;
|
||||
-webkit-transform: translateX(0) translateY(100%) translateY(16px) !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Welcome page tab color adjustments.
|
||||
*/
|
||||
.welcome {
|
||||
/**
|
||||
* The text color of the selected tab and hovered tabs.
|
||||
*/
|
||||
li.bcVmZW,
|
||||
li.bcVmZW:hover,
|
||||
li.kheoEp:hover {
|
||||
color: #172B4D;
|
||||
}
|
||||
|
||||
/**
|
||||
* The color of the inactive tab text.
|
||||
*/
|
||||
li.kheoEp {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* The color of the underline of a selected tab.
|
||||
*/
|
||||
li>span.kByArU {
|
||||
background-color: #172B4D;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,55 +13,23 @@
|
||||
float: left;
|
||||
}
|
||||
.navigate-section-list-tile {
|
||||
background-color: #1754A9;
|
||||
height: 90px;
|
||||
width: 260px;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
margin-bottom: 8px;
|
||||
background-color: #1754A9;
|
||||
margin-right: 8px;
|
||||
min-height: 100px;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
|
||||
&.with-click-handler {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.with-click-handler:hover {
|
||||
background-color: #1a5dbb;
|
||||
}
|
||||
|
||||
i {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.element-after {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.join-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover .join-button {
|
||||
display: block
|
||||
}
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
.navigate-section-tile-body {
|
||||
@extend %navigate-section-list-tile-text;
|
||||
font-weight: normal;
|
||||
line-height: 24px;
|
||||
}
|
||||
.navigate-section-list-tile-info {
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
.navigate-section-tile-title {
|
||||
@extend %navigate-section-list-tile-text;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
}
|
||||
.navigate-section-section-header {
|
||||
@extend %navigate-section-list-text;
|
||||
@@ -72,8 +40,4 @@
|
||||
position: relative;
|
||||
margin-top: 36px;
|
||||
margin-bottom: 36px;
|
||||
width: 100%;
|
||||
}
|
||||
.navigate-section-list-empty {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -2,42 +2,6 @@
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.recording-dialog {
|
||||
.authorization-panel {
|
||||
border-bottom: 2px solid rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.dropbox-sign-in {
|
||||
align-items: center;
|
||||
border: 1px solid #4285f4;
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 10px 0px;
|
||||
color: #4285f4;
|
||||
|
||||
.dropbox-logo {
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
padding-right: 5px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.logged-in-pannel {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.live-stream-dialog {
|
||||
/**
|
||||
* Set font-size to be consistent with Atlaskit FieldText.
|
||||
@@ -70,6 +34,39 @@
|
||||
color: $errorColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Google sign in button must follow Google's design guidelines.
|
||||
* See: https://developers.google.com/identity/branding-guidelines
|
||||
*/
|
||||
.google-sign-in {
|
||||
background-color: #4285f4;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-family: Roboto, arial, sans-serif;
|
||||
font-size: 14px;
|
||||
padding: 1px;
|
||||
|
||||
.google-cta {
|
||||
color: white;
|
||||
display: inline-block;
|
||||
/**
|
||||
* Hack the line height for vertical centering of text.
|
||||
*/
|
||||
line-height: 32px;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.google-logo {
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.google-panel {
|
||||
align-items: center;
|
||||
border-bottom: 2px solid rgba(0, 0, 0, 0.3);
|
||||
|
||||
@@ -2,19 +2,12 @@
|
||||
bottom: 10%;
|
||||
font-size: 16px;
|
||||
font-weight: 1000;
|
||||
left: 50%;
|
||||
max-width: 50vw;
|
||||
opacity: 0.80;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 1px 1px rgba(0,0,0,0.3),
|
||||
1px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 0px 1px rgba(0,0,0,0.3);
|
||||
transform: translateX(-50%);
|
||||
z-index: $filmstripVideosZ + 1;
|
||||
|
||||
span {
|
||||
background: black;
|
||||
}
|
||||
width: 100%;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ body.welcome-page {
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#enter_room {
|
||||
@@ -63,30 +62,12 @@ body.welcome-page {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
width: 650px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-page-button {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.welcome-page-settings {
|
||||
color: $welcomePageDescriptionColor;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
z-index: $zindex2;
|
||||
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-watermark {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
@@ -14,9 +14,14 @@
|
||||
* Focused video thumbnail.
|
||||
*/
|
||||
&.videoContainerFocused {
|
||||
border: $thumbnailVideoBorder solid $videoThumbnailSelected;
|
||||
transition-duration: 0.5s;
|
||||
-webkit-transition-duration: 0.5s;
|
||||
-webkit-animation-name: greyPulse;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: 1;
|
||||
border: $thumbnailVideoBorder solid $videoThumbnailSelected !important;
|
||||
box-shadow: inset 0 0 3px $videoThumbnailSelected,
|
||||
0 0 3px $videoThumbnailSelected;
|
||||
0 0 3px $videoThumbnailSelected !important;
|
||||
}
|
||||
|
||||
.remotevideomenu > .icon-menu {
|
||||
@@ -26,7 +31,7 @@
|
||||
/**
|
||||
* Hovered video thumbnail.
|
||||
*/
|
||||
&:hover:not(.videoContainerFocused):not(.active-speaker) {
|
||||
&:hover {
|
||||
cursor: hand;
|
||||
border: $thumbnailVideoBorder solid $videoThumbnailHovered;
|
||||
box-shadow: inset 0 0 3px $videoThumbnailHovered,
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
/**
|
||||
* CSS styles that are specific to the filmstrip that shows the thumbnail tiles.
|
||||
*/
|
||||
.tile-view {
|
||||
/**
|
||||
* Add a border around the active speaker to make the thumbnail easier to
|
||||
* see.
|
||||
*/
|
||||
.active-speaker {
|
||||
box-shadow: 0 0 5px 3px $videoThumbnailSelected
|
||||
}
|
||||
|
||||
#filmstripRemoteVideos {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.filmstrip__videos .videocontainer {
|
||||
&:not(.active-speaker),
|
||||
&:hover:not(.active-speaker) {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
#remoteVideos {
|
||||
/**
|
||||
* Height is modified with an inline style in horizontal filmstrip mode
|
||||
* so !important is used to override that.
|
||||
*/
|
||||
height: 100% !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: $filmstripVideosZ
|
||||
}
|
||||
|
||||
/**
|
||||
* Regardless of the user setting, do not let the filmstrip be in a hidden
|
||||
* state.
|
||||
*/
|
||||
.filmstrip__videos.hidden {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#filmstripRemoteVideos {
|
||||
box-sizing: border-box;
|
||||
|
||||
/**
|
||||
* Allow vertical scrolling of the thumbnails.
|
||||
*/
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* The size of the thumbnails should be set with javascript, based on
|
||||
* desired column count and window width. The rows are created using flex
|
||||
* and allowing the thumbnails to wrap.
|
||||
*/
|
||||
#filmstripRemoteVideosContainer {
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 100vh;
|
||||
justify-content: center;
|
||||
padding: 100px 0;
|
||||
|
||||
.videocontainer {
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
video {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.has-overflow#filmstripRemoteVideosContainer {
|
||||
align-content: baseline;
|
||||
}
|
||||
|
||||
.has-overflow .videocontainer {
|
||||
align-self: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Firefox flex acts a little differently. To make sure the bottom row of
|
||||
* thumbnails is not overlapped by the horizontal toolbar, margin is added
|
||||
* to the local thumbnail to keep it from the bottom of the screen. It is
|
||||
* assumed the local thumbnail will always be on the bottom row.
|
||||
*/
|
||||
.has-overflow #localVideoContainer {
|
||||
margin-bottom: 100px !important;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* Various overrides outside of the filmstrip to style the app to support a
|
||||
* tiled thumbnail experience.
|
||||
*/
|
||||
.tile-view {
|
||||
/**
|
||||
* Let the avatar grow with the tile.
|
||||
*/
|
||||
.userAvatar {
|
||||
max-height: initial;
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide various features that should not be displayed while in tile view.
|
||||
*/
|
||||
#dominantSpeaker,
|
||||
#filmstripLocalVideoThumbnail,
|
||||
#largeVideoElementsContainer,
|
||||
#sharedVideo,
|
||||
.filmstrip__toolbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#localConnectionMessage,
|
||||
#remoteConnectionMessage,
|
||||
.watermark {
|
||||
z-index: $filmstripVideosZ + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The follow styling uses !important to override inline styles set with
|
||||
* javascript.
|
||||
*
|
||||
* TODO: These overrides should be more easy to remove and should be removed
|
||||
* when the components are in react so their rendering done declaratively,
|
||||
* making conditional styling easier to apply.
|
||||
*/
|
||||
#largeVideoElementsContainer,
|
||||
#remoteConnectionMessage,
|
||||
#remotePresenceMessage {
|
||||
display: none !important;
|
||||
}
|
||||
#largeVideoContainer {
|
||||
background-color: $defaultBackground !important;
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,6 @@
|
||||
@import 'modals/settings/settings';
|
||||
@import 'modals/speaker_stats/speaker_stats';
|
||||
@import 'modals/video-quality/video-quality';
|
||||
@import 'modals/local-recording/local-recording';
|
||||
@import 'videolayout_default';
|
||||
@import 'notice';
|
||||
@import 'popup_menu';
|
||||
@@ -73,8 +72,6 @@
|
||||
@import 'filmstrip/filmstrip_toolbar';
|
||||
@import 'filmstrip/horizontal_filmstrip';
|
||||
@import 'filmstrip/small_video';
|
||||
@import 'filmstrip/tile_view';
|
||||
@import 'filmstrip/tile_view_overrides';
|
||||
@import 'filmstrip/vertical_filmstrip';
|
||||
@import 'filmstrip/vertical_filmstrip_overrides';
|
||||
@import 'unsupported-browser/main';
|
||||
@@ -82,7 +79,4 @@
|
||||
@import 'deep-linking/main';
|
||||
@import 'transcription-subtitles';
|
||||
@import 'navigate_section_list';
|
||||
@import 'third-party-branding/google';
|
||||
@import 'third-party-branding/microsoft';
|
||||
|
||||
/* Modules END */
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
.localrec-participant-stats {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
|
||||
.localrec-participant-stats-item__status-dot {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
margin: 0 auto;
|
||||
|
||||
&.status-on {
|
||||
background: green;
|
||||
}
|
||||
|
||||
&.status-off {
|
||||
background: gray;
|
||||
}
|
||||
|
||||
&.status-unknown {
|
||||
background: darkgoldenrod;
|
||||
}
|
||||
|
||||
&.status-error {
|
||||
background: darkred;
|
||||
}
|
||||
}
|
||||
|
||||
.localrec-participant-stats-item__status,
|
||||
.localrec-participant-stats-item__name,
|
||||
.localrec-participant-stats-item__sessionid {
|
||||
display: inline-block;
|
||||
margin: 5px 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.localrec-participant-stats-item__status {
|
||||
width: 5%;
|
||||
}
|
||||
.localrec-participant-stats-item__name {
|
||||
width: 40%;
|
||||
}
|
||||
.localrec-participant-stats-item__sessionid {
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
.localrec-participant-stats-item__name,
|
||||
.localrec-participant-stats-item__sessionid {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.localrec-control-info-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.localrec-control-info-label:after {
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
.localrec-control-action-link {
|
||||
display: inline-block;
|
||||
line-height: 1.5em;
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.localrec-control-action-link:before {
|
||||
color: $linkFontColor;
|
||||
content: '\2022';
|
||||
font-size: 1.5em;
|
||||
padding: 0 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.localrec-control-action-link:first-child:before {
|
||||
content: '';
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.localrec-control-action-links {
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -10,7 +10,6 @@
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.calendar-tab,
|
||||
.device-selection {
|
||||
margin-top: 20px;
|
||||
}
|
||||
@@ -23,7 +22,6 @@
|
||||
padding: 20px 0px 4px 0px;
|
||||
}
|
||||
|
||||
.calendar-tab,
|
||||
.more-tab,
|
||||
.profile-edit {
|
||||
display: flex;
|
||||
@@ -42,20 +40,4 @@
|
||||
.language-settings {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.calendar-tab {
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-size: 14px;
|
||||
min-height: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.calendar-tab-sign-in {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.sign-out-cta {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,10 +168,6 @@
|
||||
background: #FF5630;
|
||||
}
|
||||
|
||||
.circular-label.local-rec {
|
||||
background: #FF5630;
|
||||
}
|
||||
|
||||
.circular-label.stream {
|
||||
background: #0065FF;
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* The Google sign in button must follow Google's design guidelines.
|
||||
* See: https://developers.google.com/identity/branding-guidelines
|
||||
*/
|
||||
.google-sign-in {
|
||||
background-color: #4285f4;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-family: Roboto, arial, sans-serif;
|
||||
font-size: 14px;
|
||||
padding: 1px;
|
||||
|
||||
.google-cta {
|
||||
color: white;
|
||||
display: inline-block;
|
||||
/**
|
||||
* Hack the line height for vertical centering of text.
|
||||
*/
|
||||
line-height: 32px;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.google-logo {
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* The Microsoft sign in button must follow Microsoft's brand guidelines.
|
||||
* See: https://docs.microsoft.com/en-us/azure/active-directory/
|
||||
* develop/active-directory-branding-guidelines
|
||||
*/
|
||||
.microsoft-sign-in {
|
||||
align-items: center;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #8C8C8C;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-family: Segoe UI, Roboto, arial, sans-serif;
|
||||
height: 41px;
|
||||
padding: 12px;
|
||||
|
||||
.microsoft-cta {
|
||||
display: inline-block;
|
||||
color: #5E5E5E;
|
||||
font-size: 15px;
|
||||
line-height: 41px;
|
||||
}
|
||||
|
||||
.microsoft-logo {
|
||||
display: inline-block;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
@@ -135,8 +135,7 @@ server {
|
||||
location / {
|
||||
ssi on;
|
||||
}
|
||||
# BOSH, Bidirectional-streams Over Synchronous HTTP
|
||||
# https://en.wikipedia.org/wiki/BOSH_(protocol)
|
||||
# BOSH
|
||||
location /http-bind {
|
||||
proxy_pass http://localhost:5280/http-bind;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# Setting up Google Authentication
|
||||
|
||||
- Create a Firebase project here: https://firebase.google.com/. You'll need a
|
||||
signed Android build for that, that can be a debug auto-signed build too, just
|
||||
retrieve the signing hash.
|
||||
- Place the generated ```google-services.json``` file in ```android/app```
|
||||
for Android and the ```GoogleService-Info.plist``` into ```ios/app/src``` for
|
||||
iOS (you can stop at that step, no need for the driver and the code changes they
|
||||
suggest in the wizard).
|
||||
- You may want to exclude these files in YOUR GIT config (do not exclude them in
|
||||
the ```.gitignore``` of the application itself!).
|
||||
- Your WEB and iOS client IDs are auto generated during the Firebase project
|
||||
creation. Find them in the Google Developer console:
|
||||
https://console.developers.google.com/
|
||||
- Make sure your config reflects these IDs so then the Redux state of the
|
||||
feature ```features/base/config``` contains variables
|
||||
```googleApiApplicationClientID``` and ```googleApiIOSClientID``` with the
|
||||
respective values.
|
||||
- Add your iOS client ID as an application URL schema into
|
||||
```ios/app/src/Info.plist``` (replacing placeholder).
|
||||
- Enable YouTube API access on the developer console (see above) for live
|
||||
streaming.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.8 KiB |
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="324px" height="63.8px" viewBox="0 0 324 63.8" style="enable-background:new 0 0 324 63.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0061FF;}
|
||||
.st1{display:none;}
|
||||
.st2{display:inline;}
|
||||
.st3{fill:none;}
|
||||
</style>
|
||||
<path class="st0" d="M37.6,12L18.8,24l18.8,12L18.8,48L0,35.9l18.8-12L0,12L18.8,0L37.6,12z M18.7,51.8l18.8-12l18.8,12l-18.8,12
|
||||
L18.7,51.8z M37.6,35.9l18.8-12L37.6,12L56.3,0l18.8,12L56.3,24l18.8,12L56.3,48L37.6,35.9z"/>
|
||||
<path d="M89.8,12H105c9.7,0,17.7,5.6,17.7,18.4v2.7c0,12.9-7.5,18.7-17.4,18.7H89.8V12z M98.3,19.2v25.3h6.5c5.5,0,9.2-3.6,9.2-11.6
|
||||
v-2.1c0-8-3.9-11.6-9.5-11.6H98.3z M127.2,19.6h6.8l1.1,7.5c1.3-5.1,4.6-7.8,10.6-7.8h2.1v8.6h-3.5c-6.9,0-8.6,2.4-8.6,9.2v14.8
|
||||
h-8.4V19.6H127.2z M149.5,36.4v-0.9c0-10.8,6.9-16.7,16.3-16.7c9.6,0,16.3,5.9,16.3,16.7v0.9c0,10.6-6.5,16.3-16.3,16.3
|
||||
C155.4,52.6,149.5,47,149.5,36.4z M173.5,36.3v-0.8c0-6-3-9.6-7.8-9.6c-4.7,0-7.8,3.3-7.8,9.6v0.8c0,5.8,3,9.1,7.8,9.1
|
||||
C170.5,45.3,173.5,42.1,173.5,36.3z M186.5,19.6h7l0.8,6.1c1.7-4.1,5.3-6.9,10.6-6.9c8.2,0,13.6,5.9,13.6,16.8v0.9
|
||||
c0,10.6-6,16.2-13.6,16.2c-5.1,0-8.6-2.3-10.3-6V63h-8.2L186.5,19.6L186.5,19.6z M210,36.3v-0.7c0-6.4-3.3-9.6-7.7-9.6
|
||||
c-4.7,0-7.8,3.6-7.8,9.6v0.6c0,5.7,3,9.3,7.7,9.3C207,45.4,210,42.3,210,36.3z M230.9,45.9l-0.7,5.9H223v-43h8.2v16.5
|
||||
c1.8-4.2,5.4-6.5,10.5-6.5c7.7,0.1,13.4,5.4,13.4,16.1v1c0,10.7-5.4,16.8-13.6,16.8C236.1,52.6,232.6,50.1,230.9,45.9z M246.5,35.9
|
||||
v-0.8c0-5.9-3.2-9.2-7.7-9.2c-4.6,0-7.8,3.7-7.8,9.3v0.7c0,6,3.1,9.5,7.7,9.5C243.6,45.4,246.5,42.3,246.5,35.9z M258.7,36.4v-0.9
|
||||
c0-10.8,6.9-16.7,16.3-16.7c9.6,0,16.3,5.9,16.3,16.7v0.9c0,10.6-6.6,16.3-16.3,16.3C264.6,52.6,258.7,47,258.7,36.4z M282.8,36.3
|
||||
v-0.8c0-6-3-9.6-7.8-9.6c-4.7,0-7.8,3.3-7.8,9.6v0.8c0,5.8,3,9.1,7.8,9.1C279.8,45.3,282.8,42.1,282.8,36.3z M302.3,35.1L291,19.6
|
||||
h9.7l6.5,9.7l6.6-9.7h9.6L311.9,35L324,51.8h-9.5l-7.4-10.7l-7.2,10.7H290L302.3,35.1z"/>
|
||||
<g id="Editble" class="st1">
|
||||
<g class="st2">
|
||||
<rect x="-105" y="5" class="st3" width="506" height="71.8"/>
|
||||
<path d="M0.2,13.6h16.3c10.4,0,19,6.1,19,19.8v2.9c0,13.8-8,20-18.7,20H0.2V13.6z M9.4,21.3v27.2h7c5.9,0,9.9-3.9,9.9-12.5v-2.2
|
||||
c0-8.6-4.1-12.5-10.2-12.5H9.4z M40.4,21.8h7.3l1.1,8c1.4-5.5,4.9-8.3,11.3-8.3h2.2v9.2h-3.7c-7.4,0-9.2,2.6-9.2,9.9v15.8h-9
|
||||
C40.4,56.4,40.4,21.8,40.4,21.8z M64.3,39.8v-1c0-11.6,7.4-17.9,17.5-17.9c10.3,0,17.5,6.4,17.5,17.9v1c0,11.4-7,17.5-17.5,17.5
|
||||
C70.6,57.3,64.3,51.2,64.3,39.8z M90.1,39.7v-0.8c0-6.5-3.2-10.3-8.3-10.3c-5,0-8.4,3.5-8.4,10.3v0.8c0,6.2,3.2,9.7,8.3,9.7
|
||||
C86.9,49.4,90.1,46,90.1,39.7z M104,21.8h7.6l0.9,6.6c1.9-4.4,5.7-7.4,11.4-7.4c8.8,0,14.6,6.4,14.6,18v1
|
||||
c0,11.4-6.4,17.3-14.6,17.3c-5.5,0-9.2-2.5-11-6.5v17.5H104V21.8z M129.3,39.8V39c0-6.9-3.5-10.3-8.3-10.3c-5,0-8.4,3.8-8.4,10.3
|
||||
v0.7c0,6.1,3.2,10,8.2,10C126,49.5,129.3,46.1,129.3,39.8z M151.7,50.1l-0.7,6.3h-7.8V10.2h8.8V28c1.9-4.5,5.8-7,11.2-7
|
||||
c8.2,0.1,14.3,5.8,14.3,17.3v1c0,11.5-5.8,18-14.6,18C157.3,57.3,153.5,54.5,151.7,50.1z M168.5,39.3v-0.8c0-6.4-3.5-9.8-8.3-9.8
|
||||
c-5,0-8.4,4-8.4,10v0.7c0,6.5,3.3,10.2,8.3,10.2C165.3,49.5,168.5,46.1,168.5,39.3z M181.6,39.8v-1c0-11.6,7.4-17.9,17.5-17.9
|
||||
c10.3,0,17.5,6.4,17.5,17.9v1c0,11.4-7.1,17.5-17.5,17.5C187.9,57.3,181.6,51.2,181.6,39.8z M207.4,39.7v-0.8
|
||||
c0-6.5-3.2-10.3-8.3-10.3c-5,0-8.4,3.5-8.4,10.3v0.8c0,6.2,3.2,9.7,8.3,9.7C204.2,49.4,207.4,46,207.4,39.7z M228.3,38.4
|
||||
l-12.1-16.7h10.4l7,10.4l7.1-10.4H251l-12.3,16.6l13,18h-10.2l-8-11.5l-7.7,11.5h-10.6L228.3,38.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21"><title>MS-SymbolLockup</title><rect x="1" y="1" width="9" height="9" fill="#f25022"/><rect x="1" y="11" width="9" height="9" fill="#00a4ef"/><rect x="11" y="1" width="9" height="9" fill="#7fba00"/><rect x="11" y="11" width="9" height="9" fill="#ffb900"/></svg>
|
||||
|
Before Width: | Height: | Size: 343 B |
@@ -48,11 +48,10 @@ var interfaceConfig = {
|
||||
'microphone', 'camera', 'closedcaptions', 'desktop', 'fullscreen',
|
||||
'fodeviceselection', 'hangup', 'profile', 'info', 'chat', 'recording',
|
||||
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
||||
'tileview'
|
||||
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts'
|
||||
],
|
||||
|
||||
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
|
||||
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile' ],
|
||||
|
||||
// Determines how the video would fit the screen. 'both' would fit the whole
|
||||
// screen, 'height' would fit the original video height to the height of the
|
||||
@@ -173,12 +172,6 @@ var interfaceConfig = {
|
||||
*/
|
||||
RECENT_LIST_ENABLED: true
|
||||
|
||||
/**
|
||||
* How many columns the tile view can expand to. The respected range is
|
||||
* between 1 and 5.
|
||||
*/
|
||||
// TILE_VIEW_MAX_COLUMNS: 5,
|
||||
|
||||
/**
|
||||
* Specify custom URL for downloading android mobile app.
|
||||
*/
|
||||
|
||||
@@ -35,8 +35,8 @@ target 'JitsiMeet' do
|
||||
pod 'react-native-locale-detector',
|
||||
:path => '../node_modules/react-native-locale-detector'
|
||||
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
|
||||
pod 'RNGoogleSignin',
|
||||
:path => '../node_modules/react-native-google-signin'
|
||||
pod 'ReactNativePermissions',
|
||||
:path => '../node_modules/react-native-permissions'
|
||||
pod 'RNSound', :path => '../node_modules/react-native-sound'
|
||||
pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
|
||||
pod 'react-native-calendar-events',
|
||||
|
||||
@@ -7,31 +7,11 @@ PODS:
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- glog (0.3.4)
|
||||
- GoogleSignIn (4.2.0):
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)"
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (~> 2.1)"
|
||||
- GTMOAuth2 (~> 1.0)
|
||||
- GTMSessionFetcher/Core (~> 1.1)
|
||||
- GoogleToolboxForMac/DebugUtils (2.1.4):
|
||||
- GoogleToolboxForMac/Defines (= 2.1.4)
|
||||
- GoogleToolboxForMac/Defines (2.1.4)
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (2.1.4)":
|
||||
- GoogleToolboxForMac/DebugUtils (= 2.1.4)
|
||||
- GoogleToolboxForMac/Defines (= 2.1.4)
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (= 2.1.4)"
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (2.1.4)"
|
||||
- GTMOAuth2 (1.1.6):
|
||||
- GTMSessionFetcher (~> 1.1)
|
||||
- GTMSessionFetcher (1.2.0):
|
||||
- GTMSessionFetcher/Full (= 1.2.0)
|
||||
- GTMSessionFetcher/Core (1.2.0)
|
||||
- GTMSessionFetcher/Full (1.2.0):
|
||||
- GTMSessionFetcher/Core (= 1.2.0)
|
||||
- React (0.55.4):
|
||||
- React/Core (= 0.55.4)
|
||||
- react-native-background-timer (2.0.0):
|
||||
- React
|
||||
- react-native-calendar-events (1.6.2):
|
||||
- react-native-calendar-events (1.6.0):
|
||||
- React
|
||||
- react-native-fast-image (4.0.14):
|
||||
- FLAnimatedImage
|
||||
@@ -42,7 +22,7 @@ PODS:
|
||||
- React
|
||||
- react-native-locale-detector (1.0.0):
|
||||
- React
|
||||
- react-native-webrtc (1.63.0):
|
||||
- react-native-webrtc (1.58.2):
|
||||
- React
|
||||
- React/Core (0.55.4):
|
||||
- yoga (= 0.55.4.React)
|
||||
@@ -83,8 +63,7 @@ PODS:
|
||||
- React/Core
|
||||
- React/fishhook
|
||||
- React/RCTBlob
|
||||
- RNGoogleSignin (1.0.0-rc3):
|
||||
- GoogleSignIn
|
||||
- ReactNativePermissions (1.1.1):
|
||||
- React
|
||||
- RNSound (0.10.9):
|
||||
- React/Core
|
||||
@@ -119,7 +98,7 @@ DEPENDENCIES:
|
||||
- React/RCTNetwork (from `../node_modules/react-native`)
|
||||
- React/RCTText (from `../node_modules/react-native`)
|
||||
- React/RCTWebSocket (from `../node_modules/react-native`)
|
||||
- RNGoogleSignin (from `../node_modules/react-native-google-signin`)
|
||||
- ReactNativePermissions (from `../node_modules/react-native-permissions`)
|
||||
- RNSound (from `../node_modules/react-native-sound`)
|
||||
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
|
||||
- yoga (from `../node_modules/react-native/ReactCommon/yoga`)
|
||||
@@ -128,10 +107,6 @@ SPEC REPOS:
|
||||
https://github.com/cocoapods/specs.git:
|
||||
- boost-for-react-native
|
||||
- FLAnimatedImage
|
||||
- GoogleSignIn
|
||||
- GoogleToolboxForMac
|
||||
- GTMOAuth2
|
||||
- GTMSessionFetcher
|
||||
- SDWebImage
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
@@ -155,8 +130,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-locale-detector"
|
||||
react-native-webrtc:
|
||||
:path: "../node_modules/react-native-webrtc"
|
||||
RNGoogleSignin:
|
||||
:path: "../node_modules/react-native-google-signin"
|
||||
ReactNativePermissions:
|
||||
:path: "../node_modules/react-native-permissions"
|
||||
RNSound:
|
||||
:path: "../node_modules/react-native-sound"
|
||||
RNVectorIcons:
|
||||
@@ -170,10 +145,6 @@ SPEC CHECKSUMS:
|
||||
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
|
||||
Folly: 211775e49d8da0ca658aebc8eab89d642935755c
|
||||
glog: 1de0bb937dccdc981596d3b5825ebfb765017ded
|
||||
GoogleSignIn: 591e46382014e591269f862ba6e7bc0fbd793532
|
||||
GoogleToolboxForMac: 91c824d21e85b31c2aae9bb011c5027c9b4e738f
|
||||
GTMOAuth2: c77fe325e4acd453837e72d91e3b5f13116857b2
|
||||
GTMSessionFetcher: 0c4baf0a73acd0041bf9f71ea018deedab5ea84e
|
||||
React: aa2040dbb6f317b95314968021bd2888816e03d5
|
||||
react-native-background-timer: 63dcbf37dbcf294b5c6c071afcdc661fa06a7594
|
||||
react-native-calendar-events: fe6fbc8ed337a7423c98f2c9012b25f20444de09
|
||||
@@ -181,12 +152,12 @@ SPEC CHECKSUMS:
|
||||
react-native-keep-awake: 0de4bd66de0c23178107dce0c2fcc3354b2a8e94
|
||||
react-native-locale-detector: d1b2c6fe5abb56e3a1efb6c2d6f308c05c4251f1
|
||||
react-native-webrtc: 31b6d3f1e3e2ce373aa43fd682b04367250f807d
|
||||
RNGoogleSignin: 44debd8c359a662c0e2d585952e88b985bf78008
|
||||
ReactNativePermissions: 9f2d9c45c98800795e6c2ed330e25d11a66a8169
|
||||
RNSound: b360b3862d3118ed1c74bb9825696b5957686ac4
|
||||
RNVectorIcons: c0dbfbf6068fefa240c37b0f71bd03b45dddac44
|
||||
SDWebImage: 624d6e296c69b244bcede364c72ae0430ac14681
|
||||
yoga: a23273df0088bf7f2bb7e5d7b00044ea57a2a54a
|
||||
|
||||
PODFILE CHECKSUM: da74c08f6eb674668c49d8d799f8d9e2476a9fc5
|
||||
PODFILE CHECKSUM: 1d5c8382f73d9540fac68d93b32e1d3b58d069ee
|
||||
|
||||
COCOAPODS: 1.5.3
|
||||
|
||||
@@ -32,16 +32,6 @@
|
||||
<string>org.jitsi.meet</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.googleusercontent.apps</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>com.googleusercontent.apps.YOUR_ID_HERE</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
|
||||
@@ -376,8 +376,6 @@
|
||||
);
|
||||
inputPaths = (
|
||||
"${SRCROOT}/../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources.sh",
|
||||
"${PODS_ROOT}/GTMOAuth2/Source/Touch/GTMOAuth2ViewTouch.xib",
|
||||
"${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
|
||||
@@ -392,8 +390,6 @@
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GTMOAuth2ViewTouch.nib",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
{
|
||||
"en": "Inglês",
|
||||
"az": "Azerbaijanês",
|
||||
"bg": "Búlgaro",
|
||||
"cs": "Checo",
|
||||
"de": "Alemão",
|
||||
"el": "Grego",
|
||||
"eo": "Esperanto",
|
||||
"es": "Espanhol",
|
||||
"fr": "Francês",
|
||||
"hy": "Armênio",
|
||||
"it": "Italiano",
|
||||
"ja": "Japonês",
|
||||
"ko": "Coreano",
|
||||
"nb": "Bokmal norueguês",
|
||||
"oc": "Occitano",
|
||||
"pl": "Polonês",
|
||||
"ptBR": "Português (Brasil)",
|
||||
@@ -21,6 +14,7 @@
|
||||
"sl": "Esloveno",
|
||||
"sv": "Sueco",
|
||||
"tr": "Turco",
|
||||
"vi": "Vietnamita",
|
||||
"zhCN": "Chinês (China)"
|
||||
"zhCN": "Chinês (China)",
|
||||
"nb": "Bokmal norueguês",
|
||||
"eo": "Esperanto"
|
||||
}
|
||||
@@ -15,6 +15,6 @@
|
||||
"sv": "Шведский",
|
||||
"tr": "Турецкий",
|
||||
"zhCN": "Китайский (Китай)",
|
||||
"nb": "Норвежский букмол",
|
||||
"eo": "Эсперанто"
|
||||
"nb": "",
|
||||
"eo": ""
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"en": "English",
|
||||
"bg": "Bulgarian",
|
||||
"de": "German",
|
||||
"es": "Spanish",
|
||||
"fr": "French",
|
||||
"hy": "Armenian",
|
||||
"it": "Italian",
|
||||
"oc": "Occitan",
|
||||
"pl": "Polish",
|
||||
"ptBR": "Portuguese (Brazil)",
|
||||
"ru": "Russian",
|
||||
"sk": "Slovak",
|
||||
"sl": "Slovenian",
|
||||
"sv": "Swedish",
|
||||
"tr": "Turkish",
|
||||
"zhCN": "中文 简体 (中国)",
|
||||
"nb": "Norwegian Bokmal",
|
||||
"eo": "Esperanto"
|
||||
}
|
||||
@@ -35,39 +35,31 @@
|
||||
"raiseHand": "Erga ou baixe sua mão",
|
||||
"pushToTalk": "Pressione para falar",
|
||||
"toggleScreensharing": "Trocar entre câmera e compartilhamento de tela",
|
||||
"toggleFilmstrip": "Mostrar ou ocultar miniaturas de vídeo",
|
||||
"toggleShortcuts": "Mostrar ou ocultar atalhos de teclado",
|
||||
"toggleFilmstrip": "Mostrar ou ocultar a barra lateral",
|
||||
"toggleShortcuts": "Mostrar ou ocultar este menu de ajuda",
|
||||
"focusLocal": "Focar no seu vídeo",
|
||||
"focusRemote": "Focar no vídeo de outro participante",
|
||||
"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",
|
||||
"showSpeakerStats": "Exibir estatísticas do alto falante",
|
||||
"localRecording": "Mostrar ou ocultar controles de gravação local"
|
||||
"showSpeakerStats": "Exibir estatísticas do alto falante"
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
"join": "Toque para entrar",
|
||||
"roomname": "Digite o nome da sala"
|
||||
},
|
||||
"appDescription": "Vá em frente, converse por vídeo com toda a equipe. De fato, convide todos que você conhece. __app__ é uma solução de videoconferência totalmente criptografada e 100% de código aberto que você pode usar todos os dias, a cada dia, gratuitamente — sem necessidade de conta.",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "Voz",
|
||||
"video": "Vídeo"
|
||||
},
|
||||
"calendar": "Calendário",
|
||||
"connectCalendarText": "Conecte seu calendário para ver todas as suas reuniões no __app__. Além disso, adicione reuniões no __app__ ao seu calendário e inicie-as com um clique.",
|
||||
"connectCalendarButton": "Conectar seu calendário",
|
||||
"go": "IR",
|
||||
"join": "Entrar",
|
||||
"privacy": "Política de Privacidade",
|
||||
"recentList": "Hitstórico",
|
||||
"roomname": "Digite o nome da sala",
|
||||
"roomnameHint": "Digite o nome ou a URL da sala que você deseja entrar. Você pode digitar um nome, e apenas deixe para as pessoas que você quer se reunir digitem o mesmo nome.",
|
||||
"sendFeedback": "Enviar comentários",
|
||||
"terms": "Termos",
|
||||
"title": "Vídeo conferência mais segura, mais flexível e completamente livre."
|
||||
"title": "Vídeo conferência mais segura, mais flexível e completamente livre "
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
@@ -79,41 +71,8 @@
|
||||
"rejoinKeyTitle": "Reconectar"
|
||||
},
|
||||
"toolbar": {
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "Alternar para apenas áudio",
|
||||
"audioRoute": "",
|
||||
"callQuality": "Gerenciar qualidade da chamada",
|
||||
"chat": "Alternar para janela de chat",
|
||||
"cc": "Alternar legendas",
|
||||
"document": "Alternar para documento compartilhado",
|
||||
"feedback": "Deixar feedback",
|
||||
"fullScreen": "Alternar para tela cheia",
|
||||
"hangup": "Sair da chamada",
|
||||
"invite": "Convidar pessoas",
|
||||
"localRecording": "Alternar controles de gravação local",
|
||||
"lockRoom": "Alternar trava da sala",
|
||||
"moreActions": "Alternar mais menu de ações",
|
||||
"moreActionsMenu": "Menu de mais ações",
|
||||
"mute": "Alternar mudo do áudio",
|
||||
"pip": "Alternar modo Picture-in-Picture",
|
||||
"profile": "Editar seu perfil",
|
||||
"raiseHand": "Alternar levantar a mão",
|
||||
"recording": "Alternar gravação",
|
||||
"Settings": "Alternar configurações",
|
||||
"sharedvideo": "Alternar compartilhamento de vídeo do Youtube",
|
||||
"shareRoom": "Convidar alguém",
|
||||
"shareYourScreen": "Alternar compartilhamento de tela",
|
||||
"shortcuts": "Alternar atalhos",
|
||||
"speakerStats": "Alternar estatísticas do apresentador",
|
||||
"toggleCamera": "",
|
||||
"tileView": "",
|
||||
"videomute": "Alternar mudo do vídeo"
|
||||
},
|
||||
"addPeople": "Adicionar pessoas à sua chamada",
|
||||
"audioonly": "Ativar / desativar modo somente áudio (economiza banda)",
|
||||
"audioOnlyOn": "Ativar modo somente áudio (economiza banda)",
|
||||
"audioOnlyOff": "Desativar modo somente áudio",
|
||||
"audioRoute": "Selecionar o dispositivo de som",
|
||||
"callQuality": "Gerenciar qualidade da chamada",
|
||||
"enterFullScreen": "Ver em tela cheia",
|
||||
"exitFullScreen": "Sair da tela cheia",
|
||||
@@ -127,8 +86,8 @@
|
||||
"etherpad": "Abrir ou fechar o documento compartilhado",
|
||||
"documentOpen": "Abrir documento compartilhado",
|
||||
"documentClose": "Fechar documento compartilhado",
|
||||
"shareRoom": "Compartilhar sala",
|
||||
"sharedvideo": "Compartilhar um vídeo do YouTube",
|
||||
"sharescreen": "Compartilhamento de tela",
|
||||
"stopSharedVideo": "Parar vídeo do YouTube",
|
||||
"fullscreen": "Entrar ou sair da tela cheia",
|
||||
"sip": "Chamar número SIP",
|
||||
@@ -137,20 +96,16 @@
|
||||
"login": "Iniciar sessão",
|
||||
"logout": "Encerrar sessão",
|
||||
"sharedVideoMutedPopup": "Seu vídeo compartilhado foi silenciado para que você possa conversar com os outros membros.",
|
||||
"toggleCamera": "Alternar câmera",
|
||||
"micMutedPopup": "Seu microfone foi silenciado para que você aproveite 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",
|
||||
"pip": "Entrar em modo Quadro-a-Quadro",
|
||||
"profile": "Editar seu perfil",
|
||||
"raiseHand": "Erguer / Baixar sua mão",
|
||||
"raiseHand": "Erguer o baixar sua mão",
|
||||
"shortcuts": "Ver atalhos",
|
||||
"speakerStats": "Estatísticas do Apresentador",
|
||||
"tileViewToggle": "Alternar visualização em blocos",
|
||||
"invite": "Convidar pessoas"
|
||||
"speakerStats": "Estatísticas do Apresentador"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
@@ -160,13 +115,6 @@
|
||||
"messagebox": "Digite um texto..."
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "A integração do calendário __appName__ é usada para acessar com segurança o seu calendário para que ele possa ler os próximos eventos.",
|
||||
"disconnect": "Desconectar",
|
||||
"microsoftSignIn": "Entrar com Microsoft",
|
||||
"signedIn": "Atualmente acessando eventos do calendário para __email__. Clique no botão Desconectar abaixo para parar de acessar os eventos da agenda.",
|
||||
"title": "Calendário"
|
||||
},
|
||||
"title": "Configurações",
|
||||
"update": "Atualizar",
|
||||
"name": "Nome",
|
||||
@@ -176,15 +124,11 @@
|
||||
"selectMic": "Microfone",
|
||||
"selectAudioOutput": "Saída de áudio",
|
||||
"followMe": "Todos me seguem",
|
||||
"language": "Idioma",
|
||||
"loggedIn": "Conectado como __name__",
|
||||
"noDevice": "Nenhum",
|
||||
"cameraAndMic": "Câmera e microfone",
|
||||
"moderator": "Moderador",
|
||||
"more": "Mais",
|
||||
"moderator": "MODERADOR",
|
||||
"password": "DEFINIR SENHA",
|
||||
"audioVideo": "ÁUDIO E VÍDEO",
|
||||
"devices": "Dispositivos"
|
||||
"audioVideo": "ÁUDIO E VÍDEO"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Perfil",
|
||||
@@ -243,6 +187,7 @@
|
||||
"focus": "Foco da conferência",
|
||||
"focusFail": "__component__ não disponĩvel - tente em __ms__ seg.",
|
||||
"grantedTo": "Direitos de moderador concedido para __to__!",
|
||||
"grantedToUnknown": "Direitos de moderador concedido para $t(notify.somebody)!",
|
||||
"muted": "Você iniciou uma conversa em mudo.",
|
||||
"mutedTitle": "Você está mudo!",
|
||||
"raisedHand": "Gostaria de falar.",
|
||||
@@ -250,11 +195,7 @@
|
||||
"suboptimalExperienceDescription": "Eer ... temos medo de que sua experiência com o __appName__ não seja tão boa aqui. Estamos procurando maneiras de melhorar isso, mas até lá tente usar um dos <a href='static/recommendedBrowsers.html' target='_blank'> navegadores totalmente compatíveis</a>."
|
||||
},
|
||||
"dialog": {
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "Transmissão ao vivo"
|
||||
},
|
||||
"allow": "Permitir",
|
||||
"confirm": "Confirmar",
|
||||
"kickMessage": "Ouch! Você foi expulso da reunião!",
|
||||
"popupErrorTitle": "Popup bloqueado",
|
||||
"popupError": "Seu navegador está bloqueando janelas popup deste site. Habilite os popups nas configurações de segurança no seu navegador e tente novamente.",
|
||||
@@ -278,7 +219,6 @@
|
||||
"rejoinNow": "Reconectar agora",
|
||||
"maxUsersLimitReachedTitle": "Limite máximo de membros alcançado",
|
||||
"maxUsersLimitReached": "O limite para o máximo do número de membros foi alcançado. A conferência está cheia. Contate o dono da reunião ou tente de novo depois!",
|
||||
"lockRoom": "Travar sala",
|
||||
"lockTitle": "Bloqueio falhou",
|
||||
"lockMessage": "Falha ao travar a conferência.",
|
||||
"warning": "Atenção",
|
||||
@@ -326,7 +266,6 @@
|
||||
"reservationError": "Erro de sistema de reserva",
|
||||
"reservationErrorMsg": "Código do erro: __code__, mensagem: __msg__",
|
||||
"password": "Insira a senha",
|
||||
"unlockRoom": "Destravar sala",
|
||||
"userPassword": "senha do usuário",
|
||||
"token": "token",
|
||||
"tokenAuthFailedTitle": "Falha de autenticação",
|
||||
@@ -339,11 +278,11 @@
|
||||
"sorryFeedback": "Sentimos muito pelos transtornos. Poderia nos das mais detalhes?",
|
||||
"liveStreaming": "Transmissão ao Vivo",
|
||||
"streamKey": "Chave para transmissão ao vivo",
|
||||
"startLiveStreaming": "Iniciar transmissão ao vivo",
|
||||
"startLiveStreaming": "Ir ao vivo agora",
|
||||
"startRecording": "Parar gravação",
|
||||
"stopStreamingWarning": "Tem certeza que deseja parar a transmissão ao vivo?",
|
||||
"stopRecordingWarning": "Tem certeza que deseja parar a gravação?",
|
||||
"stopLiveStreaming": "Parar transmissão ao vivo",
|
||||
"stopLiveStreaming": "Parar a transmissão ao vivo",
|
||||
"stopRecording": "Parar a gravação",
|
||||
"doNotShowMessageAgain": "Não mostre esta mensagem novamente",
|
||||
"permissionDenied": "Permissão Negada",
|
||||
@@ -373,10 +312,6 @@
|
||||
"muteParticipantTitle": "Silenciar esse membro?",
|
||||
"muteParticipantBody": "Você não está habilitado para tirar o mudo deles, mas eles podem tirar o mudo deles mesmos a qualquer tempo.",
|
||||
"muteParticipantButton": "Mudo",
|
||||
"liveStreamingDisabledTooltip": "Iniciar transmissão ao vivo desativada.",
|
||||
"liveStreamingDisabledForGuestTooltip": "Visitantes não podem iniciar transmissão ao vivo.",
|
||||
"recordingDisabledTooltip": "Iniciar gravação desativada.",
|
||||
"recordingDisabledForGuestTooltip": "Visitantes não podem iniciar gravações.",
|
||||
"remoteControlTitle": "Conexão de área de trabalho remota",
|
||||
"remoteControlRequestMessage": "Deseja permitir que __user__ controle remotamente sua área de trabalho?",
|
||||
"remoteControlShareScreenWarning": "Note que se você pressionar \"Permitir\" você vai compartilhar sua tela!",
|
||||
@@ -387,11 +322,8 @@
|
||||
"remoteControlStopMessage": "A sessão de controle remoto terminou!",
|
||||
"close": "Fechar",
|
||||
"shareYourScreen": "Compartilhar sua tela",
|
||||
"shareYourScreenDisabled": "Compartilhamento de tela desativada.",
|
||||
"shareYourScreenDisabledForGuest": "Visitantes não podem compartilhar tela.",
|
||||
"yourEntireScreen": "Toda sua tela",
|
||||
"applicationWindow": "Janela de aplicativo",
|
||||
"transcribing": "Transcrevendo"
|
||||
"applicationWindow": "Janela de aplicativo"
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
@@ -421,22 +353,6 @@
|
||||
],
|
||||
"and": "e"
|
||||
},
|
||||
"share": {
|
||||
"mainText": [
|
||||
"Clique no seguinte link para entrar na reunião:",
|
||||
"__roomUrl__"
|
||||
],
|
||||
"dialInfoText": [
|
||||
"",
|
||||
"",
|
||||
"=====",
|
||||
"",
|
||||
"Quer apenas ligar no seu telefone?",
|
||||
"",
|
||||
"Clique neste link para ver a discagem nos números de telefone para esta reunião",
|
||||
"__dialInfoPageUrl__"
|
||||
]
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "Erro",
|
||||
"CONNECTING": "Conectando",
|
||||
@@ -450,37 +366,18 @@
|
||||
"ATTACHED": "Anexado"
|
||||
},
|
||||
"recording": {
|
||||
"beta": "BETA",
|
||||
"busy": "Estamos trabalhando para liberar recursos de gravação. Tente novamente em alguns minutos.",
|
||||
"busyTitle": "Todas as gravações estão atualmente ocupadas",
|
||||
"buttonTooltip": "Iniciar / parar gravação",
|
||||
"error": "A gravação falhou. Tente novamente.",
|
||||
"failedToStart": "Falha ao iniciar a gravação",
|
||||
"live": "AOVIVO",
|
||||
"off": "Gravação parada",
|
||||
"on": "Gravando",
|
||||
"pending": "Preparando para gravar a reunião...",
|
||||
"rec": "GRAVAR",
|
||||
"authDropboxText": "Enviar sua gravação para o Dropbox.",
|
||||
"authDropboxCompletedText": "Seu arquivo de gravação aparecerá no seu Dropbox logo após terminar a gravação.",
|
||||
"pending": "Aguardando um participante para iniciar a gravação...",
|
||||
"serviceName": "Serviço de gravação",
|
||||
"signOut": "Sair",
|
||||
"signIn": "entrar",
|
||||
"loggedIn": "Conectado como __userName__",
|
||||
"availableSpace": "Espaço disponível: __spaceLeft__ MB (aproximadamente __duration__ minutos de gravação)",
|
||||
"startRecordingBody": "Tem certeza que deseja iniciar a gravação?",
|
||||
"unavailable": "Oops! O __serviceName__ está indisponível. Estamos trabalhando para resolver o problema. Por favor, tente mais tarde.",
|
||||
"unavailableTitle": "Gravação indisponível"
|
||||
},
|
||||
"transcribing": {
|
||||
"pending": "Preparando a transcrição da reunião...",
|
||||
"off": "Transcrição parada",
|
||||
"error": "Transcrição falhou. Tente novamente.",
|
||||
"failedToStart": "Transcrição falhou ao iniciar",
|
||||
"tr": "TR",
|
||||
"labelToolTip": "A reunião esta sendo transcrita",
|
||||
"ccButtonTooltip": "Iniciar / Parar de mostrar as legendas"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "Estamos trabalhando para liberar os recursos de transmissão. Tente novamente em alguns minutos.",
|
||||
"busyTitle": "Todas as transmissões estão atualmente ocupadas",
|
||||
@@ -491,7 +388,6 @@
|
||||
"enterStreamKey": "Insira sua chave de transmissão ao vivo do YouTube aqui.",
|
||||
"error": "Falha na transmissão ao vivo. Tente de novo.",
|
||||
"errorAPI": "Ocorreu um erro ao acessar suas transmissões do YouTube. Por favor tente logar novamente.",
|
||||
"errorLiveStreamNotEnabled": "Transmissão ao vivo não está ativada em __email__. Ative a transmissão ao vivo ou registre numa conta com transmissão ao vivo ativada.",
|
||||
"failedToStart": "Falha ao iniciar a transmissão ao vivo",
|
||||
"off": "Transmissão ao vivo encerrada",
|
||||
"on": "Transmissão ao Vivo",
|
||||
@@ -528,10 +424,9 @@
|
||||
"noPermission": "Permissão não concedida",
|
||||
"previewUnavailable": "Visualização indisponível",
|
||||
"selectADevice": "Selecione um dispositivo",
|
||||
"testAudio": "Tocar um som de teste"
|
||||
"testAudio": "Testar o som"
|
||||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "AUD",
|
||||
"callQuality": "Qualidade da Chamada",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "Ver vídeo em alta definição",
|
||||
@@ -560,7 +455,6 @@
|
||||
"countryNotSupported": "Ainda não suportamos este destino.",
|
||||
"countryReminder": "Ligando fora dos EUA? Por favor, certifique-se de começar com o código do país!",
|
||||
"disabled": "Você não pode convidar pessoas.",
|
||||
"footerText": "Discagem está desativada.",
|
||||
"invite": "Convidar",
|
||||
"loading": "Procurando por pessoas e números de telefone",
|
||||
"loadingNumber": "Validando o número de telefone",
|
||||
@@ -597,7 +491,6 @@
|
||||
"veryGood": "Muito boa"
|
||||
},
|
||||
"info": {
|
||||
"accessibilityLabel": "Mostrar info",
|
||||
"addPassword": "Adicionar uma senha",
|
||||
"cancelPassword": "Cancelar senha",
|
||||
"conferenceURL": "Link:",
|
||||
@@ -619,7 +512,7 @@
|
||||
"numbers": "Números de discagem",
|
||||
"password": "Senha:",
|
||||
"title": "Compartilhar",
|
||||
"tooltip": "Compartilhar link e discagem para esta reunião"
|
||||
"tooltip": "Obtenha informações de acesso sobre a reunião"
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "OK",
|
||||
@@ -635,17 +528,17 @@
|
||||
"startWithVideoMuted": "Iniciar sem vídeo"
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Adicionar um link da reunião",
|
||||
"today": "Hoje",
|
||||
"later": "Depois",
|
||||
"next": "Recebendo",
|
||||
"nextMeeting": "próxima reunião",
|
||||
"noEvents": "Não há eventos próximos agendados.",
|
||||
"ongoingMeeting": "reunião em progresso",
|
||||
"now": "Agora",
|
||||
"permissionButton": "Abrir configurações",
|
||||
"permissionMessage": "Permissão do calendário é requerida para ver suas reuniões na aplicação.",
|
||||
"refresh": "Atualizar calendário"
|
||||
"permissionMessage": "Permissão do calendário é requerida para listar suas reuniões na aplicação."
|
||||
},
|
||||
"recentList": {
|
||||
"joinPastMeeting": "Entrar em uma reunião passada"
|
||||
"today": "Hoje",
|
||||
"yesterday": "Ontem",
|
||||
"earlier": "Mais cedo"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Puxe para atualizar"
|
||||
@@ -658,60 +551,5 @@
|
||||
"appNotInstalled": "Você precisa do aplicativo móvel __app__ para participar da reunião no seu telefone.",
|
||||
"downloadApp": "Baixe o Aplicativo",
|
||||
"openApp": "Continue na aplicação"
|
||||
},
|
||||
"presenceStatus": {
|
||||
"invited": "Convidar",
|
||||
"ringing": "Toque...",
|
||||
"calling": "Chamando...",
|
||||
"initializingCall": "Iniciando Chamada...",
|
||||
"connected": "Conectado",
|
||||
"connecting": "Conectando...",
|
||||
"connecting2": "Conectando*...",
|
||||
"disconnected": "Desconectado",
|
||||
"busy": "Ocupado",
|
||||
"rejected": "Rejeitado",
|
||||
"ignored": "Ignorado",
|
||||
"expired": "Expirado"
|
||||
},
|
||||
"dateUtils": {
|
||||
"today": "Hoje",
|
||||
"yesterday": "Ontem",
|
||||
"earlier": "Mais cedo"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "Responder",
|
||||
"audioCallTitle": "Chamada chegando",
|
||||
"decline": "Dispensar",
|
||||
"productLabel": "do Jitsi Meet",
|
||||
"videoCallTitle": "Chamada de vídeo chegando"
|
||||
},
|
||||
"localRecording": {
|
||||
"localRecording": "Gravação local",
|
||||
"dialogTitle": "Controles da Gravação Local",
|
||||
"start": "Iniciar gravação",
|
||||
"stop": "Parar a Gravação",
|
||||
"moderator": "Moderador",
|
||||
"me": "Eu",
|
||||
"duration": "Duração",
|
||||
"durationNA": "N/A",
|
||||
"encoding": "Codificando",
|
||||
"participantStats": "Estatísticas dos Participantes",
|
||||
"participant": "Participante",
|
||||
"sessionToken": "Token de Sessão",
|
||||
"clientState": {
|
||||
"on": "On",
|
||||
"off": "Off",
|
||||
"unknown": "Desconhecido"
|
||||
},
|
||||
"messages": {
|
||||
"engaged": "Gravação local iniciada.",
|
||||
"finished": "Sessão de gravação __token__ terminada. Por favor, envie o arquivo gravado para o moderador.",
|
||||
"finishedModerator": "Sessão de gravação __token__ terminada. A gravação da faixa local foi salva. Por favor, peça aos outros participantes para enviar suas gravações.",
|
||||
"notModerator": "Você não é o moderador. Você não pode iniciar ou parar a gravação local."
|
||||
},
|
||||
"yes": "Sim",
|
||||
"no": "Não",
|
||||
"label": "LOR",
|
||||
"labelToolTip": "Gravação local está envolvida"
|
||||
}
|
||||
}
|
||||
@@ -1,148 +1,183 @@
|
||||
{
|
||||
"contactlist_plural": "Участников: __count__",
|
||||
"contactlist": "Участники (__pcount__)",
|
||||
"addParticipants": "Поделиться ссылкой",
|
||||
"roomLocked": "Вызывающие должны ввести пароль",
|
||||
"roomUnlocked": "Любой, владеющий ссылкой, может присоединиться",
|
||||
"passwordSetRemotely": "установлен другим участником",
|
||||
"connectionsettings": "Настройки подключения",
|
||||
"poweredby": "работает на",
|
||||
"inviteUrlDefaultMsg": "Подготавливаем вашу встречу...",
|
||||
"me": "я",
|
||||
"speaker": "Колонка",
|
||||
"raisedHand": "Хочет говорить",
|
||||
"defaultNickname": "напр. Яна Цветкова",
|
||||
"defaultLink": "напр. __url__",
|
||||
"audioDevices": {
|
||||
"bluetooth": "Bluetooth",
|
||||
"headphones": "Наушники",
|
||||
"phone": "Телефон",
|
||||
"speaker": "Колонка"
|
||||
"feedback": {
|
||||
"average": "",
|
||||
"bad": "",
|
||||
"good": "",
|
||||
"rateExperience": "Пожалуйста, оцените ваш опыт встречи.",
|
||||
"veryBad": "",
|
||||
"veryGood": ""
|
||||
},
|
||||
"inviteUrlDefaultMsg": "Ваша конференция создается в данный момент...",
|
||||
"me": "Я",
|
||||
"speaker": "Говорящий",
|
||||
"raisedHand": "Хочет говорить",
|
||||
"defaultNickname": "напр. Яна Цветочкина",
|
||||
"defaultLink": "напр. __url__",
|
||||
"callingName": "__name__",
|
||||
"audioOnly": {
|
||||
"audioOnly": "Только звук",
|
||||
"featureToggleDisabled": "Переключение функции __feature__ недоступно в режиме \"только звук\""
|
||||
"audioOnly": "",
|
||||
"featureToggleDisabled": ""
|
||||
},
|
||||
"userMedia": {
|
||||
"react-nativeGrantPermissions": "Выберите <b><i>Разрешить</i></b>, когда браузер спросит о разрешениях.",
|
||||
"chromeGrantPermissions": "Выберите <b><i>Разрешить</i></b>, когда браузер спросит о разрешениях.",
|
||||
"androidGrantPermissions": "Выберите <b><i>Разрешить</i></b>, когда браузер спросит о разрешениях.",
|
||||
"electronGrantPermissions": "Пожалуйста, дайте разрешение на доступ к камере и микрофону",
|
||||
"firefoxGrantPermissions": "Выберите <b><i>Поделиться выбранным устройством</i></b>, когда браузер спросит о разрешениях.",
|
||||
"operaGrantPermissions": "Выберите <b><i>Разрешить</i></b>, когда браузер спросит о разрешениях.",
|
||||
"iexplorerGrantPermissions": "Выберите <b><i>OK</i></b>, когда браузер спросит о разрешениях.",
|
||||
"safariGrantPermissions": "Выберите <b><i>OK</i></b>, когда браузер спросит о разрешениях.",
|
||||
"nwjsGrantPermissions": "Пожалуйста, дайте разрешение на доступ к камере и микрофону",
|
||||
"edgeGrantPermissions": "Выберите <b><i>Да</i></b>, когда браузер спросит о разрешениях."
|
||||
"react-nativeGrantPermissions": "",
|
||||
"chromeGrantPermissions": "",
|
||||
"androidGrantPermissions": "",
|
||||
"firefoxGrantPermissions": "",
|
||||
"operaGrantPermissions": "",
|
||||
"iexplorerGrantPermissions": "",
|
||||
"safariGrantPermissions": "",
|
||||
"nwjsGrantPermissions": "Пожалуйста дайте разрешение на доступ к камере и микрофону",
|
||||
"edgeGrantPermissions": ""
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"keyboardShortcuts": "Комбинации клавиш",
|
||||
"raiseHand": "Поднять или опустить руку",
|
||||
"pushToTalk": "Нажмите, чтобы говорить",
|
||||
"toggleScreensharing": "Переключиться между камерой и показом экрана",
|
||||
"toggleScreensharing": "Переключиться между камерой и совместным использованием экрана",
|
||||
"toggleFilmstrip": "Показать или скрыть видео",
|
||||
"toggleShortcuts": "Показать или скрыть это справочное меню",
|
||||
"focusLocal": "Фокус на ваше видео",
|
||||
"focusRemote": "Фокус на видео другого участника",
|
||||
"toggleChat": "Чат (открыть/закрыть)",
|
||||
"mute": "Микрофон (вкл./выкл.)",
|
||||
"fullScreen": "Полноэкранный режим (вкл./выкл.)",
|
||||
"videoMute": "Камера (вкл./выкл.)",
|
||||
"showSpeakerStats": "Показать статистику выступающего"
|
||||
"focusRemote": "Фокус на видео другого абонента",
|
||||
"toggleChat": "Открыть или закрыть чат",
|
||||
"mute": "Заглушить или включить микрофон",
|
||||
"fullScreen": "Войти или выйти из полноэкранного режима",
|
||||
"videoMute": "Включить или выключить вашу камеру",
|
||||
"showSpeakerStats": ""
|
||||
},
|
||||
"welcomepage": {
|
||||
"appDescription": "Попробуйте видеочат со всей командой. Приглашайте знакомых! __app__ — полностью зашифрованное решение для видеоконференций с открытым исходным кодом. Пользуйтесь каждый день, бесплатно и без регистрации.",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "Календарь",
|
||||
"video": "Видео"
|
||||
"disable": "Не показывать эту страницу снова",
|
||||
"feature1": {
|
||||
"content": "Нет нужды что-либо скачивать. __app__ работает прямо из вашего браузера. Просто отправьте URL ссылку на вашу конференцию другим, чтобы начать общение.",
|
||||
"title": "Простой в использовании"
|
||||
},
|
||||
"calendar": "Календарь",
|
||||
"go": "ОК",
|
||||
"join": "ПРИСОЕДИНИТЬСЯ",
|
||||
"privacy": "Приватность",
|
||||
"roomname": "Укажите название комнаты",
|
||||
"roomnameHint": "Укажите название комнаты или ее адрес. Можете сами создать название и передать его будущим участникам встречи, чтобы они использовали именно его.",
|
||||
"sendFeedback": "Обратная связь",
|
||||
"terms": "Условия",
|
||||
"title": "Видеоконференции. Безопасно. Гибко. Полностью бесплатно"
|
||||
"feature2": {
|
||||
"content": "Многопользовательским видеоконференциям достаточно скорости передачи данных в 128 Кбит/с. Демонстрация экрана или аудиоконференции требуют и того меньше.",
|
||||
"title": "Низкие требования к ширине канала"
|
||||
},
|
||||
"feature3": {
|
||||
"content": "__app__ лицензирован под Apache License. Вы можете свободно скачивать, использовать, изменять это ПО в соответствии с условиями лицензии.",
|
||||
"title": "Исходный код открыт"
|
||||
},
|
||||
"feature4": {
|
||||
"content": "Нет никаких искусственных ограничений по количеству пользовательниц или участников конференций. Вас отграничивают только мощность сервера и качество соединения.",
|
||||
"title": "Количество пользовательниц не ограничено"
|
||||
},
|
||||
"feature5": {
|
||||
"content": "С лёгкостью можно пользоваться экраном совместно. __app__ идеально для онлайн презентаций, лекций и сеансов техподдержки.",
|
||||
"title": "Общий доступ к экрану"
|
||||
},
|
||||
"feature6": {
|
||||
"content": "Нужно больше приватности? __app__ конференц-комнаты могут быть защищены паролем, чтобы исключить незваных гостей или заминки.",
|
||||
"title": "Защищённые комнаты"
|
||||
},
|
||||
"feature7": {
|
||||
"content": "__app__ включает Etherpad, текстовый редактор для совместной работы над текстом в реальном времени, который замечательно подходит, чтобы вести протоколы или совместно писать статьи.",
|
||||
"title": "Поделиться заметками"
|
||||
},
|
||||
"feature8": {
|
||||
"content": "Узнайте больше о пользователях с помощью интеграции с Piwik, Google Analytics и другими системами мониторига и сбора статистики.",
|
||||
"title": "Статистика использования"
|
||||
},
|
||||
"go": "Вперед!",
|
||||
"join": "",
|
||||
"privacy": "",
|
||||
"roomname": "Введите название комнаты",
|
||||
"roomnamePlaceHolder": "",
|
||||
"sendFeedback": "",
|
||||
"terms": ""
|
||||
},
|
||||
"\u0005welcomepage": {},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
"title": "__app__ требуется доступ к микрофону и камере."
|
||||
"policyText": "",
|
||||
"title": "__app__ нуждается в использовании вашего микрофона и камеры."
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"title": "Видеосвязь прервана. Причина: этот компьютер перешел в режим сна.",
|
||||
"text": "Для восстановления связи нажмите кнопку <i>Подключиться снова</i>.",
|
||||
"rejoinKeyTitle": "Подключиться снова"
|
||||
"title": "",
|
||||
"text": "",
|
||||
"rejoinKeyTitle": "Присоединиться повторно"
|
||||
},
|
||||
"toolbar": {
|
||||
"addPeople": "Добавить людей к вашему сеансу связи",
|
||||
"audioonly": "Режим \"только звук\" (вкл./выкл.), экономит трафик",
|
||||
"callQuality": "Качество связи",
|
||||
"enterFullScreen": "Полный экран",
|
||||
"exitFullScreen": "Полный экран",
|
||||
"feedback": "Оставить отзыв",
|
||||
"moreActions": "Больше",
|
||||
"mute": "Звук (вкл./выкл.)",
|
||||
"videomute": "Камера",
|
||||
"addPeople": "",
|
||||
"audioonly": "",
|
||||
"mute": "Вкл. / Выкл. звук",
|
||||
"videomute": "Вкл / Выкл камеру",
|
||||
"authenticate": "Аутентифицировать",
|
||||
"lock": "Блокировка комнаты",
|
||||
"chat": "Чат",
|
||||
"etherpad": "Общий документ",
|
||||
"documentOpen": "Открыть общий документ",
|
||||
"documentClose": "Закрыть общий документ",
|
||||
"sharedvideo": "Видео YouTube",
|
||||
"sharescreen": "Показ экрана",
|
||||
"stopSharedVideo": "Остановить видео на YouTube",
|
||||
"fullscreen": "Полноэкранный режим (вкл./выкл.)",
|
||||
"sip": "Набрать номер SIP",
|
||||
"lock": "Заблокировать / разблокировать комнату",
|
||||
"invite": "Поделиться ссылкой",
|
||||
"chat": "Открыть / Закрыть чат",
|
||||
"etherpad": "Открыть / Закрыть общий документ",
|
||||
"sharedvideo": "Поделиться YouTube видео",
|
||||
"sharescreen": "Начать / Завершить совместное использование экрана",
|
||||
"fullscreen": "Вкл / Выкл полноэкранный режим",
|
||||
"sip": "Набрать SIP номер",
|
||||
"Settings": "Настройки",
|
||||
"hangup": "Выход",
|
||||
"hangup": "Покинуть",
|
||||
"login": "Войти",
|
||||
"logout": "Завершить сеанс",
|
||||
"sharedVideoMutedPopup": "Звук видео, которым вы делитесь, отключен, чтобы вы могли общаться с другими участниками.",
|
||||
"micMutedPopup": "Ваш микрофон отключен, чтобы вы могли слышать звук вашего видео.",
|
||||
"talkWhileMutedPopup": "Пытаетесь говорить? У вас отключен звук.",
|
||||
"dialpad": "Открыть / Закрыть клавиатуру для набора номера",
|
||||
"sharedVideoMutedPopup": "У видео, которым Вы поделились, отключён звук, чтобы вы могли говорить с остальными.",
|
||||
"micMutedPopup": "Ваш микрофон отключён, чтобы вы могли сосредоточиться на видео, которым поделились.",
|
||||
"talkWhileMutedPopup": "Пытаетесь говорить? Вы приглушены.",
|
||||
"unableToUnmutePopup": "Вы не можете включить звук, потому что включено видео.",
|
||||
"cameraDisabled": "Камера недоступна",
|
||||
"micDisabled": "Микрофон недоступен",
|
||||
"filmstrip": "Видео (показать/скрыть)",
|
||||
"profile": "Редактировать профиль",
|
||||
"raiseHand": "Хочу говорить",
|
||||
"shortcuts": "Комбинации клавиш",
|
||||
"speakerStats": "Статистика"
|
||||
"filmstrip": "Показать / Скрыть видео",
|
||||
"profile": "Редактировать ваш профиль",
|
||||
"raiseHand": "Поднять / Опустить вашу руку"
|
||||
},
|
||||
"unsupportedBrowser": {
|
||||
"appInstalled": "",
|
||||
"appNotInstalled": "",
|
||||
"downloadApp": "",
|
||||
"joinConversation": "",
|
||||
"startConference": ""
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Открыть / Закрыть чат",
|
||||
"filmstrip": "Показать / Скрыть видео",
|
||||
"contactlist": "Просмотреть и пригласить участников"
|
||||
},
|
||||
"\u0005toolbar": {},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "Введите имя в поле ниже",
|
||||
"popover": "Выберите имя"
|
||||
},
|
||||
"messagebox": "Пишите..."
|
||||
"messagebox": "Введите текст.."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
"update": "Обновить",
|
||||
"name": "Имя",
|
||||
"startAudioMuted": "Все начинают с выключенным звуком",
|
||||
"startVideoMuted": "Все начинают в скрытом режиме",
|
||||
"startAudioMuted": "Каждый начинает глушиться",
|
||||
"startVideoMuted": "Все начинают скрываться",
|
||||
"selectCamera": "Камера",
|
||||
"selectMic": "Микрофон",
|
||||
"selectAudioOutput": "Звуковой выход",
|
||||
"followMe": "Все следуют за мной",
|
||||
"noDevice": "Все как я",
|
||||
"followMe": "Каждый следует за мной",
|
||||
"noDevice": "Нет",
|
||||
"cameraAndMic": "Камера и микрофон",
|
||||
"moderator": "МОДЕРАТОР",
|
||||
"password": "УСТАНОВИТЬ ПАРОЛЬ",
|
||||
"audioVideo": "ЗВУК И ВИДЕО"
|
||||
"audioVideo": "АУДИО И ВИДЕО"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Профиль",
|
||||
"setDisplayNameLabel": "Отображаемое имя",
|
||||
"setEmailLabel": "E-mail для gravatar",
|
||||
"setEmailInput": "Введите e-mail"
|
||||
"setDisplayNameLabel": "Установить ваше отображаемое имя",
|
||||
"setEmailLabel": "Установить электронную почту для gravatar",
|
||||
"setEmailInput": "Введите электронную почту"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"moderator": "Модератор",
|
||||
"videomute": "Участник отключил камеру",
|
||||
"mute": "Участник отключил звук",
|
||||
"kick": "Выкинуть",
|
||||
"editnickname": "Нажми, чтобы<br/>поменять имя экрана",
|
||||
"moderator": "Хозяйка конференции.",
|
||||
"videomute": "Участник<br/>остановил камеру",
|
||||
"mute": "Без звука",
|
||||
"kick": "Прогнать",
|
||||
"muted": "Звук выключен",
|
||||
"domute": "Выключить звук",
|
||||
"flip": "Отразить",
|
||||
@@ -150,221 +185,208 @@
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "Данные соединения",
|
||||
"bitrate": "Битрейт:",
|
||||
"packetloss": "Потери пакетов:",
|
||||
"bitrate": "Битрейт",
|
||||
"packetloss": "Потеря пакетов:",
|
||||
"resolution": "Разрешение:",
|
||||
"framerate": "Частота кадров:",
|
||||
"less": "Меньше",
|
||||
"more": "Больше",
|
||||
"framerate": "",
|
||||
"less": "Свернуть",
|
||||
"more": "Показать больше",
|
||||
"address": "Адрес:",
|
||||
"remoteport": "Удаленные порты:",
|
||||
"remoteport_plural_2": "Удаленные порты:",
|
||||
"remoteport_plural_5": "Удаленные порты:",
|
||||
"localport": "Локальные порты:",
|
||||
"remoteport": "Удалённый порт:",
|
||||
"remoteport_plural_2": "",
|
||||
"remoteport_plural_5": "",
|
||||
"localport": "Локальный порт:",
|
||||
"localport_plural_2": "Локальные порты:",
|
||||
"localport_plural_5": "Локальные порты:",
|
||||
"localaddress": "Локальные адреса:",
|
||||
"localport_plural_5": "",
|
||||
"localaddress": "Локальный адрес:",
|
||||
"localaddress_plural_2": "Локальные адреса:",
|
||||
"localaddress_plural_5": "Локальные адреса:",
|
||||
"remoteaddress": "Удаленные адреса:",
|
||||
"remoteaddress_plural_2": "Удаленные адреса:",
|
||||
"remoteaddress_plural_5": "Удаленные адреса:",
|
||||
"transport": "Методы отправки:",
|
||||
"transport_plural_2": "Методы отправки:",
|
||||
"transport_plural_5": "Методы отправки:",
|
||||
"bandwidth": "Средняя скорость:",
|
||||
"na": "Во время беседы вы можете следить за данными о соединении",
|
||||
"turn": " (повернуть)",
|
||||
"quality": {
|
||||
"good": "Хорошо",
|
||||
"inactive": "не активно",
|
||||
"lost": "потеряно",
|
||||
"nonoptimal": "не оптимально",
|
||||
"poor": "плохо"
|
||||
},
|
||||
"status": "Связь:"
|
||||
"localaddress_plural_5": "",
|
||||
"remoteaddress": "Удалённый адрес:",
|
||||
"remoteaddress_plural_2": "",
|
||||
"remoteaddress_plural_5": "",
|
||||
"transport": "Метод отправки:",
|
||||
"bandwidth": "Средняя скорость соединения:",
|
||||
"na": "Вернитесь сюда за информацией о соединении, когда конференция начнётся",
|
||||
"turn": ""
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "соединение разорвано",
|
||||
"moderator": "Получены права модератора!",
|
||||
"connectedOneMember": "__name__ подключен",
|
||||
"connectedTwoMembers": "__first__ и __second__ подключены",
|
||||
"connectedThreePlusMembers": "__name__ и другие (__count__) подключены",
|
||||
"moderator": "Получены права для модерации!",
|
||||
"connected": "подключено",
|
||||
"somebody": "Кто-то",
|
||||
"me": "Я",
|
||||
"focus": "Фокус встречи",
|
||||
"focusFail": "__component__ недоступен, повторите через __ms__ с",
|
||||
"grantedTo": "__to__ получил права модератора!",
|
||||
"grantedToUnknown": "К сожалению, что-то пошло не так.",
|
||||
"muted": "Вы начали разговор без звука.",
|
||||
"focus": "Фокусировка конференции",
|
||||
"focusFail": "__component__ недоступен - повторите через __ms__ секунд",
|
||||
"grantedTo": "Теперь модерирует __to__!",
|
||||
"grantedToUnknown": "",
|
||||
"muted": "Вы начали конференцию без звука.",
|
||||
"mutedTitle": "Вы без звука!",
|
||||
"raisedHand": "Хочет говорить.",
|
||||
"suboptimalExperienceTitle": "К сожалению, этот браузер может не подойти для работы с __appName__. Мы работаем над проблемой, а пока попробуйте один из <a href='static/recommendedBrowsers.html 'target='_blank'>полностью поддерживаемых браузеров</a>.",
|
||||
"suboptimalExperienceDescription": "К сожалению, этот браузер не очень подходит для работы с __appName__. Мы работаем над проблемой, а пока попробуйте один из <a href='static/recommendedBrowsers.html 'target='_blank'>полностью поддерживаемых браузеров</a>."
|
||||
"raisedHand": "Хочу высказаться."
|
||||
},
|
||||
"dialog": {
|
||||
"allow": "Разрешить",
|
||||
"kickMessage": "Вас выкинули из комнаты!",
|
||||
"popupErrorTitle": "Заблокировано всплывающее окно",
|
||||
"popupError": "Ваш браузер блокирует всплывающие окна этого сайта. Пожалуйста, разрешите всплывающие окна в настройках безопасности браузера и попробуйте снова.",
|
||||
"add": "Добавить",
|
||||
"allow": "",
|
||||
"kickMessage": "Фигасе! Вас прогнали со встречи!",
|
||||
"popupError": "Ваш браузер блокирует всплывающие окна на этом сайте. Пожалуйста разрешите всплывающие окна в настройках безопасности и попробуйте снова.",
|
||||
"passwordErrorTitle": "Ошибка пароля",
|
||||
"passwordError": "Эта встреча защищена паролем. Только организатор встречи может устанавливать пароль.",
|
||||
"passwordError2": "Эта встреча не защищена паролем. Только организатор встречи может устанавливать пароль.",
|
||||
"connectError": "Ошибка. Невозможно установить связь для вашей встречи.",
|
||||
"connectErrorWithMsg": "Ошибка. Невозможно установить связь для вашей встречи: __msg__",
|
||||
"incorrectPassword": "Ошибка имени пользователя или пароля",
|
||||
"connecting": "Подключение",
|
||||
"passwordError": "Этот разговор сейчас защищён паролем. Только хозяйка конференции может устанавливать пароль.",
|
||||
"passwordError2": "Эта конференция защищена паролем. Только хозяйка конференции может устанавливать пароль.",
|
||||
"connectError": "Ёпрст! Что-то пошло не так и мы не можем связаться с конференцией.",
|
||||
"connectErrorWithMsg": "Ёпрст! Что-то пошло не так и мы не можем связаться с конференцией: __msg__",
|
||||
"incorrectPassword": "Неверный пароль",
|
||||
"connecting": "Идёт подключение",
|
||||
"copy": "Копировать",
|
||||
"contactSupport": "Связь с поддержкой",
|
||||
"error": "Ошибка",
|
||||
"detectext": "Ошибка связи с расширением для показа экрана.",
|
||||
"failedpermissions": "Ошибка доступа к локальному микрофону и/или камере.",
|
||||
"conferenceReloadTitle": "К сожалению, что-то пошло не так.",
|
||||
"conferenceReloadMsg": "Мы стараемся это исправить. Восстановление связи через __seconds__ с.",
|
||||
"conferenceDisconnectTitle": "Вы отключены.",
|
||||
"conferenceDisconnectMsg": "Следует проверить интернет-соединение. Попытка восстановления связи через __seconds__ с.",
|
||||
"dismiss": "Отклонить",
|
||||
"rejoinNow": "Подключиться снова",
|
||||
"maxUsersLimitReachedTitle": "Достигнут лимит числа участников",
|
||||
"maxUsersLimitReached": "Достигнуто максимальное число участников. Больше нельзя. Пожалуйста, свяжитесь с организатором встречи или попробуйте позже!",
|
||||
"createPassword": "Создать пароль",
|
||||
"detectext": "Ошибка при попытке определить расширение для совместного использования экрана.",
|
||||
"failtoinstall": "Невозможно установить расширение для совместного использования рабочего стола",
|
||||
"failedpermissions": "Невозможно получить права на использование локального микрофона и/или камеры.",
|
||||
"conferenceReloadTitle": "",
|
||||
"conferenceReloadMsg": "",
|
||||
"conferenceDisconnectTitle": "",
|
||||
"conferenceDisconnectMsg": "",
|
||||
"rejoinNow": "",
|
||||
"maxUsersLimitReached": "Достигнут максимум количества участников конференции. Конференция заполнена. Пожалуйста попробуйте позже!",
|
||||
"lockTitle": "Блокировка не удалась",
|
||||
"lockMessage": "Не удалось запереть конференцию",
|
||||
"warning": "Внимание",
|
||||
"passwordNotSupportedTitle": "Пароль не поддержвается",
|
||||
"passwordNotSupported": "Установка пароля не поддерживается.",
|
||||
"passwordNotSupported": "Пароли для комнат сейчас не поддерживаются.",
|
||||
"internalErrorTitle": "Внутренняя ошибка",
|
||||
"internalError": "Что-то пошло не так. Ошибка: __error__",
|
||||
"unableToSwitch": "Невозможно сменить трансляцию видео.",
|
||||
"SLDFailure": "Что-то пошло не так, невозможно выключить звук! (ошибка SLD)",
|
||||
"SRDFailure": "Что-то пошло не так, невозможно выключить видео! (ошибка SRD)",
|
||||
"oops": "Ой!",
|
||||
"currentPassword": "Текущий пароль",
|
||||
"internalError": "Ой! Что-то пошло не так. Возникла следующая ошибка: [setRemoteDescription]",
|
||||
"unableToSwitch": "Невозможно сменить видео трансляцию.",
|
||||
"SLDFailure": "Ёпрст! Что-то пошло не так и мы не можем отключить звук! (ошибка SLD)",
|
||||
"SRDFailure": "Ёпрст! Что-то пошло не так и мы не можем остановить видео! (ошибка SRD)",
|
||||
"oops": "Ёпрст!",
|
||||
"currentPassword": "Текущим паролем является",
|
||||
"passwordLabel": "Пароль",
|
||||
"defaultError": "Произошла ошибка",
|
||||
"defaultError": "Какая-то ошибка",
|
||||
"passwordRequired": "Требуется пароль",
|
||||
"Ok": "Ok",
|
||||
"done": "Готово",
|
||||
"Remove": "Удалить",
|
||||
"removePassword": "Удалить пароль",
|
||||
"shareVideoTitle": "Поделиться видео",
|
||||
"shareVideoLinkError": "Пожалуйста, укажите корректную ссылку Youtube.",
|
||||
"removeSharedVideoTitle": "Убрать видео",
|
||||
"removeSharedVideoMsg": "Уверены, что хотите убрать видео, которым поделились?",
|
||||
"alreadySharedVideoMsg": "Другой участник уже показывает свое видео. Нельзя показывать два видео одновременно.",
|
||||
"alreadySharedVideoTitle": "Допускается показ только одного видео",
|
||||
"WaitingForHost": "Ждем организатора...",
|
||||
"WaitForHostMsg": "Встреча <b>__room__ </b> еще не началась. Если вы организатор встречи, пожалуйста, представьтесь. Если нет, подождите организатора.",
|
||||
"IamHost": "Я организатор",
|
||||
"Cancel": "Отмена",
|
||||
"Submit": "ОК",
|
||||
"shareVideoLinkError": "Пожалуйста введите корректную youtube ссылку.",
|
||||
"removeSharedVideoTitle": "Удалить общее видео",
|
||||
"removeSharedVideoMsg": "Вы уверрены, что хотите удалить ваше расшаренное видео?",
|
||||
"alreadySharedVideoMsg": "Другая участница сейчас делится видео. В этой конференции можно делиться только одним видео одновременно.",
|
||||
"WaitingForHost": "Ожидание хоста...",
|
||||
"WaitForHostMsg": "Конференция <b>__room__ </b> ещё не началась. Если вы её хост - аутентифицируйтесь. Или сидите ждите хоста.",
|
||||
"IamHost": "Я хост",
|
||||
"Cancel": "Отменить",
|
||||
"Submit": "Принять",
|
||||
"retry": "Повторить",
|
||||
"logoutTitle": "Завершить сеанс",
|
||||
"logoutQuestion": "Уверены, что хотите выйти и остановить встречу?",
|
||||
"sessTerminated": "Связь прервана",
|
||||
"logoutQuestion": "Вы уверены, что хотите выйти и остановить конференцию?",
|
||||
"sessTerminated": "Сеанс закрыт",
|
||||
"hungUp": "Вы повесили трубку",
|
||||
"joinAgain": "Войти снова",
|
||||
"joinAgain": "Войдите заново",
|
||||
"Share": "Поделиться",
|
||||
"Save": "Сохранить",
|
||||
"recording": "Запись",
|
||||
"recordingToken": "Введите токен для записи",
|
||||
"passwordCheck": "Вы уверены, что хотите удалить ваш пароль?",
|
||||
"passwordMsg": "Введите пароль для вашей комнаты",
|
||||
"shareLink": "Поделитесь ссылкой на звонок",
|
||||
"settings1": "Настройка Вашей конференции",
|
||||
"settings2": "Участница подключилась без звука",
|
||||
"settings3": "Нужны имена<br/><br/>Установите пароль, чтобы запереть Вашу комнату:",
|
||||
"yourPassword": "Введите новый пароль",
|
||||
"Back": "Назад",
|
||||
"serviceUnavailable": "Служба недоступна",
|
||||
"gracefulShutdown": "Технические работы. Пожалуйста, попробуйте позже.",
|
||||
"gracefulShutdown": "Сервис закрыт на переучёт. Пожалуйста попробуйте позже.",
|
||||
"Yes": "Да",
|
||||
"reservationError": "Ошибка системы резервирования",
|
||||
"reservationError": "Ошибка системы резервации",
|
||||
"reservationErrorMsg": "Код ошибки: __code__, сообщение: __msg__",
|
||||
"password": "Введите пароль",
|
||||
"userPassword": "пароль пользователя",
|
||||
"token": "токен",
|
||||
"tokenAuthFailedTitle": "Ошибка аутентификации",
|
||||
"tokenAuthFailed": "Извините, вам не разрешено присоединиться к этому сеансу связи.",
|
||||
"displayNameRequired": "Требуется имя",
|
||||
"enterDisplayName": "Пожалуйста, введите имя",
|
||||
"feedbackHelp": "Ваш отзыв поможет нам улучшать параметры видеосвязи.",
|
||||
"feedbackQuestion": "Расскажите, как вам понравилась связь!",
|
||||
"thankYou": "Спасибо, что используете __appName__!",
|
||||
"sorryFeedback": "Жаль. Может, расскажете подробнее?",
|
||||
"tokenAuthFailed": "Извините, вам не разрешено присоединиться к этому звонку.",
|
||||
"displayNameRequired": "Требуется отображаемое имя",
|
||||
"enterDisplayName": "Пожалуйста, введите Ваше имя экрана",
|
||||
"extensionRequired": "Требуется расширение:",
|
||||
"firefoxExtensionPrompt": "Нужно установить расширение Firefox, чтобы совместно пользоваться экраном. Попробуйте позже, скачав его <a href='__url__'>отсюда</a>!",
|
||||
"feedbackHelp": "Ваша поддержка поможет нам улучшить опыт видео.",
|
||||
"feedbackQuestion": "Расскажите нам о вашем звонке!",
|
||||
"thankYou": "Спасибо за использование __appName__!",
|
||||
"sorryFeedback": "Мы удручены услышанным. Может расскажете поподробнее?",
|
||||
"liveStreaming": "Трансляция",
|
||||
"streamKey": "Ключ трансляции",
|
||||
"streamKey": "Имя/ключ трансляции",
|
||||
"startLiveStreaming": "Начать трансляцию",
|
||||
"startRecording": "Начать запись",
|
||||
"stopStreamingWarning": "Уверены, что хотите остановить трансляцию?",
|
||||
"stopRecordingWarning": "Уверены, что хотите остановить запись?",
|
||||
"stopStreamingWarning": "Вы уверены, что хотите остановить трансляцию?",
|
||||
"stopRecordingWarning": "Вы уверены, что хотите остановить запись?",
|
||||
"stopLiveStreaming": "Остановить трансляцию",
|
||||
"stopRecording": "Остановить запись",
|
||||
"doNotShowMessageAgain": "Больше не показывать это сообщение",
|
||||
"permissionDenied": "Доступ запрещен",
|
||||
"screenSharingFailedToInstall": "Ошибка установки расширения для показа экрана.",
|
||||
"screenSharingFailedToInstallTitle": "Расширение для показа экрана не установлено",
|
||||
"screenSharingFirefoxPermissionDeniedError": "Что-то пошло не так, когда мы пытались поделиться вашим экраном. Пожалуйста, убедитесь, что вы дали нам разрешение на это.",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "Ошибка показа экрана!",
|
||||
"screenSharingPermissionDeniedError": "Ошибка доступа к вашему расширению для показа экрана. Пожалуйста, перезапустите браузер и попробуйте снова.",
|
||||
"cameraUnsupportedResolutionError": "Ваша камера не поддерживает необходимое разрешение видео.",
|
||||
"cameraUnknownError": "Неизвестная ошибка использования камеры.",
|
||||
"cameraPermissionDeniedError": "Нет доступа к камере. Вы можете участвовать во встрече, но другие не будут вас видеть. Используйте значок камеры в адресной строке браузера, чтобы устранить проблему.",
|
||||
"cameraNotFoundError": "Камера не обнаружена.",
|
||||
"cameraConstraintFailedError": "Камера не отвечает определенным требованиям.",
|
||||
"micUnknownError": "Неизвестная ошибка использования микрофона.",
|
||||
"micPermissionDeniedError": "Нет доступа к микрофону. Вы можете участвовать во встрече, но другие не будут вас слышать. Используйте значок камеры в адресной строке браузера, чтобы устранить проблему.",
|
||||
"micNotFoundError": "Микрофон не обнаружен.",
|
||||
"micConstraintFailedError": "Ваш микрофон не отвечает определенным требованиям.",
|
||||
"micNotSendingDataTitle": "Нет доступа к микрофону",
|
||||
"micNotSendingData": "Ошибка доступа к микрофону. Пожалуйста, выберите другое устройство из меню настроек или попробуйте перезапустить приложение.",
|
||||
"cameraNotSendingDataTitle": "Нет доступа к камере",
|
||||
"cameraNotSendingData": "Ошибка доступа к камере. Пожалуйста, проверьте, не использует ли камеру какая-нибудь другая программа. Вы можете также выбрать другое устройство из меню настроек или попробовать перезапустить приложение.",
|
||||
"doNotShowWarningAgain": "Больше не показывать это предупреждение",
|
||||
"doNotShowMessageAgain": "Не показывать больше это сообщение",
|
||||
"permissionDenied": "Доступ запрещён",
|
||||
"screenSharingPermissionDeniedError": "У Вас нет прав совместно использовать Ваш экран",
|
||||
"micErrorPresent": "Произошла ошибка при подключении к Вашему микрофону",
|
||||
"cameraErrorPresent": "Произошла ошибка при подключении к Вашей камере",
|
||||
"cameraUnsupportedResolutionError": "Ваша камера не поддерживает необходимое разрешение.",
|
||||
"cameraUnknownError": "Не могу использовать камеру по неизвестной причине.",
|
||||
"cameraPermissionDeniedError": "У вас нет прав на использование камеры. Вы можете участвовать в конференции, но другие не будут Вас видеть. Используйте значок с камерой в строке адреса, чтобы устранить проблему.",
|
||||
"cameraNotFoundError": "Камера не была найдена.",
|
||||
"cameraConstraintFailedError": "",
|
||||
"micUnknownError": "Не могу пользоваться микрофоном по непонятным причинам.",
|
||||
"micPermissionDeniedError": "Вы не дали прав на использование микрофона. Вы все-равно можете присоединиться к конференции, но никто не будет Вас слышать. Используйте иконку с камерой в адресной строке браузера, чтобы исправить это.",
|
||||
"micNotFoundError": "Микрофон не был найден.",
|
||||
"micConstraintFailedError": "",
|
||||
"micNotSendingData": "Мы не можем получить доступ к вашему микрофону. Пожалуйста, выберите другое устройство из меню настроек или попробуйте перезапустить приложение.",
|
||||
"cameraNotSendingData": "Мы не можем получить доступ к вашей камере. Пожалуйста, проверьте, используется ли это устройство другим приложением, выберите другое устройство из меню настроек или же перезапустите приложение.",
|
||||
"goToStore": "Перейти к интернет-магазину",
|
||||
"externalInstallationTitle": "Требуется расширение",
|
||||
"externalInstallationMsg": "Вам необходимо установить наше дополнение для совместного использования рабочего стола.",
|
||||
"inlineInstallationMsg": "Вам необходимо установить наше дополнение для совместного использования рабочего стола.",
|
||||
"inlineInstallExtension": "Установить",
|
||||
"muteParticipantTitle": "Выключить звук этому участнику?",
|
||||
"muteParticipantBody": "Вы не можете включить им звук, но они могут сделать это сами в любое время.",
|
||||
"inlineInstallExtension": "",
|
||||
"muteParticipantTitle": "Приглушить этого участника?",
|
||||
"muteParticipantBody": "Вы не сможете перестать глушить их, но они могут сделать это сами в любое время.",
|
||||
"muteParticipantButton": "Выключить звук",
|
||||
"remoteControlTitle": "Удаленное управление рабочим столом",
|
||||
"remoteControlRequestMessage": "Разрешить __user__ удаленное управление вашим рабочим столом?",
|
||||
"remoteControlShareScreenWarning": "Если нажмете \"Разрешить\", то поделитесь своим экраном!",
|
||||
"remoteControlDeniedMessage": "__user__ отклонил ваш запрос на удаленное управление!",
|
||||
"remoteControlAllowedMessage": "__user__ принял ваш запрос на удаленное управление!",
|
||||
"remoteControlErrorMessage": "Произошла ошибка при попытке запросить разрешения удаленного управления от __user__.",
|
||||
"startRemoteControlErrorMessage": "Ошибка начала сессии удаленного управления!",
|
||||
"remoteControlStopMessage": "Сессия удаленного управления завершена!",
|
||||
"close": "Закрыть",
|
||||
"shareYourScreen": "Показать экран",
|
||||
"yourEntireScreen": "Весь экран",
|
||||
"applicationWindow": "Окно приложения"
|
||||
"remoteControlTitle": "",
|
||||
"remoteControlRequestMessage": "",
|
||||
"remoteControlShareScreenWarning": "",
|
||||
"remoteControlDeniedMessage": "__user__ отклонил ваш запрос на дистанционное управление!",
|
||||
"remoteControlAllowedMessage": "__user__ принял ваш запрос на дистанционное управление!",
|
||||
"remoteControlErrorMessage": "Произошла ошибка при попытке запросить разрешения удалённого управления от __user__!",
|
||||
"startRemoteControlErrorMessage": "",
|
||||
"remoteControlStopMessage": "Сессия дистанционного управления завершена!",
|
||||
"close": "",
|
||||
"shareYourScreen": "",
|
||||
"yourEntireScreen": "",
|
||||
"applicationWindow": ""
|
||||
},
|
||||
"\u0005dialog": {},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
"Эта встреча защищена паролем. Пожалуйста, используйте для входа:",
|
||||
"Эта конференция защищена паролем. Пожалуйста, используйте это пин для входа:",
|
||||
"",
|
||||
"",
|
||||
"__sharedKey__",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"subject": "Приглашение в __appName__ (__conferenceName__)",
|
||||
"subject": "Приглашение для __appName__ (__conferenceName__)",
|
||||
"body": [
|
||||
"Здравствуйте! Приглашаю вас на текущую встречу __appName__.",
|
||||
"Привет! я бы хотел пригласить тебя на __appName__ конференцию, которую мы как раз начали.",
|
||||
"",
|
||||
"",
|
||||
"Для подключения, пожалуйста, используйте ссылку:",
|
||||
"Пожелуста, следуй по ссылке, чтобы подключиться к конференции.",
|
||||
"",
|
||||
"",
|
||||
"__roomUrl__",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
"Имейте в виду, что __appName__ в настоящее время поддерживается только в __supportedBrowsers__. Вам понадобится один из этих браузеров.",
|
||||
"Имей в виду, что __appName__ сейчас поддерживается только __supportedBrowsers__, так что полюзуйся одним из этих браузеров.",
|
||||
"",
|
||||
"",
|
||||
"Присоединяйтесь!"
|
||||
"Услышимся через секунду!"
|
||||
],
|
||||
"and": "и"
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "Ошибка",
|
||||
"CONNECTING": "Подключение",
|
||||
"CONNECTING": "Идёт подключение",
|
||||
"RECONNECTING": "Проблема с сетью. Переподключение...",
|
||||
"CONNFAIL": "Сбой подключения",
|
||||
"AUTHENTICATING": "Аутентификация",
|
||||
@@ -375,190 +397,89 @@
|
||||
"ATTACHED": "Прикреплено"
|
||||
},
|
||||
"recording": {
|
||||
"busy": "Мы стараемся обеспечить больше ресурсов для записи. Пожалуйста, попробуйте через несколько минут.",
|
||||
"busyTitle": "Все записывающие устройства заняты",
|
||||
"buttonTooltip": "Запись (старт/стоп)",
|
||||
"error": "Ошибка записи. Пожалуйста, попробуйте позже.",
|
||||
"failedToStart": "Ошибка начала записи",
|
||||
"off": "Запись остановлена",
|
||||
"pending": "Записываем ожидаем подключение участницы...",
|
||||
"on": "Запись",
|
||||
"pending": "Запись приостановлена: ожидаем подключение участника...",
|
||||
"serviceName": "Служба записи",
|
||||
"unavailable": "Служба __serviceName__ сейчас недоступна. Мы работаем над исправлением этой ошибки. Пожалуйста, попробуйте позже.",
|
||||
"unavailableTitle": "Запись невозможна"
|
||||
"off": "Запись остановлена",
|
||||
"failedToStart": "Ошибка при начале записи",
|
||||
"buttonTooltip": "Начать / Остановить запись",
|
||||
"error": "Ошибка записи. Попробуйте позже.",
|
||||
"unavailable": "Сервис записи сейчас недоступен. Попробуйте позже."
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "Освобождаем новые ресурсы для трансляции. Пожалуйста, попробуйте снова через несколько минут.",
|
||||
"busyTitle": "Все ресурсы для трансляции уже задействованы",
|
||||
"buttonTooltip": "Трансляция (старт/стоп)",
|
||||
"changeSignIn": "Переключить аккаунты.",
|
||||
"choose": "Выбрать трансляцию",
|
||||
"chooseCTA": "Выберите трансляцию. Вы вошли в систему как __email__. ",
|
||||
"enterStreamKey": "Введите ваш ключ трансляции YouTube.",
|
||||
"error": "Ошибка трансляции. Пожалуйста, попробуйте снова.",
|
||||
"errorAPI": "Произошла ошибка при доступе к вашим трансляциям на YouTube. Повторите попытку входа в систему.",
|
||||
"failedToStart": "Ошибка трансляции видео",
|
||||
"off": "Трансляция остановлена",
|
||||
"pending": "Начинаю трансляцию...",
|
||||
"on": "Трансляция",
|
||||
"pending": "Начинаем трансляцию...",
|
||||
"serviceName": "Служба трансляции",
|
||||
"signIn": "Войти через Google",
|
||||
"signInCTA": "Войдите или введите свой ключ трансляции YouTube.",
|
||||
"start": "Начать трансляцию",
|
||||
"streamIdHelp": "Что это?",
|
||||
"unavailableTitle": "Трансляция недоступна"
|
||||
},
|
||||
"videoSIPGW": {
|
||||
"busy": "Мы работаем над высвобождением ресурсов. Пожалуйста, попробуйте через несколько минут.",
|
||||
"busyTitle": "Служба сейчас занята",
|
||||
"errorInvite": "Встреча еще не началась. Пожалуйста, попробуйте позже.",
|
||||
"errorInviteTitle": "Ошибка приглашения в комнату",
|
||||
"errorAlreadyInvited": "__displayName__ уже приглашен",
|
||||
"errorInviteFailedTitle": "Ошибка приглашения __displayName__",
|
||||
"errorInviteFailed": "Мы работаем над решением проблемы. Пожалуйста, попробуйте позже.",
|
||||
"pending": "__displayName__ был приглашен",
|
||||
"serviceName": "Служба комнат",
|
||||
"unavailableTitle": "Служба недоступна"
|
||||
"off": "Трансляция остановлена",
|
||||
"unavailable": "Служба трансляций сейчас недоступна. Попробуйте позже.",
|
||||
"failedToStart": "Трансляция видео не может быть начата",
|
||||
"buttonTooltip": "Начать / Остановить прямую трансляцию",
|
||||
"streamIdRequired": "Пожалуйста введите идентификатор трансляции, чтобы запустить её.",
|
||||
"streamIdHelp": "Где я могу найти это?",
|
||||
"error": "Не удалось начать трансляцию. Попробуйте снова.",
|
||||
"busy": "Все рекордеры сейчас заняты. Попробуйте позже."
|
||||
},
|
||||
"speakerStats": {
|
||||
"hours": "__count__ч",
|
||||
"minutes": "__count__м",
|
||||
"hours": "",
|
||||
"minutes": "",
|
||||
"name": "Имя",
|
||||
"seconds": "__count__с",
|
||||
"speakerStats": "Статистика выступлений",
|
||||
"speakerTime": "Время выступлений"
|
||||
"seconds": "",
|
||||
"speakerStats": "",
|
||||
"speakerTime": ""
|
||||
},
|
||||
"deviceSelection": {
|
||||
"deviceSettings": "Настройки устройства",
|
||||
"noPermission": "Нет доступа",
|
||||
"previewUnavailable": "Предпросмотр недоступен",
|
||||
"selectADevice": "Выбор устройства",
|
||||
"testAudio": "Тест звука"
|
||||
"deviceSettings": "",
|
||||
"noPermission": "",
|
||||
"previewUnavailable": "",
|
||||
"selectADevice": "",
|
||||
"testAudio": ""
|
||||
},
|
||||
"invite": {
|
||||
"addPassword": "",
|
||||
"callNumber": "",
|
||||
"enterID": "",
|
||||
"howToDialIn": "",
|
||||
"hidePassword": "",
|
||||
"inviteTo": "",
|
||||
"invitedYouTo": "",
|
||||
"locked": "",
|
||||
"showPassword": "",
|
||||
"unlocked": ""
|
||||
},
|
||||
"videoStatus": {
|
||||
"callQuality": "Качество связи",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "Видео высокого качества",
|
||||
"highDefinition": "Высокое качество",
|
||||
"labelTooltipAudioOnly": "Включен режим \"только звук\"",
|
||||
"labelTooiltipNoVideo": "Нет видео",
|
||||
"labelTooltipVideo": "Текущее качество видео",
|
||||
"ld": "LD",
|
||||
"ldTooltip": "Видео низкого качества",
|
||||
"lowDefinition": "Низкое качество",
|
||||
"onlyAudioAvailable": "Только звук",
|
||||
"onlyAudioSupported": "В этом браузере разрешен только звук.",
|
||||
"p2pEnabled": "Включен режим \"точка-к-точке\"",
|
||||
"p2pVideoQualityDescription": "В режиме \"точка-к-точке\" качество входящего сигнала может быть установлено в позицию \"высокое\" или \"только звук\". Остальные варианты в этом режиме не работают.",
|
||||
"recHighDefinitionOnly": "Предпочтительно высокое качество.",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Видео стандартного качества",
|
||||
"standardDefinition": "Стандартное качество (SD)",
|
||||
"qualityButtonTip": "Изменить качество входящего видео"
|
||||
"callQuality": "",
|
||||
"changeVideoTip": "",
|
||||
"hd": "",
|
||||
"highDefinition": "",
|
||||
"ld": "",
|
||||
"lowDefinition": "",
|
||||
"p2pEnabled": "",
|
||||
"p2pVideoQualityDescription": "",
|
||||
"recHighDefinitionOnly": "",
|
||||
"sd": "",
|
||||
"standardDefinition": "",
|
||||
"qualityButtonTip": ""
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "сейчас __status__"
|
||||
"dial": "Дозвон",
|
||||
"dialOut": "",
|
||||
"statusMessage": "",
|
||||
"enterPhone": "",
|
||||
"phoneNotAllowed": ""
|
||||
},
|
||||
"addPeople": {
|
||||
"add": "Пригласить",
|
||||
"countryNotSupported": "Эта страна пока не поддерживается.",
|
||||
"countryReminder": "Вызов не в США? Пожалуйста, убедитесь, что указали код страны!",
|
||||
"disabled": "",
|
||||
"invite": "Пригласить",
|
||||
"loading": "Поиск людей и номеров телефонов",
|
||||
"loadingNumber": "Поиск людей для приглашения",
|
||||
"loadingPeople": "Поиск людей для приглашения",
|
||||
"noResults": "Поиск не дал результата",
|
||||
"noValidNumbers": "Пожалуйста, введите номер телефона",
|
||||
"notAvailable": "Поиск не дал результата",
|
||||
"searchNumbers": "Добавить номера телефонов",
|
||||
"searchPeople": "Поиск не дал результата",
|
||||
"searchPeopleAndNumbers": "Поиск людей или добавление их телефонов",
|
||||
"telephone": "Номер: __number__",
|
||||
"title": "Пригласить людей на эту встречу",
|
||||
"failedToAdd": "Ошибка добавления участников"
|
||||
"add": "Добавить",
|
||||
"noResults": "",
|
||||
"searchPlaceholder": "",
|
||||
"title": "",
|
||||
"failedToAdd": ""
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "Небольшая заминка.",
|
||||
"retry": "Попробовать снова",
|
||||
"support": "Поддержка",
|
||||
"supportMsg": "Если это продолжится, свяжитесь с"
|
||||
"msg": "",
|
||||
"retry": "",
|
||||
"support": "",
|
||||
"supportMsg": ""
|
||||
},
|
||||
"deviceError": {
|
||||
"cameraError": "Ошибка доступа к камере",
|
||||
"microphoneError": "Ошибка доступа к микрофону",
|
||||
"cameraPermission": "Ошибка доступа к микрофону",
|
||||
"microphonePermission": "Нет разрешения на доступ к микрофону"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "Средне",
|
||||
"bad": "Плохо",
|
||||
"good": "Хорошо",
|
||||
"detailsLabel": "Расскажите подробнее.",
|
||||
"rateExperience": "Оценка качества связи",
|
||||
"veryBad": "Очень плохо",
|
||||
"veryGood": "Очень хорошо"
|
||||
},
|
||||
"info": {
|
||||
"addPassword": "Добавить пароль",
|
||||
"cancelPassword": "Убрать пароль",
|
||||
"conferenceURL": "Ссылка:",
|
||||
"country": "Страна",
|
||||
"dialANumber": "Чтобы присоединиться к встрече, наберите один из этих номеров, а затем введите PIN-код: __conferenceID __ #",
|
||||
"dialInNumber": "Номер:",
|
||||
"dialInConferenceID": "PIN:",
|
||||
"dialInNotSupported": "К сожалению, набор номера в настоящее время не поддерживается.",
|
||||
"genericError": "Что-то пошло не так.",
|
||||
"inviteLiveStream": "Трансляция этой встречи: __url__",
|
||||
"invitePhone": "Чтобы присоединиться по телефону, наберите __number__ и введите PIN-код: __conferenceID __#",
|
||||
"invitePhoneAlternatives": "Посмотреть другие номера телефонов: __url__",
|
||||
"inviteURL": "Присоединиться к видеоконференции: __url__",
|
||||
"liveStreamURL": "Трансляция:",
|
||||
"moreNumbers": "Больше номеров",
|
||||
"noNumbers": "Нет номеров для набора.",
|
||||
"noPassword": "нет",
|
||||
"noRoom": "Для набора номера не было указано ни одной комнаты.",
|
||||
"numbers": "Номера для набора",
|
||||
"password": "Пароль:",
|
||||
"title": "Поделиться",
|
||||
"tooltip": "Получить информацию об этой встрече"
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "OK",
|
||||
"alertTitle": "Внимание",
|
||||
"alertURLText": "Ошибка адреса сервера",
|
||||
"conferenceSection": "Номера для набора",
|
||||
"displayName": "Отображаемое имя",
|
||||
"email": "Email",
|
||||
"header": "Настройки",
|
||||
"profileSection": "Профиль",
|
||||
"serverURL": "Адрес сервера",
|
||||
"startWithAudioMuted": "Начать с отключенным звуком",
|
||||
"startWithVideoMuted": "Начать с отключенным видео"
|
||||
},
|
||||
"calendarSync": {
|
||||
"later": "Позже",
|
||||
"next": "Предстоящие",
|
||||
"nextMeeting": "следующая встреча",
|
||||
"now": "Сейчас",
|
||||
"permissionButton": "Открыть настройки",
|
||||
"permissionMessage": "Для показа ваших встреч в приложении нужен доступ к календарю."
|
||||
},
|
||||
"recentList": {
|
||||
"today": "Сегодня",
|
||||
"yesterday": "Вчера",
|
||||
"earlier": "Ранее"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Потяните для обновления"
|
||||
},
|
||||
"deepLinking": {
|
||||
"title": "Запуск вашей встречи в __app __...",
|
||||
"description": "Ничего не случилось? Мы попытались запустить вашу встречу в настольном приложении __app__. Повторите попытку или запустите ее в веб-приложении __app__.",
|
||||
"tryAgainButton": "Повторите в настольном приложении",
|
||||
"launchWebButton": "Запустить в браузере",
|
||||
"appNotInstalled": "Чтобы присоединиться к этой встрече на телефоне, нужно мобильное приложение __app__.",
|
||||
"downloadApp": "Скачать приложение",
|
||||
"openApp": "Перейти к приложению"
|
||||
"cameraPermission": "",
|
||||
"microphonePermission": ""
|
||||
}
|
||||
}
|
||||
@@ -1,551 +0,0 @@
|
||||
{
|
||||
"contactlist_plural": "__count__ 位成員",
|
||||
"passwordSetRemotely": "由另一位成員設置",
|
||||
"poweredby": "技術支援",
|
||||
"inviteUrlDefaultMsg": "您的會議正在建立起來………",
|
||||
"me": "我",
|
||||
"speaker": "",
|
||||
"raisedHand": "請求發言",
|
||||
"defaultNickname": "例如 阿美 志明",
|
||||
"defaultLink": "例如 __url__",
|
||||
"audioDevices": {
|
||||
"bluetooth": "藍牙",
|
||||
"headphones": "耳機",
|
||||
"phone": "電話",
|
||||
"speaker": "發言者"
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "僅用音訊",
|
||||
"featureToggleDisabled": "在僅用音訊模式下,開關 __feature__ 功能是停用的"
|
||||
},
|
||||
"userMedia": {
|
||||
"react-nativeGrantPermissions": "",
|
||||
"chromeGrantPermissions": "",
|
||||
"androidGrantPermissions": "",
|
||||
"electronGrantPermissions": "",
|
||||
"firefoxGrantPermissions": "當瀏覽器要求權限允許時,請選擇<b><i>分享設備</i></b> ",
|
||||
"operaGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>允許</i></b>",
|
||||
"iexplorerGrantPermissions": "",
|
||||
"safariGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>OK</i></b>",
|
||||
"nwjsGrantPermissions": "請允許權限使用您的攝影裝置和麥克風",
|
||||
"edgeGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>是的</i></b>"
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"keyboardShortcuts": "快捷鍵",
|
||||
"raiseHand": "舉手發言或不作發言",
|
||||
"pushToTalk": "按鍵通話",
|
||||
"toggleScreensharing": "在攝影鏡頭和螢幕分享之間進行切換",
|
||||
"toggleFilmstrip": "顯示或隱藏視訊",
|
||||
"toggleShortcuts": "顯示或隱藏說明選單",
|
||||
"focusLocal": "聚焦於自己的視訊",
|
||||
"focusRemote": "聚焦於另一位通話者的視訊",
|
||||
"toggleChat": "開啟或關閉聊天",
|
||||
"mute": "靜音或解除靜音",
|
||||
"fullScreen": "進入或退出全螢幕",
|
||||
"videoMute": "啟動或停止自己的攝影裝置",
|
||||
"showSpeakerStats": "顯示發言者數據"
|
||||
},
|
||||
"welcomepage": {
|
||||
"appDescription": "快來使用吧,團隊全部成員使用視訊通話,可以邀請任何您所認識的人。 __app__ 是一套完全加密、100% 開放源碼的視訊會議解決方案。無需註冊帳號,無時無刻不分日夜均可免費使用。",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "語音",
|
||||
"video": "視訊"
|
||||
},
|
||||
"calendar": "日曆",
|
||||
"go": "開始",
|
||||
"join": "加入",
|
||||
"privacy": "隱私",
|
||||
"roomname": "輸入會議室名稱",
|
||||
"roomnameHint": "請輸入您想加入的會議室 URL 網址或名稱。您可以用個名稱來建立會議室,只要其他人輸入相同的名稱就能加入會議室喔。",
|
||||
"sendFeedback": "發送回報",
|
||||
"terms": "條款",
|
||||
"title": "更加安全、更具彈性、又完全免費的視訊會議系統"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
"title": "__app__ 需要使用您的麥克風和攝影裝置。"
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"title": "由於電腦進入休眠,您的視訊通話已經中斷。",
|
||||
"text": "按下 <i>重新加入</i> 按鈕重新連接。",
|
||||
"rejoinKeyTitle": "重新加入"
|
||||
},
|
||||
"toolbar": {
|
||||
"addPeople": "新增人員到您的通話中",
|
||||
"audioonly": "啟用/停用 僅用音訊模式(節省頻寬)",
|
||||
"callQuality": "管理通話品質",
|
||||
"enterFullScreen": "觀看全螢幕",
|
||||
"exitFullScreen": "跳出全螢幕",
|
||||
"feedback": "留言回報",
|
||||
"moreActions": "更多動作",
|
||||
"mute": "靜音 / 解除靜音",
|
||||
"videomute": "啟動/停止 攝影裝置",
|
||||
"authenticate": "驗證",
|
||||
"lock": "鎖定/解鎖 會議室",
|
||||
"chat": "開啟/關閉 聊天",
|
||||
"etherpad": "開啟/關閉 分享文件檔案",
|
||||
"documentOpen": "開啟分享的文件檔案",
|
||||
"documentClose": "關閉分享的文件檔案",
|
||||
"sharedvideo": "分享 YouTube 視訊",
|
||||
"sharescreen": "螢幕分享",
|
||||
"stopSharedVideo": "停止 YouTube 視訊",
|
||||
"fullscreen": "觀看/跳出 全螢幕",
|
||||
"sip": "播打 SIP 號碼",
|
||||
"Settings": "",
|
||||
"hangup": "留言",
|
||||
"login": "登入",
|
||||
"logout": "",
|
||||
"sharedVideoMutedPopup": "您分享的視訊已經靜音,現在可以和其他成員交談了。",
|
||||
"micMutedPopup": "您的麥克風已經處於靜音,可以觀看分享視訊了。",
|
||||
"talkWhileMutedPopup": "您要發言嗎? 目前您處於靜音。",
|
||||
"unableToUnmutePopup": "當分享視訊正在使用時,您不能解除靜音。",
|
||||
"cameraDisabled": "攝影裝置無法使用",
|
||||
"micDisabled": "麥克風無法使用",
|
||||
"filmstrip": "顯示/隱藏 視訊",
|
||||
"profile": "編輯您的簡介",
|
||||
"raiseHand": "舉手/取消 請求發言",
|
||||
"shortcuts": "查看快捷鍵",
|
||||
"speakerStats": "發言者數據"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "請在下面欄位輸入暱稱",
|
||||
"popover": "選擇暱稱"
|
||||
},
|
||||
"messagebox": "請輸入文字..."
|
||||
},
|
||||
"settings": {
|
||||
"title": "",
|
||||
"update": "更新",
|
||||
"name": "",
|
||||
"startAudioMuted": "全部人啟動時處於靜音",
|
||||
"startVideoMuted": "全部人啟動時隱藏視訊畫面",
|
||||
"selectCamera": "攝影裝置",
|
||||
"selectMic": "麥克風",
|
||||
"selectAudioOutput": "音訊輸出",
|
||||
"followMe": "全部人跟隨仿照我",
|
||||
"noDevice": "",
|
||||
"cameraAndMic": "攝影裝置和麥克風",
|
||||
"moderator": "主持人",
|
||||
"password": "設定密碼",
|
||||
"audioVideo": "音訊和視訊"
|
||||
},
|
||||
"profile": {
|
||||
"title": "",
|
||||
"setDisplayNameLabel": "設定您的顯示名稱",
|
||||
"setEmailLabel": "設置您的大頭人像電子信箱",
|
||||
"setEmailInput": "輸入您的電子信箱"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"moderator": "主持人",
|
||||
"videomute": "成員已經停用攝影裝置",
|
||||
"mute": "成員處於靜音",
|
||||
"kick": "踢出",
|
||||
"muted": "處於靜音",
|
||||
"domute": "",
|
||||
"flip": "翻轉",
|
||||
"remoteControl": "遠端控制"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "連接資料",
|
||||
"bitrate": "比特率:",
|
||||
"packetloss": "丟包:",
|
||||
"resolution": "解析度:",
|
||||
"framerate": "影格率:",
|
||||
"less": "顯示較少",
|
||||
"more": "顯示更多",
|
||||
"address": "地址:",
|
||||
"remoteport": "遠端端口:",
|
||||
"localport": "本地端口:",
|
||||
"localaddress": "本地地址:",
|
||||
"remoteaddress": "遠端地址:",
|
||||
"transport": "傳輸:",
|
||||
"bandwidth": "估計頻寬:",
|
||||
"na": "一旦會議啟動,即可回到此處查看連接資訊",
|
||||
"turn": " (轉)",
|
||||
"quality": {
|
||||
"good": "",
|
||||
"inactive": "未啟用",
|
||||
"lost": "漏失",
|
||||
"nonoptimal": "不甚理想",
|
||||
"poor": "不好"
|
||||
},
|
||||
"status": "連接:"
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "已經中斷連接",
|
||||
"moderator": "主持人權限已經取得!",
|
||||
"connectedOneMember": "__name__ 已經連接",
|
||||
"connectedTwoMembers": "__first__ 與 __second__ 已經連接",
|
||||
"connectedThreePlusMembers": "__name__ 與 __count__ 位其他成員已經連接",
|
||||
"somebody": "某人",
|
||||
"me": "自己",
|
||||
"focus": "會議焦點",
|
||||
"focusFail": "__component__ 無法使用 - 請在 __ms__ 秒後重試",
|
||||
"grantedTo": "主持人權限已授予 __to__!",
|
||||
"grantedToUnknown": "主持人權限已經授予 $t(somebody) !",
|
||||
"muted": "您已經啟動通話,並處於靜音狀態。",
|
||||
"mutedTitle": "您目前處於靜音!",
|
||||
"raisedHand": "請求發言。",
|
||||
"suboptimalExperienceTitle": "瀏覽器警告",
|
||||
"suboptimalExperienceDescription": "呃……恐怕您對 __appName__ 的體驗不是很好,我們正在嘗試找方法改進對此瀏覽器的支援。現下敬請選用 <a href='static/recommendedBrowsers.html' target='_blank'>全力支援的瀏覽器</a> 來進行。"
|
||||
},
|
||||
"dialog": {
|
||||
"allow": "允許",
|
||||
"kickMessage": "您已經被踢出會議!",
|
||||
"popupErrorTitle": "彈出視窗遭到阻攔",
|
||||
"popupError": "您的瀏覽器在此網站上阻攔彈出視窗。請在瀏覽器的安全設置中開啟它並再試一次。",
|
||||
"passwordErrorTitle": "密碼錯誤",
|
||||
"passwordError": "此會議目前已受密碼保護。只有會議的擁有者可以設定密碼。",
|
||||
"passwordError2": "此會議目前未受密碼保護。只有會議的擁有者可以設定密碼。",
|
||||
"connectError": "喔哦!發生錯誤,無法連接至會議。",
|
||||
"connectErrorWithMsg": "喔哦!發生錯誤,無法連接至會議: __msg__",
|
||||
"incorrectPassword": "錯誤的用戶名稱或密碼",
|
||||
"connecting": "",
|
||||
"copy": "複製",
|
||||
"contactSupport": "聯絡支援",
|
||||
"error": "",
|
||||
"detectext": "嘗試偵測桌面分享擴充應用程式時發生錯誤。",
|
||||
"failedpermissions": "未能取得使用本地麥克風或攝影裝置的權限。",
|
||||
"conferenceReloadTitle": "不好意思,出錯了。",
|
||||
"conferenceReloadMsg": "我們正試著修復狀況。重新連接於 __seconds__ 秒內……",
|
||||
"conferenceDisconnectTitle": "您已經被中斷連接。",
|
||||
"conferenceDisconnectMsg": "請檢查一下網路連接。將在 __seconds__ 秒後重新連接…",
|
||||
"dismiss": "解除",
|
||||
"rejoinNow": "立即重新加入",
|
||||
"maxUsersLimitReachedTitle": "成員人數已經達到上限",
|
||||
"maxUsersLimitReached": "由於會議已達到人數上限,額滿不能加入。請聯絡會議發起人,或是稍後再次嘗試!",
|
||||
"lockTitle": "鎖定失敗",
|
||||
"lockMessage": "鎖定會議失敗。",
|
||||
"warning": "",
|
||||
"passwordNotSupportedTitle": "不支援密碼",
|
||||
"passwordNotSupported": "不支援設置會議密碼。",
|
||||
"internalErrorTitle": "內部錯誤",
|
||||
"internalError": "喔哦!出現了點問題。發生錯誤: __error__",
|
||||
"unableToSwitch": "無法切換視訊串流。",
|
||||
"SLDFailure": "喔哦!發生錯誤,無法靜音! (SLD故障)",
|
||||
"SRDFailure": "喔哦!發生錯誤,無法停止視訊! (SRD故障)",
|
||||
"oops": "喔哦!",
|
||||
"currentPassword": "目前的密碼是",
|
||||
"passwordLabel": "密碼",
|
||||
"defaultError": "存在某種錯誤",
|
||||
"passwordRequired": "需要密碼",
|
||||
"Ok": "Ok",
|
||||
"done": "完成",
|
||||
"Remove": "移除",
|
||||
"removePassword": "移除密碼",
|
||||
"shareVideoTitle": "分享視訊",
|
||||
"shareVideoLinkError": "請提供正確的 YouTube 連結。",
|
||||
"removeSharedVideoTitle": "移除分享視訊",
|
||||
"removeSharedVideoMsg": "您確定要移除自己的分享視訊嗎?",
|
||||
"alreadySharedVideoMsg": "另一位成員正準備分享視訊。會議一次僅允許一位視訊分享。",
|
||||
"alreadySharedVideoTitle": "一次只能允許一位視訊分享",
|
||||
"WaitingForHost": "等侯主辦人………",
|
||||
"WaitForHostMsg": "會議 <b>__room__ </b> 尚未啟動。如果您是主辦人,請授權開始,否則請等候主辦人。",
|
||||
"IamHost": "我是主辦人",
|
||||
"Cancel": "取消",
|
||||
"Submit": "提交",
|
||||
"retry": "重試",
|
||||
"logoutTitle": "登出",
|
||||
"logoutQuestion": "您確定要登出並停止會議嗎?",
|
||||
"sessTerminated": "通話已經終止",
|
||||
"hungUp": "我方掛斷",
|
||||
"joinAgain": "再次加入",
|
||||
"Share": "",
|
||||
"Save": "儲存",
|
||||
"recording": "",
|
||||
"recordingToken": "輸入錄製標記",
|
||||
"Back": "返回",
|
||||
"serviceUnavailable": "服務無法使用",
|
||||
"gracefulShutdown": "本伺服器閉關維護中,請稍後再試。",
|
||||
"Yes": "是的",
|
||||
"reservationError": "預約系統錯誤",
|
||||
"reservationErrorMsg": "錯誤碼: __code__, 訊息: __msg__",
|
||||
"password": "輸入密碼",
|
||||
"userPassword": "用戶密碼",
|
||||
"token": "標記",
|
||||
"tokenAuthFailedTitle": "",
|
||||
"tokenAuthFailed": "對不起,您未被允許加入此會議。",
|
||||
"displayNameRequired": "顯示名稱是必須的",
|
||||
"enterDisplayName": "請輸入您的顯示名稱",
|
||||
"feedbackHelp": "您的回報將幫助提昇視訊體驗。",
|
||||
"feedbackQuestion": "請告訴我們本次通話體驗!",
|
||||
"thankYou": "感謝您使用 __appName__!",
|
||||
"sorryFeedback": "很抱歉聽到這些,能告訴我們更多詳情嗎?",
|
||||
"liveStreaming": "",
|
||||
"streamKey": "直播串流密鑰",
|
||||
"startLiveStreaming": "立即開始直播",
|
||||
"startRecording": "啟動錄製作業",
|
||||
"stopStreamingWarning": "確定要停止直播串流嗎?",
|
||||
"stopRecordingWarning": "確定要停止錄製作業嗎?",
|
||||
"stopLiveStreaming": "停止直播串流",
|
||||
"stopRecording": "停止錄製作業",
|
||||
"doNotShowMessageAgain": "不再顯示此訊息",
|
||||
"permissionDenied": "權限受到禁止",
|
||||
"screenSharingFailedToInstall": "喔哦!螢幕分享擴充程式安裝失敗。",
|
||||
"screenSharingFailedToInstallTitle": "螢幕分享擴充安裝失敗",
|
||||
"screenSharingFirefoxPermissionDeniedError": "嘗試進行螢幕分享時遇到問題。請確認您有賦予相對的權限允許。",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "喔哦!我們無法啟動螢幕分享!",
|
||||
"screenSharingPermissionDeniedError": "喔哦!您的視訊分享擴充權限發生一點問題。請重新載入再試一次。",
|
||||
"cameraUnsupportedResolutionError": "您的攝影裝置不支援所需的視訊解析度。",
|
||||
"cameraUnknownError": "由於不明原因,無法使用攝影裝置。",
|
||||
"cameraPermissionDeniedError": "您未取得權限使用您的攝影裝置。您仍然可參加會議,但是其他人無法看到。可以利用位址欄中的攝影裝置按鈕來修復啟動。",
|
||||
"cameraNotFoundError": "未發現攝影裝置。",
|
||||
"cameraConstraintFailedError": "您的攝影裝置不符合要求。",
|
||||
"micUnknownError": "不明原因造成麥克風無法使用。",
|
||||
"micPermissionDeniedError": "您未取得權限使用麥克風。您仍然可參加會議,但是其他人無法聽到。可以利用位址欄中的攝影裝置按鈕來修復啟動。",
|
||||
"micNotFoundError": "未發現麥克風。",
|
||||
"micConstraintFailedError": "您的麥克風不符合要求。",
|
||||
"micNotSendingDataTitle": "無法取用麥克風",
|
||||
"micNotSendingData": "我們無法取用您的麥克風。請從設罝選單裡選擇其他設備或者重新裝載。",
|
||||
"cameraNotSendingDataTitle": "無法取用攝影裝置",
|
||||
"cameraNotSendingData": "我們無法取用您的攝影裝置。請檢查是否有其他程序正在使用這個設備,否則請從設置選單裡選擇其他設備或者重新裝載。",
|
||||
"goToStore": "前往應用商店",
|
||||
"externalInstallationTitle": "需要擴充應用程式",
|
||||
"externalInstallationMsg": "",
|
||||
"inlineInstallationMsg": "您需要安裝桌面分享擴充應用程式。",
|
||||
"inlineInstallExtension": "立即安裝",
|
||||
"muteParticipantTitle": "靜音這位成員?",
|
||||
"muteParticipantBody": "您無法對他們解除靜音,但是他們自己隨時可以解除靜音。",
|
||||
"muteParticipantButton": "靜音",
|
||||
"remoteControlTitle": "遠端桌面控制",
|
||||
"remoteControlRequestMessage": "您要允許 __user__ 遠端控制您的桌面嗎?",
|
||||
"remoteControlShareScreenWarning": "注意:如果按下 \"允許\" 您將分享自己的螢幕!",
|
||||
"remoteControlDeniedMessage": "__user__ 拒絕您進行遠端控制的要求!",
|
||||
"remoteControlAllowedMessage": "__user__ 接受您進行遠端控制的要求!",
|
||||
"remoteControlErrorMessage": "在嘗試向 __user__ 請求遠端控制權限時發生錯誤!",
|
||||
"startRemoteControlErrorMessage": "嘗試啟動遠端控制階段時發生錯誤!",
|
||||
"remoteControlStopMessage": "遠端控制階段結束!",
|
||||
"close": "關閉",
|
||||
"shareYourScreen": "分享自己的螢幕",
|
||||
"yourEntireScreen": "自己的全螢幕",
|
||||
"applicationWindow": "應用程式視窗"
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
"該會議受密碼保護,請在加入會議時使用下列密碼:",
|
||||
"",
|
||||
"",
|
||||
"__sharedKey__",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"subject": "邀請發至__appName__ (__conferenceName__)",
|
||||
"body": [
|
||||
"嗨, 我想邀請您加入剛剛建立的 __appName__ 會議。",
|
||||
"",
|
||||
"",
|
||||
"請點按下面的連結來加入會議。",
|
||||
"",
|
||||
"",
|
||||
"__roomUrl__",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
" 請注意 __appName__ 目前僅支援下列瀏覽器:__supportedBrowsers__, 敬請選用其一。",
|
||||
"",
|
||||
"",
|
||||
"即刻加入與您交談喔!"
|
||||
],
|
||||
"and": "與"
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "錯誤",
|
||||
"CONNECTING": "連接中",
|
||||
"RECONNECTING": "網絡錯誤發生。重新連接中………",
|
||||
"CONNFAIL": "連接失敗",
|
||||
"AUTHENTICATING": "驗證中",
|
||||
"AUTHFAIL": "驗證失敗",
|
||||
"CONNECTED": "已經連接",
|
||||
"DISCONNECTED": "已經中斷連接",
|
||||
"DISCONNECTING": "中斷連接中",
|
||||
"ATTACHED": "已經附加"
|
||||
},
|
||||
"recording": {
|
||||
"busy": "我們正在釋放錄製資源。請過幾分鐘後再試。",
|
||||
"busyTitle": "全部錄製設備正在忙碌",
|
||||
"buttonTooltip": "啟動/停止 錄製作業",
|
||||
"error": "錄製作業失敗。請再次重試。",
|
||||
"failedToStart": "錄製啟動失敗",
|
||||
"off": "錄製作業已經停止",
|
||||
"on": "錄製作業中",
|
||||
"pending": "錄製作業正在等待成員加入……",
|
||||
"serviceName": "錄製作業服務",
|
||||
"unavailable": "喔哦!__serviceName__ 目前無法使用。我們正在解決此問題,請稍後再試。",
|
||||
"unavailableTitle": "錄製作業無法使用"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "我們正在釋放串流資源。請過幾分鐘後再試。",
|
||||
"busyTitle": "全部串流設備正在忙碌",
|
||||
"buttonTooltip": "啟動/停止 直播串流",
|
||||
"changeSignIn": "切換帳號。",
|
||||
"choose": "選擇直播串流",
|
||||
"chooseCTA": "請選擇直播串流選項。您目前是以 __email__ 身份登入。",
|
||||
"enterStreamKey": "在此輸入您的 YouTube 直播串流密鑰。",
|
||||
"error": "直播串流失敗。請重試。",
|
||||
"errorAPI": "取用您的 YouTube 播出時發生錯誤。請重新登入。",
|
||||
"failedToStart": "直播串流啟動失敗",
|
||||
"off": "直播串流已經停止",
|
||||
"on": "直播串流中",
|
||||
"pending": "啟動直播串流………",
|
||||
"serviceName": "直播串流服務",
|
||||
"signIn": "使用 Google 帳戶登入",
|
||||
"signInCTA": "輸入 YouTube 直播串流密鑰,或登入 YouTube 帳號。",
|
||||
"start": "啟動直播串流",
|
||||
"streamIdHelp": "這是什麼?",
|
||||
"unavailableTitle": "直播串流無法使用"
|
||||
},
|
||||
"videoSIPGW": {
|
||||
"busy": "我們正在清理釋放資源。請過幾分鐘後再試。",
|
||||
"busyTitle": "會議室服務正處於忙碌中",
|
||||
"errorInvite": "會議尚未開始,請稍後再來。",
|
||||
"errorInviteTitle": "錯誤邀請會議室",
|
||||
"errorAlreadyInvited": "__displayName__ 已受邀請",
|
||||
"errorInviteFailedTitle": "邀請 __displayName__ 失敗",
|
||||
"errorInviteFailed": "我們正在解決問題。請稍後再試。",
|
||||
"pending": "__displayName__ 已經邀請",
|
||||
"serviceName": "會議室服務",
|
||||
"unavailableTitle": "會議室服務無法使用"
|
||||
},
|
||||
"speakerStats": {
|
||||
"hours": "__count__h",
|
||||
"minutes": "__count__m",
|
||||
"name": "名稱",
|
||||
"seconds": "__count__s",
|
||||
"speakerStats": "發言者數據",
|
||||
"speakerTime": "發言者時間"
|
||||
},
|
||||
"deviceSelection": {
|
||||
"deviceSettings": "設備設置",
|
||||
"noPermission": "未取得權限",
|
||||
"previewUnavailable": "預覽無法使用",
|
||||
"selectADevice": "選擇設備",
|
||||
"testAudio": "測試聲音"
|
||||
},
|
||||
"videoStatus": {
|
||||
"callQuality": "通話品質",
|
||||
"hd": "HD 高清",
|
||||
"hdTooltip": "觀看高清視訊 HD",
|
||||
"highDefinition": "高清品質 HD",
|
||||
"labelTooltipAudioOnly": "僅有音訊模式已經啟用",
|
||||
"labelTooiltipNoVideo": "沒有視訊",
|
||||
"labelTooltipVideo": "目前視訊品質",
|
||||
"ld": "LD 低清",
|
||||
"ldTooltip": "觀看低清視訊 LD",
|
||||
"lowDefinition": "低清品質 LD",
|
||||
"onlyAudioAvailable": "僅有音訊可以使用",
|
||||
"onlyAudioSupported": "在此瀏覽器我們僅支援音訊功能。",
|
||||
"p2pEnabled": "點對點功能已經啟用",
|
||||
"p2pVideoQualityDescription": "在點對點模式下,通話品質只能使有高清和僅用音訊兩個選項。其他選項只有在點對點模式退出後才可使用。",
|
||||
"recHighDefinitionOnly": "將會偏好使用高清模式 HD。",
|
||||
"sd": "SD 標清",
|
||||
"sdTooltip": "觀看標清視訊 SD",
|
||||
"standardDefinition": "標清品質 SD",
|
||||
"qualityButtonTip": "修改接收視訊品質"
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "現在狀態為 __status__"
|
||||
},
|
||||
"addPeople": {
|
||||
"add": "",
|
||||
"countryNotSupported": "此目標區域尚未支援。",
|
||||
"countryReminder": "嘗試在美國外地通話?請確認開頭使用的國家代碼!",
|
||||
"disabled": "",
|
||||
"invite": "邀請",
|
||||
"loading": "尋找聯絡人及電話號碼",
|
||||
"loadingNumber": "驗證電話號碼",
|
||||
"loadingPeople": "正在尋搜人員進行邀請",
|
||||
"noResults": "沒有符合要求的搜尋結果",
|
||||
"noValidNumbers": "請輸入一組電話號碼",
|
||||
"notAvailable": "您不可以邀請人員。",
|
||||
"searchNumbers": "新增電話號碼",
|
||||
"searchPeople": "尋找人員",
|
||||
"searchPeopleAndNumbers": "尋找人員或新增電話號碼",
|
||||
"telephone": "電話: __number__",
|
||||
"title": "邀請人員參加會議",
|
||||
"failedToAdd": "無法增加成員"
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "好像有點卡卡不順。",
|
||||
"retry": "重試",
|
||||
"support": "支援",
|
||||
"supportMsg": "如果狀況一直發生,請聯絡"
|
||||
},
|
||||
"deviceError": {
|
||||
"cameraError": "無法取用您的攝影裝置",
|
||||
"microphoneError": "無法取用您的麥克風",
|
||||
"cameraPermission": "無法獲得攝影裝置取用權限",
|
||||
"microphonePermission": "無法獲得麥克風取用權限"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "普通中等",
|
||||
"bad": "很差",
|
||||
"good": "很好",
|
||||
"detailsLabel": "告訴我們本次會議使用上更多結果。",
|
||||
"rateExperience": "請您評價這次會議的體驗成效",
|
||||
"veryBad": "極差",
|
||||
"veryGood": "極好"
|
||||
},
|
||||
"info": {
|
||||
"addPassword": "新增密碼",
|
||||
"cancelPassword": "取消密碼",
|
||||
"conferenceURL": "連結:",
|
||||
"country": "國家",
|
||||
"dialANumber": "要加入您的會議,請撥打其中之一的電話號碼並輸入 PIN:__conferenceID__#",
|
||||
"dialInNumber": "播入:",
|
||||
"dialInConferenceID": "PIN 號碼:",
|
||||
"dialInNotSupported": "抱歉,目前不支援電話播入。",
|
||||
"genericError": "糟糕!出錯了。",
|
||||
"inviteLiveStream": "要觀看這場會議的直播串流,點按此連結: __url__",
|
||||
"invitePhone": "要想使用電話加入會議,請撥打 __number__ 並輸入 PIN: __conferenceID__#",
|
||||
"invitePhoneAlternatives": "查看更多電話號碼,點按此連結: __url__",
|
||||
"inviteURL": "加入視訊會議,請點按此連結: __url__",
|
||||
"liveStreamURL": "直播串流:",
|
||||
"moreNumbers": "更多成員",
|
||||
"noNumbers": "無播入號碼。",
|
||||
"noPassword": "無",
|
||||
"noRoom": "沒有會議室是指定要播打進入。",
|
||||
"numbers": "播入號碼",
|
||||
"password": "密碼:",
|
||||
"title": "分享",
|
||||
"tooltip": "取得關於會議的連接使用資訊"
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "確認",
|
||||
"alertTitle": "警告",
|
||||
"alertURLText": "所輸入的伺服器 URL 是無效的",
|
||||
"conferenceSection": "會議",
|
||||
"displayName": "顯示名稱",
|
||||
"email": "電子郵件",
|
||||
"header": "設置",
|
||||
"profileSection": "簡介",
|
||||
"serverURL": "伺服器 URL",
|
||||
"startWithAudioMuted": "啟動並音訊靜音",
|
||||
"startWithVideoMuted": "啟動並視訊靜音"
|
||||
},
|
||||
"calendarSync": {
|
||||
"later": "稍後",
|
||||
"next": "即將推出",
|
||||
"nextMeeting": "下次會議",
|
||||
"now": "現在",
|
||||
"permissionButton": "開啟設定",
|
||||
"permissionMessage": "日曆允許權限是必須的,以列入您的會議於應用程式中。"
|
||||
},
|
||||
"recentList": {
|
||||
"today": "今日",
|
||||
"yesterday": "昨天",
|
||||
"earlier": "稍早"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "下滑以重新整理"
|
||||
},
|
||||
"deepLinking": {
|
||||
"title": "發起您的會議於 __app__...",
|
||||
"description": "沒有發生作用嗎?我們嘗試發起您的會議於 __app__ desktop 桌面應用程式。請再試一次,或是發起會議於 __app__ 網路應用程式。",
|
||||
"tryAgainButton": "在桌面上再試一次",
|
||||
"launchWebButton": "在網路上發起",
|
||||
"appNotInstalled": "在您的手機上需要 __app__ 行動應用程式去加入這場會議。",
|
||||
"downloadApp": "下載應用 APP",
|
||||
"openApp": "繼續前往此應用程式"
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,7 @@
|
||||
"mute": "Mute or unmute your microphone",
|
||||
"fullScreen": "View or exit full screen",
|
||||
"videoMute": "Start or stop your camera",
|
||||
"showSpeakerStats": "Show speaker stats",
|
||||
"localRecording": "Show or hide local recording controls"
|
||||
"showSpeakerStats": "Show speaker stats"
|
||||
},
|
||||
"welcomepage":{
|
||||
"accessibilityLabel": {
|
||||
@@ -57,13 +56,10 @@
|
||||
"video": "Video"
|
||||
},
|
||||
"calendar": "Calendar",
|
||||
"connectCalendarText": "Connect your calendar to view all your meetings in __app__. Plus, add __app__ meetings to your calendar and start them with one click.",
|
||||
"connectCalendarButton": "Connect your calendar",
|
||||
"go": "GO",
|
||||
"join": "JOIN",
|
||||
"privacy": "Privacy",
|
||||
"recentList": "Recent",
|
||||
"recentListEmpty": "Your recent list is currently empty. Chat with your team and you will find all your recent meetings here.",
|
||||
"recentList": "History",
|
||||
"roomname": "Enter room name",
|
||||
"roomnameHint": "Enter the name or URL of the room you want to join. You may make a name up, just let the people you are meeting know it so that they enter the same name.",
|
||||
"sendFeedback": "Send feedback",
|
||||
@@ -91,7 +87,6 @@
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hangup": "Leave the call",
|
||||
"invite": "Invite people",
|
||||
"localRecording": "Toggle local recording controls",
|
||||
"lockRoom": "Toggle room lock",
|
||||
"moreActions": "Toggle more actions menu",
|
||||
"moreActionsMenu": "More actions menu",
|
||||
@@ -107,7 +102,6 @@
|
||||
"shortcuts": "Toggle shortcuts",
|
||||
"speakerStats": "Toggle speaker statistics",
|
||||
"toggleCamera": "Toggle camera",
|
||||
"tileView": "Toggle tile view",
|
||||
"videomute": "Toggle mute video"
|
||||
},
|
||||
"addPeople": "Add people to your call",
|
||||
@@ -150,7 +144,6 @@
|
||||
"raiseHand": "Raise / Lower your hand",
|
||||
"shortcuts": "View shortcuts",
|
||||
"speakerStats": "Speaker stats",
|
||||
"tileViewToggle": "Toggle tile view",
|
||||
"invite": "Invite people"
|
||||
},
|
||||
"chat":{
|
||||
@@ -160,14 +153,8 @@
|
||||
},
|
||||
"messagebox": "Enter text..."
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "The __appName__ calendar integration is used to securely access your calendar so it can read upcoming events.",
|
||||
"disconnect": "Disconnect",
|
||||
"microsoftSignIn": "Sign in with Microsoft",
|
||||
"signedIn": "Currently accessing calendar events for __email__. Click the Disconnect button below to stop accessing calendar events.",
|
||||
"title": "Calendar"
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
"title": "Settings",
|
||||
"update": "Update",
|
||||
"name": "Name",
|
||||
@@ -211,7 +198,6 @@
|
||||
"packetloss": "Packet loss:",
|
||||
"resolution": "Resolution:",
|
||||
"framerate": "Frame rate:",
|
||||
"e2e_rtt": "E2E RTT:",
|
||||
"less": "Show less",
|
||||
"more": "Show more",
|
||||
"address": "Address:",
|
||||
@@ -262,7 +248,6 @@
|
||||
"allow": "Allow",
|
||||
"confirm": "Confirm",
|
||||
"kickMessage": "Ouch! You have been kicked out of the meet!",
|
||||
"kickTitle": "Kicked from meeting",
|
||||
"popupErrorTitle": "Pop-up blocked",
|
||||
"popupError": "Your browser is blocking pop-up windows from this site. Please enable pop-ups in your browser's security settings and try again.",
|
||||
"passwordErrorTitle": "Password Error",
|
||||
@@ -428,11 +413,6 @@
|
||||
],
|
||||
"and": "and"
|
||||
},
|
||||
"share":
|
||||
{
|
||||
"mainText": "Click the following link to join the meeting:\n__roomUrl__",
|
||||
"dialInfoText": "\n\n=====\n\nJust want to dial in on your phone?\n\n__defaultDialInNumber__Click this link to see the dial in phone numbers for this meeting\n__dialInfoPageUrl__"
|
||||
},
|
||||
"connection":
|
||||
{
|
||||
"ERROR": "Error",
|
||||
@@ -465,13 +445,7 @@
|
||||
"on": "Recording",
|
||||
"pending": "Preparing to record the meeting...",
|
||||
"rec": "REC",
|
||||
"authDropboxText": "Upload your recording to Dropbox.",
|
||||
"authDropboxCompletedText": "Your recording file will appear in your Dropbox shortly after the recording has finished.",
|
||||
"serviceName": "Recording service",
|
||||
"signOut": "Sign Out",
|
||||
"signIn": "sign in",
|
||||
"loggedIn": "Logged in as __userName__",
|
||||
"availableSpace": "Available space: __spaceLeft__ MB (approximately __duration__ minutes of recording)",
|
||||
"startRecordingBody": "Are you sure you would like to start recording?",
|
||||
"unavailable": "Oops! The __serviceName__ is currently unavailable. We're working on resolving the issue. Please try again later.",
|
||||
"unavailableTitle": "Recording unavailable"
|
||||
@@ -484,9 +458,7 @@
|
||||
"failedToStart": "Transcribing failed to start",
|
||||
"tr": "TR",
|
||||
"labelToolTip": "The meeting is being transcribed",
|
||||
"ccButtonTooltip": "Start / Stop showing subtitles",
|
||||
"start": "Start showing subtitles",
|
||||
"stop": "Stop showing subtitles"
|
||||
"ccButtonTooltip": "Start / Stop showing subtitles"
|
||||
},
|
||||
"liveStreaming":
|
||||
{
|
||||
@@ -505,9 +477,7 @@
|
||||
"on": "Live Streaming",
|
||||
"pending": "Starting Live Stream...",
|
||||
"serviceName": "Live Streaming service",
|
||||
"signedInAs": "You are currently signed in as:",
|
||||
"signIn": "Sign in with Google",
|
||||
"signOut": "Sign out",
|
||||
"signInCTA": "Sign in or enter your live stream key from YouTube.",
|
||||
"start": "Start a live stream",
|
||||
"streamIdHelp": "What's this?",
|
||||
@@ -647,21 +617,16 @@
|
||||
"startWithVideoMuted": "Start with video muted"
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Add a meeting link",
|
||||
"confirmAddLink": "Do you want to add a Jitsi link to this event?",
|
||||
"confirmAddLinkTitle": "Calendar",
|
||||
"join": "Join",
|
||||
"joinTooltip": "Join the meeting",
|
||||
"later": "Later",
|
||||
"next": "Upcoming",
|
||||
"nextMeeting": "next meeting",
|
||||
"noEvents": "There are no upcoming events scheduled.",
|
||||
"now": "Now",
|
||||
"ongoingMeeting": "ongoing meeting",
|
||||
"permissionButton": "Open settings",
|
||||
"permissionMessage": "The Calendar permission is required to see your meetings in the app.",
|
||||
"refresh": "Refresh calendar",
|
||||
"today": "Today"
|
||||
"permissionMessage": "The Calendar permission is required to see your meetings in the app."
|
||||
},
|
||||
"recentList": {
|
||||
"joinPastMeeting": "Join a past meeting"
|
||||
"joinPastMeeting": "Join A Past Meeting"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Pull to refresh"
|
||||
@@ -700,34 +665,5 @@
|
||||
"decline": "Dismiss",
|
||||
"productLabel": "from Jitsi Meet",
|
||||
"videoCallTitle": "Incoming video call"
|
||||
},
|
||||
"localRecording": {
|
||||
"localRecording": "Local Recording",
|
||||
"dialogTitle": "Local Recording Controls",
|
||||
"start": "Start Recording",
|
||||
"stop": "Stop Recording",
|
||||
"moderator": "Moderator",
|
||||
"me": "Me",
|
||||
"duration": "Duration",
|
||||
"durationNA": "N/A",
|
||||
"encoding": "Encoding",
|
||||
"participantStats": "Participant Stats",
|
||||
"participant": "Participant",
|
||||
"sessionToken": "Session Token",
|
||||
"clientState": {
|
||||
"on": "On",
|
||||
"off": "Off",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"messages": {
|
||||
"engaged": "Local recording engaged.",
|
||||
"finished": "Recording session __token__ finished. Please send the recorded file to the moderator.",
|
||||
"finishedModerator": "Recording session __token__ finished. The recording of the local track has been saved. Please ask the other participants to submit their recordings.",
|
||||
"notModerator": "You are not the moderator. You cannot start or stop local recording."
|
||||
},
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"label": "LOR",
|
||||
"labelToolTip": "Local recording is engaged"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
getPinnedParticipant,
|
||||
pinParticipant
|
||||
} from '../react/features/base/participants';
|
||||
import { setTileView } from '../react/features/video-layout';
|
||||
import UIEvents from '../service/UI/UIEvents';
|
||||
import VideoLayout from './UI/videolayout/VideoLayout';
|
||||
|
||||
@@ -118,31 +117,6 @@ class State {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for this object instance to know the state of tile view.
|
||||
*
|
||||
* @returns {boolean} True if tile view is enabled.
|
||||
*/
|
||||
get tileViewEnabled() {
|
||||
return this._tileViewEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter for {@link tileViewEnabled}. Fires a property change event for
|
||||
* other participants to follow.
|
||||
*
|
||||
* @param {boolean} b - Whether or not tile view is enabled.
|
||||
* @returns {void}
|
||||
*/
|
||||
set tileViewEnabled(b) {
|
||||
const oldValue = this._tileViewEnabled;
|
||||
|
||||
if (oldValue !== b) {
|
||||
this._tileViewEnabled = b;
|
||||
this._firePropertyChange('tileViewEnabled', oldValue, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {_propertyChangeCallback} to notify it that {property} had its
|
||||
* value changed from {oldValue} to {newValue}.
|
||||
@@ -215,10 +189,6 @@ class FollowMe {
|
||||
this._sharedDocumentToggled
|
||||
.bind(this, this._UI.getSharedDocumentManager().isVisible());
|
||||
}
|
||||
|
||||
this._tileViewToggled.bind(
|
||||
this,
|
||||
APP.store.getState()['features/video-layout'].tileViewEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,10 +214,6 @@ class FollowMe {
|
||||
this.sharedDocEventHandler = this._sharedDocumentToggled.bind(this);
|
||||
this._UI.addListener(UIEvents.TOGGLED_SHARED_DOCUMENT,
|
||||
this.sharedDocEventHandler);
|
||||
|
||||
this.tileViewEventHandler = this._tileViewToggled.bind(this);
|
||||
this._UI.addListener(UIEvents.TOGGLED_TILE_VIEW,
|
||||
this.tileViewEventHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,8 +227,6 @@ class FollowMe {
|
||||
this.sharedDocEventHandler);
|
||||
this._UI.removeListener(UIEvents.PINNED_ENDPOINT,
|
||||
this.pinnedEndpointEventHandler);
|
||||
this._UI.removeListener(UIEvents.TOGGLED_TILE_VIEW,
|
||||
this.tileViewEventHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -302,18 +266,6 @@ class FollowMe {
|
||||
this._local.sharedDocumentVisible = sharedDocumentVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this instance that the tile view mode has been enabled or
|
||||
* disabled.
|
||||
*
|
||||
* @param {boolean} enabled - True if tile view has been enabled, false
|
||||
* if has been disabled.
|
||||
* @returns {void}
|
||||
*/
|
||||
_tileViewToggled(enabled) {
|
||||
this._local.tileViewEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the nextOnStage property value.
|
||||
*
|
||||
@@ -364,8 +316,7 @@ class FollowMe {
|
||||
attributes: {
|
||||
filmstripVisible: local.filmstripVisible,
|
||||
nextOnStage: local.nextOnStage,
|
||||
sharedDocumentVisible: local.sharedDocumentVisible,
|
||||
tileViewEnabled: local.tileViewEnabled
|
||||
sharedDocumentVisible: local.sharedDocumentVisible
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -404,7 +355,6 @@ class FollowMe {
|
||||
this._onFilmstripVisible(attributes.filmstripVisible);
|
||||
this._onNextOnStage(attributes.nextOnStage);
|
||||
this._onSharedDocumentVisible(attributes.sharedDocumentVisible);
|
||||
this._onTileViewEnabled(attributes.tileViewEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -484,21 +434,6 @@ class FollowMe {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a tile view enabled / disabled event received from FOLLOW-ME.
|
||||
*
|
||||
* @param {boolean} enabled - Whether or not tile view should be shown.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onTileViewEnabled(enabled) {
|
||||
if (typeof enabled === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(setTileView(enabled === 'true'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pins / unpins the video thumbnail given by clickId.
|
||||
*
|
||||
|
||||
@@ -157,7 +157,7 @@ UI.notifyKicked = function() {
|
||||
messageHandler.showError({
|
||||
hideErrorSupportLink: true,
|
||||
descriptionKey: 'dialog.kickMessage',
|
||||
titleKey: 'dialog.kickTitle'
|
||||
titleKey: 'dialog.sessTerminated'
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* global $ */
|
||||
|
||||
import SmallVideo from '../videolayout/SmallVideo';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
/* global $, APP, interfaceConfig */
|
||||
|
||||
import { setFilmstripVisible } from '../../../react/features/filmstrip';
|
||||
import {
|
||||
LAYOUTS,
|
||||
getCurrentLayout,
|
||||
getMaxColumnCount,
|
||||
getTileViewGridDimensions,
|
||||
shouldDisplayTileView
|
||||
} from '../../../react/features/video-layout';
|
||||
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
@@ -240,10 +233,6 @@ const Filmstrip = {
|
||||
* @returns {*|{localVideo, remoteVideo}}
|
||||
*/
|
||||
calculateThumbnailSize() {
|
||||
if (shouldDisplayTileView(APP.store.getState())) {
|
||||
return this._calculateThumbnailSizeForTileView();
|
||||
}
|
||||
|
||||
const availableSizes = this.calculateAvailableSize();
|
||||
const width = availableSizes.availableWidth;
|
||||
const height = availableSizes.availableHeight;
|
||||
@@ -258,10 +247,11 @@ const Filmstrip = {
|
||||
* @returns {{availableWidth: number, availableHeight: number}}
|
||||
*/
|
||||
calculateAvailableSize() {
|
||||
const state = APP.store.getState();
|
||||
const currentLayout = getCurrentLayout(state);
|
||||
const isHorizontalFilmstripView
|
||||
= currentLayout === LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW;
|
||||
let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT;
|
||||
const thumbs = this.getThumbs(true);
|
||||
const numvids = thumbs.remoteThumbs.length;
|
||||
|
||||
const localVideoContainer = $('#localVideoContainer');
|
||||
|
||||
/**
|
||||
* If the videoAreaAvailableWidth is set we use this one to calculate
|
||||
@@ -278,15 +268,10 @@ const Filmstrip = {
|
||||
- UIUtil.parseCssInt(this.filmstrip.css('borderRightWidth'), 10)
|
||||
- 5;
|
||||
|
||||
let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT;
|
||||
let availableWidth = videoAreaAvailableWidth;
|
||||
|
||||
const thumbs = this.getThumbs(true);
|
||||
|
||||
// If local thumb is not hidden
|
||||
if (thumbs.localThumb) {
|
||||
const localVideoContainer = $('#localVideoContainer');
|
||||
|
||||
availableWidth = Math.floor(
|
||||
videoAreaAvailableWidth - (
|
||||
UIUtil.parseCssInt(
|
||||
@@ -304,12 +289,10 @@ const Filmstrip = {
|
||||
);
|
||||
}
|
||||
|
||||
// If the number of videos is 0 or undefined or we're not in horizontal
|
||||
// If the number of videos is 0 or undefined or we're in vertical
|
||||
// filmstrip mode we don't need to calculate further any adjustments
|
||||
// to width based on the number of videos present.
|
||||
const numvids = thumbs.remoteThumbs.length;
|
||||
|
||||
if (numvids && isHorizontalFilmstripView) {
|
||||
if (numvids && !interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||
const remoteVideoContainer = thumbs.remoteThumbs.eq(0);
|
||||
|
||||
availableWidth = Math.floor(
|
||||
@@ -339,10 +322,8 @@ const Filmstrip = {
|
||||
availableHeight
|
||||
= Math.min(maxHeight, window.innerHeight - 18);
|
||||
|
||||
return {
|
||||
availableHeight,
|
||||
availableWidth
|
||||
};
|
||||
return { availableWidth,
|
||||
availableHeight };
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -453,51 +434,6 @@ const Filmstrip = {
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculates the size for thumbnails when in tile view layout.
|
||||
*
|
||||
* @returns {{localVideo, remoteVideo}}
|
||||
*/
|
||||
_calculateThumbnailSizeForTileView() {
|
||||
const tileAspectRatio = 16 / 9;
|
||||
|
||||
// The distance from the top and bottom of the screen, as set by CSS, to
|
||||
// avoid overlapping UI elements.
|
||||
const topBottomPadding = 200;
|
||||
|
||||
// Minimum space to keep between the sides of the tiles and the sides
|
||||
// of the window.
|
||||
const sideMargins = 30 * 2;
|
||||
|
||||
const state = APP.store.getState();
|
||||
|
||||
const viewWidth = document.body.clientWidth - sideMargins;
|
||||
const viewHeight = document.body.clientHeight - topBottomPadding;
|
||||
|
||||
const {
|
||||
columns,
|
||||
visibleRows
|
||||
} = getTileViewGridDimensions(state, getMaxColumnCount());
|
||||
const initialWidth = viewWidth / columns;
|
||||
const aspectRatioHeight = initialWidth / tileAspectRatio;
|
||||
|
||||
const heightOfEach = Math.min(
|
||||
aspectRatioHeight,
|
||||
viewHeight / visibleRows);
|
||||
const widthOfEach = tileAspectRatio * heightOfEach;
|
||||
|
||||
return {
|
||||
localVideo: {
|
||||
thumbWidth: widthOfEach,
|
||||
thumbHeight: heightOfEach
|
||||
},
|
||||
remoteVideo: {
|
||||
thumbWidth: widthOfEach,
|
||||
thumbHeight: heightOfEach
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Resizes thumbnails
|
||||
* @param local
|
||||
@@ -507,28 +443,6 @@ const Filmstrip = {
|
||||
*/
|
||||
// eslint-disable-next-line max-params
|
||||
resizeThumbnails(local, remote, forceUpdate = false) {
|
||||
const state = APP.store.getState();
|
||||
|
||||
if (shouldDisplayTileView(state)) {
|
||||
// The size of the side margins for each tile as set in CSS.
|
||||
const sideMargins = 10 * 2;
|
||||
const {
|
||||
columns,
|
||||
rows
|
||||
} = getTileViewGridDimensions(state, getMaxColumnCount());
|
||||
const hasOverflow = rows > columns;
|
||||
|
||||
// Width is set so that the flex layout can automatically wrap
|
||||
// tiles onto new rows.
|
||||
this.filmstripRemoteVideos.css({
|
||||
width: (local.thumbWidth * columns) + (columns * sideMargins)
|
||||
});
|
||||
|
||||
this.filmstripRemoteVideos.toggleClass('has-overflow', hasOverflow);
|
||||
} else {
|
||||
this.filmstripRemoteVideos.css('width', '');
|
||||
}
|
||||
|
||||
const thumbs = this.getThumbs(!forceUpdate);
|
||||
|
||||
if (thumbs.localThumb) {
|
||||
@@ -552,15 +466,13 @@ const Filmstrip = {
|
||||
});
|
||||
}
|
||||
|
||||
const currentLayout = getCurrentLayout(APP.store.getState());
|
||||
|
||||
// Let CSS take care of height in vertical filmstrip mode.
|
||||
if (currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
|
||||
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||
$('#filmstripLocalVideo').css({
|
||||
// adds 4 px because of small video 2px border
|
||||
width: `${local.thumbWidth + 4}px`
|
||||
});
|
||||
} else if (currentLayout === LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW) {
|
||||
} else {
|
||||
this.filmstrip.css({
|
||||
// adds 4 px because of small video 2px border
|
||||
height: `${remote.thumbHeight + 4}px`
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
getAvatarURLByParticipantId
|
||||
} from '../../../react/features/base/participants';
|
||||
import { updateSettings } from '../../../react/features/base/settings';
|
||||
import { shouldDisplayTileView } from '../../../react/features/video-layout';
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
@@ -27,7 +26,7 @@ function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
|
||||
this.streamEndedCallback = streamEndedCallback;
|
||||
this.container = this.createContainer();
|
||||
this.$container = $(this.container);
|
||||
this.updateDOMLocation();
|
||||
$('#filmstripLocalVideoThumbnail').append(this.container);
|
||||
|
||||
this.localVideoId = null;
|
||||
this.bindHoverHandler();
|
||||
@@ -110,7 +109,16 @@ LocalVideo.prototype.changeVideo = function(stream) {
|
||||
|
||||
this.localVideoId = `localVideo_${stream.getId()}`;
|
||||
|
||||
this._updateVideoElement();
|
||||
const localVideoContainer = document.getElementById('localVideoWrapper');
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store = { APP.store }>
|
||||
<VideoTrack
|
||||
id = { this.localVideoId }
|
||||
videoTrack = {{ jitsiTrack: stream }} />
|
||||
</Provider>,
|
||||
localVideoContainer
|
||||
);
|
||||
|
||||
// eslint-disable-next-line eqeqeq
|
||||
const isVideo = stream.videoType != 'desktop';
|
||||
@@ -120,14 +128,12 @@ LocalVideo.prototype.changeVideo = function(stream) {
|
||||
this.setFlipX(isVideo ? settings.localFlipX : false);
|
||||
|
||||
const endedHandler = () => {
|
||||
const localVideoContainer
|
||||
= document.getElementById('localVideoWrapper');
|
||||
|
||||
// Only remove if there is no video and not a transition state.
|
||||
// Previous non-react logic created a new video element with each track
|
||||
// removal whereas react reuses the video component so it could be the
|
||||
// stream ended but a new one is being used.
|
||||
if (localVideoContainer && this.videoStream.isEnded()) {
|
||||
if (this.videoStream.isEnded()) {
|
||||
ReactDOM.unmountComponentAtNode(localVideoContainer);
|
||||
}
|
||||
|
||||
@@ -229,29 +235,6 @@ LocalVideo.prototype._enableDisableContextMenu = function(enable) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Places the {@code LocalVideo} in the DOM based on the current video layout.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
LocalVideo.prototype.updateDOMLocation = function() {
|
||||
if (!this.container) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.container.parentElement) {
|
||||
this.container.parentElement.removeChild(this.container);
|
||||
}
|
||||
|
||||
const appendTarget = shouldDisplayTileView(APP.store.getState())
|
||||
? document.getElementById('localVideoTileViewContainer')
|
||||
: document.getElementById('filmstripLocalVideoThumbnail');
|
||||
|
||||
appendTarget && appendTarget.appendChild(this.container);
|
||||
|
||||
this._updateVideoElement();
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback invoked when the thumbnail is clicked. Will directly call
|
||||
* VideoLayout to handle thumbnail click if certain elements have not been
|
||||
@@ -286,28 +269,4 @@ LocalVideo.prototype._onContainerClick = function(event) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the React Element for displaying video in {@code LocalVideo}.
|
||||
*
|
||||
*/
|
||||
LocalVideo.prototype._updateVideoElement = function() {
|
||||
const localVideoContainer = document.getElementById('localVideoWrapper');
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store = { APP.store }>
|
||||
<VideoTrack
|
||||
id = 'localVideo_container'
|
||||
videoTrack = {{ jitsiTrack: this.videoStream }} />
|
||||
</Provider>,
|
||||
localVideoContainer
|
||||
);
|
||||
|
||||
// Ensure the video gets play() called on it. This may be necessary in the
|
||||
// case where the local video container was moved and re-attached, in which
|
||||
// case video does not autoplay.
|
||||
const video = this.container.querySelector('video');
|
||||
|
||||
video && video.play();
|
||||
};
|
||||
|
||||
export default LocalVideo;
|
||||
|
||||
@@ -20,11 +20,6 @@ import {
|
||||
REMOTE_CONTROL_MENU_STATES,
|
||||
RemoteVideoMenuTriggerButton
|
||||
} from '../../../react/features/remote-video-menu';
|
||||
import {
|
||||
LAYOUTS,
|
||||
getCurrentLayout,
|
||||
shouldDisplayTileView
|
||||
} from '../../../react/features/video-layout';
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
@@ -168,17 +163,8 @@ RemoteVideo.prototype._generatePopupContent = function() {
|
||||
const onVolumeChange = this._setAudioVolume;
|
||||
const { isModerator } = APP.conference;
|
||||
const participantID = this.id;
|
||||
|
||||
const currentLayout = getCurrentLayout(APP.store.getState());
|
||||
let remoteMenuPosition;
|
||||
|
||||
if (currentLayout === LAYOUTS.TILE_VIEW) {
|
||||
remoteMenuPosition = 'left top';
|
||||
} else if (currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
|
||||
remoteMenuPosition = 'left bottom';
|
||||
} else {
|
||||
remoteMenuPosition = 'top center';
|
||||
}
|
||||
const menuPosition = interfaceConfig.VERTICAL_FILMSTRIP
|
||||
? 'left bottom' : 'top center';
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store = { APP.store }>
|
||||
@@ -188,7 +174,7 @@ RemoteVideo.prototype._generatePopupContent = function() {
|
||||
initialVolumeValue = { initialVolumeValue }
|
||||
isAudioMuted = { this.isAudioMuted }
|
||||
isModerator = { isModerator }
|
||||
menuPosition = { remoteMenuPosition }
|
||||
menuPosition = { menuPosition }
|
||||
onMenuDisplay
|
||||
= {this._onRemoteVideoMenuDisplay.bind(this)}
|
||||
onRemoteControlToggle = { onRemoteControlToggle }
|
||||
|
||||
@@ -27,12 +27,6 @@ import {
|
||||
RaisedHandIndicator,
|
||||
VideoMutedIndicator
|
||||
} from '../../../react/features/filmstrip';
|
||||
import {
|
||||
LAYOUTS,
|
||||
getCurrentLayout,
|
||||
setTileView,
|
||||
shouldDisplayTileView
|
||||
} from '../../../react/features/video-layout';
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
@@ -334,21 +328,7 @@ SmallVideo.prototype.setVideoMutedView = function(isMuted) {
|
||||
SmallVideo.prototype.updateStatusBar = function() {
|
||||
const statusBarContainer
|
||||
= this.container.querySelector('.videocontainer__toolbar');
|
||||
|
||||
if (!statusBarContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentLayout = getCurrentLayout(APP.store.getState());
|
||||
let tooltipPosition;
|
||||
|
||||
if (currentLayout === LAYOUTS.TILE_VIEW) {
|
||||
tooltipPosition = 'right';
|
||||
} else if (currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
|
||||
tooltipPosition = 'left';
|
||||
} else {
|
||||
tooltipPosition = 'top';
|
||||
}
|
||||
const tooltipPosition = interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top';
|
||||
|
||||
ReactDOM.render(
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
@@ -567,8 +547,7 @@ SmallVideo.prototype.isVideoPlayable = function() {
|
||||
*/
|
||||
SmallVideo.prototype.selectDisplayMode = function() {
|
||||
// Display name is always and only displayed when user is on the stage
|
||||
if (this.isCurrentlyOnLargeVideo()
|
||||
&& !shouldDisplayTileView(APP.store.getState())) {
|
||||
if (this.isCurrentlyOnLargeVideo()) {
|
||||
return this.isVideoPlayable() && !APP.conference.isAudioOnly()
|
||||
? DISPLAY_BLACKNESS_WITH_NAME : DISPLAY_AVATAR_WITH_NAME;
|
||||
} else if (this.isVideoPlayable()
|
||||
@@ -706,10 +685,7 @@ SmallVideo.prototype.showDominantSpeakerIndicator = function(show) {
|
||||
|
||||
this._showDominantSpeaker = show;
|
||||
|
||||
this.$container.toggleClass('active-speaker', this._showDominantSpeaker);
|
||||
|
||||
this.updateIndicators();
|
||||
this.updateView();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -789,18 +765,6 @@ SmallVideo.prototype.initBrowserSpecificProperties = function() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function for re-rendering multiple react components of the small
|
||||
* video.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.rerender = function() {
|
||||
this.updateIndicators();
|
||||
this.updateStatusBar();
|
||||
this.updateView();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the React element responsible for showing connection status, dominant
|
||||
* speaker, and raised hand icons. Uses instance variables to get the necessary
|
||||
@@ -820,19 +784,7 @@ SmallVideo.prototype.updateIndicators = function() {
|
||||
const iconSize = UIUtil.getIndicatorFontSize();
|
||||
const showConnectionIndicator = this.videoIsHovered
|
||||
|| !interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED;
|
||||
const currentLayout = getCurrentLayout(APP.store.getState());
|
||||
let statsPopoverPosition, tooltipPosition;
|
||||
|
||||
if (currentLayout === LAYOUTS.TILE_VIEW) {
|
||||
statsPopoverPosition = 'right top';
|
||||
tooltipPosition = 'right';
|
||||
} else if (currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
|
||||
statsPopoverPosition = this.statsPopoverLocation;
|
||||
tooltipPosition = 'left';
|
||||
} else {
|
||||
statsPopoverPosition = this.statsPopoverLocation;
|
||||
tooltipPosition = 'top';
|
||||
}
|
||||
const tooltipPosition = interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top';
|
||||
|
||||
ReactDOM.render(
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
@@ -847,7 +799,7 @@ SmallVideo.prototype.updateIndicators = function() {
|
||||
enableStatsDisplay
|
||||
= { !interfaceConfig.filmStripOnly }
|
||||
statsPopoverPosition
|
||||
= { statsPopoverPosition }
|
||||
= { this.statsPopoverLocation }
|
||||
userID = { this.id } />
|
||||
: null }
|
||||
{ this._showRaisedHand
|
||||
|
||||
@@ -216,6 +216,8 @@ export class VideoContainer extends LargeContainer {
|
||||
this.emitter = emitter;
|
||||
this.resizeContainer = resizeContainer;
|
||||
|
||||
this.isVisible = false;
|
||||
|
||||
/**
|
||||
* Whether the background should fit the height of the container
|
||||
* (portrait) or fit the width of the container (landscape).
|
||||
@@ -601,11 +603,17 @@ export class VideoContainer extends LargeContainer {
|
||||
* TODO: refactor this since Temasys is no longer supported.
|
||||
*/
|
||||
show() {
|
||||
// its already visible
|
||||
if (this.isVisible) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
this.$wrapperParent.css('visibility', 'visible').fadeTo(
|
||||
FADE_DURATION_MS,
|
||||
1,
|
||||
() => {
|
||||
this.isVisible = true;
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
@@ -620,9 +628,15 @@ export class VideoContainer extends LargeContainer {
|
||||
// hide its avatar
|
||||
this.showAvatar(false);
|
||||
|
||||
// its already hidden
|
||||
if (!this.isVisible) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
this.$wrapperParent.fadeTo(FADE_DURATION_MS, 0, () => {
|
||||
this.$wrapperParent.css('visibility', 'hidden');
|
||||
this.isVisible = false;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
/* global APP, $, interfaceConfig */
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
import {
|
||||
getNearestReceiverVideoQualityLevel,
|
||||
setMaxReceiverVideoQuality
|
||||
} from '../../../react/features/base/conference';
|
||||
import {
|
||||
JitsiParticipantConnectionStatus
|
||||
} from '../../../react/features/base/lib-jitsi-meet';
|
||||
@@ -13,9 +9,6 @@ import {
|
||||
getPinnedParticipant,
|
||||
pinParticipant
|
||||
} from '../../../react/features/base/participants';
|
||||
import {
|
||||
shouldDisplayTileView
|
||||
} from '../../../react/features/video-layout';
|
||||
import { SHARED_VIDEO_CONTAINER_TYPE } from '../shared_video/SharedVideo';
|
||||
import SharedVideoThumb from '../shared_video/SharedVideoThumb';
|
||||
|
||||
@@ -601,19 +594,12 @@ const VideoLayout = {
|
||||
|
||||
Filmstrip.resizeThumbnails(localVideo, remoteVideo, forceUpdate);
|
||||
|
||||
if (shouldDisplayTileView(APP.store.getState())) {
|
||||
const height
|
||||
= (localVideo && localVideo.thumbHeight)
|
||||
|| (remoteVideo && remoteVideo.thumbnHeight)
|
||||
|| 0;
|
||||
const qualityLevel = getNearestReceiverVideoQualityLevel(height);
|
||||
|
||||
APP.store.dispatch(setMaxReceiverVideoQuality(qualityLevel));
|
||||
}
|
||||
|
||||
if (onComplete && typeof onComplete === 'function') {
|
||||
onComplete();
|
||||
}
|
||||
|
||||
return { localVideo,
|
||||
remoteVideo };
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1156,22 +1142,6 @@ const VideoLayout = {
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper method to invoke when the video layout has changed and elements
|
||||
* have to be re-arranged and resized.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
refreshLayout() {
|
||||
localVideoThumbnail && localVideoThumbnail.updateDOMLocation();
|
||||
VideoLayout.resizeVideoArea();
|
||||
|
||||
localVideoThumbnail && localVideoThumbnail.rerender();
|
||||
Object.values(remoteVideos).forEach(
|
||||
remoteVideo => remoteVideo.rerender()
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggers an update of large video if the passed in participant is
|
||||
* currently displayed on large video.
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Manages a queue of functions where the current function in progress will
|
||||
* automatically execute the next queued function.
|
||||
*/
|
||||
export class TaskQueue {
|
||||
/**
|
||||
* Creates a new instance of {@link TaskQueue} and sets initial instance
|
||||
* variable values.
|
||||
*/
|
||||
constructor() {
|
||||
this._queue = [];
|
||||
this._currentTask = null;
|
||||
|
||||
this._onTaskComplete = this._onTaskComplete.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new function to the queue. It will be immediately invoked if no
|
||||
* other functions are queued.
|
||||
*
|
||||
* @param {Function} taskFunction - The function to be queued for execution.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
enqueue(taskFunction) {
|
||||
this._queue.push(taskFunction);
|
||||
this._executeNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* If no queued task is currently executing, invokes the first task in the
|
||||
* queue if any.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_executeNext() {
|
||||
if (this._currentTask) {
|
||||
logger.warn('Task queued while a task is in progress.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentTask = this._queue.shift() || null;
|
||||
|
||||
if (this._currentTask) {
|
||||
this._currentTask(this._onTaskComplete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares to invoke the next function in the queue.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onTaskComplete() {
|
||||
this._currentTask = null;
|
||||
this._executeNext();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { TaskQueue } from './TaskQueue';
|
||||
|
||||
/**
|
||||
* Create deferred object.
|
||||
*
|
||||
@@ -15,12 +13,3 @@ export function createDeferred() {
|
||||
|
||||
return deferred;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of {@link TaskQueue}.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function createTaskQueue() {
|
||||
return new TaskQueue();
|
||||
}
|
||||
|
||||
1514
package-lock.json
generated
1514
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
47
package.json
47
package.json
@@ -15,28 +15,27 @@
|
||||
"author": "",
|
||||
"readmeFilename": "README.md",
|
||||
"dependencies": {
|
||||
"@atlaskit/avatar": "14.0.10",
|
||||
"@atlaskit/button": "9.0.8",
|
||||
"@atlaskit/checkbox": "4.0.6",
|
||||
"@atlaskit/avatar": "8.0.5",
|
||||
"@atlaskit/button": "5.4.2",
|
||||
"@atlaskit/checkbox": "2.0.2",
|
||||
"@atlaskit/dropdown-menu": "3.10.2",
|
||||
"@atlaskit/field-text": "7.0.10",
|
||||
"@atlaskit/field-text-area": "4.0.9",
|
||||
"@atlaskit/droplist": "4.11.1",
|
||||
"@atlaskit/field-text": "4.0.1",
|
||||
"@atlaskit/field-text-area": "1.2.0",
|
||||
"@atlaskit/flag": "6.1.0",
|
||||
"@atlaskit/icon": "13.8.1",
|
||||
"@atlaskit/icon": "10.0.0",
|
||||
"@atlaskit/inline-dialog": "5.3.0",
|
||||
"@atlaskit/inline-message": "7.0.4",
|
||||
"@atlaskit/layer-manager": "5.0.12",
|
||||
"@atlaskit/lozenge": "6.2.0",
|
||||
"@atlaskit/modal-dialog": "6.0.12",
|
||||
"@atlaskit/multi-select": "11.0.6",
|
||||
"@atlaskit/spinner": "9.0.8",
|
||||
"@atlaskit/inline-message": "4.0.0",
|
||||
"@atlaskit/layer-manager": "2.8.0",
|
||||
"@atlaskit/lozenge": "3.4.2",
|
||||
"@atlaskit/modal-dialog": "3.4.0",
|
||||
"@atlaskit/multi-select": "7.1.3",
|
||||
"@atlaskit/spinner": "4.0.0",
|
||||
"@atlaskit/tabs": "4.0.1",
|
||||
"@atlaskit/theme": "6.0.2",
|
||||
"@atlaskit/tooltip": "12.0.14",
|
||||
"@microsoft/microsoft-graph-client": "1.1.0",
|
||||
"@atlaskit/theme": "2.4.0",
|
||||
"@atlaskit/tooltip": "9.1.1",
|
||||
"@webcomponents/url": "0.7.1",
|
||||
"autosize": "1.18.13",
|
||||
"dropbox": "4.0.9",
|
||||
"i18next": "8.4.3",
|
||||
"i18next-browser-languagedetector": "2.0.0",
|
||||
"i18next-xhr-backend": "1.4.2",
|
||||
@@ -47,10 +46,8 @@
|
||||
"jquery-i18next": "1.2.0",
|
||||
"js-md5": "0.6.1",
|
||||
"jsc-android": "224109.1.0",
|
||||
"jsrsasign": "8.0.12",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#3f46c64f4f373d3b573fcc55b59568dbe9b9d51f",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#2be752fc88ff71e454c6b9178b21a33b59c53f41",
|
||||
"lodash": "4.17.4",
|
||||
"moment": "2.19.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -61,18 +58,18 @@
|
||||
"react-i18next": "4.8.0",
|
||||
"react-native": "0.55.4",
|
||||
"react-native-background-timer": "2.0.0",
|
||||
"react-native-calendar-events": "github:wmcmahan/react-native-calendar-events#cb2731db6684a49b4343e09de7f9c2fcc68bcd9b",
|
||||
"react-native-callstats": "3.53.4",
|
||||
"react-native-fast-image": "github:jitsi/react-native-fast-image#1f8c93a5584869848d75cc9b946beb9688efe285",
|
||||
"react-native-google-signin": "1.0.0-rc3",
|
||||
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#cad37355f36d17587d84af72b0095e8cc5fd3df9",
|
||||
"react-native-callstats": "3.52.0",
|
||||
"react-native-fast-image": "4.0.14",
|
||||
"react-native-immersive": "1.1.0",
|
||||
"react-native-keep-awake": "2.0.6",
|
||||
"react-native-linear-gradient": "2.4.0",
|
||||
"react-native-locale-detector": "github:jitsi/react-native-locale-detector#845281e9fd4af756f6d0f64afe5cce08c63e5ee9",
|
||||
"react-native-permissions": "1.1.1",
|
||||
"react-native-prompt": "1.0.0",
|
||||
"react-native-sound": "0.10.9",
|
||||
"react-native-vector-icons": "4.4.2",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#be3de15bb988cfabbb62cd4b3b06f4c920ee5ba0",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#6b0ea124414f6f5b7f234a7d5cec75d30f5f6312",
|
||||
"react-redux": "5.0.7",
|
||||
"redux": "4.0.0",
|
||||
"redux-thunk": "2.2.0",
|
||||
@@ -90,7 +87,7 @@
|
||||
"clean-css": "3.4.25",
|
||||
"css-loader": "0.28.7",
|
||||
"eslint": "4.12.1",
|
||||
"eslint-config-jitsi": "github:jitsi/eslint-config-jitsi#7474f6668515eb5852f1273dc5a50b940a550d3f",
|
||||
"eslint-config-jitsi": "github:jitsi/eslint-config-jitsi#v0.1.0",
|
||||
"eslint-plugin-flowtype": "2.39.1",
|
||||
"eslint-plugin-import": "2.8.0",
|
||||
"eslint-plugin-jsdoc": "3.2.0",
|
||||
|
||||
@@ -113,95 +113,6 @@ export function createConnectionEvent(action, attributes = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which indicates an action occurred in the calendar
|
||||
* integration UI.
|
||||
*
|
||||
* @param {string} eventName - The name of the calendar UI event.
|
||||
* @param {Object} attributes - Attributes to attach to the event.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createCalendarClickedEvent(eventName, attributes = {}) {
|
||||
return {
|
||||
action: 'clicked',
|
||||
actionSubject: eventName,
|
||||
attributes,
|
||||
source: 'calendar',
|
||||
type: TYPE_UI
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which indicates that the calendar container is shown and
|
||||
* selected.
|
||||
*
|
||||
* @param {Object} attributes - Attributes to attach to the event.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createCalendarSelectedEvent(attributes = {}) {
|
||||
return {
|
||||
action: 'selected',
|
||||
actionSubject: 'calendar.selected',
|
||||
attributes,
|
||||
source: 'calendar',
|
||||
type: TYPE_UI
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event indicating that a calendar has been connected.
|
||||
*
|
||||
* @param {boolean} attributes - Additional attributes to attach to the event.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createCalendarConnectedEvent(attributes = {}) {
|
||||
return {
|
||||
action: 'calendar.connected',
|
||||
actionSubject: 'calendar.connected',
|
||||
attributes
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which indicates an action occurred in the recent list
|
||||
* integration UI.
|
||||
*
|
||||
* @param {string} eventName - The name of the recent list UI event.
|
||||
* @param {Object} attributes - Attributes to attach to the event.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createRecentClickedEvent(eventName, attributes = {}) {
|
||||
return {
|
||||
action: 'clicked',
|
||||
actionSubject: eventName,
|
||||
attributes,
|
||||
source: 'recent.list',
|
||||
type: TYPE_UI
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which indicates that the recent list container is shown and
|
||||
* selected.
|
||||
*
|
||||
* @param {Object} attributes - Attributes to attach to the event.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createRecentSelectedEvent(attributes = {}) {
|
||||
return {
|
||||
action: 'selected',
|
||||
actionSubject: 'recent.list.selected',
|
||||
attributes,
|
||||
source: 'recent.list',
|
||||
type: TYPE_UI
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event for an action on the deep linking page.
|
||||
*
|
||||
|
||||
@@ -124,8 +124,6 @@ function _appNavigateToOptionalLocation(
|
||||
// FIXME Turn location's host, hostname, and port properties into
|
||||
// setters in order to reduce the risks of inconsistent state.
|
||||
location.hostname = defaultLocation.hostname;
|
||||
location.pathname
|
||||
= defaultLocation.pathname + location.pathname.substr(1);
|
||||
location.port = defaultLocation.port;
|
||||
location.protocol = defaultLocation.protocol;
|
||||
} else {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Linking } from 'react-native';
|
||||
|
||||
import '../../analytics';
|
||||
import '../../authentication';
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import '../../base/jwt';
|
||||
import { Platform } from '../../base/react';
|
||||
import {
|
||||
@@ -181,17 +180,6 @@ export class App extends AbstractApp {
|
||||
_onLinkingURL({ url }) {
|
||||
super._openURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific dialog container.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderDialogContainer() {
|
||||
return (
|
||||
<DialogContainer />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
import React from 'react';
|
||||
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import '../../base/responsive-ui';
|
||||
import '../../chat';
|
||||
import '../../room-lock';
|
||||
@@ -40,17 +39,4 @@ export class App extends AbstractApp {
|
||||
</AtlasKitThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific dialog container.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderDialogContainer() {
|
||||
return (
|
||||
<AtlasKitThemeProvider mode = 'dark'>
|
||||
<DialogContainer />
|
||||
</AtlasKitThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,6 @@ export default class BaseApp extends Component<*, State> {
|
||||
{ this._createMainElement(component) }
|
||||
<SoundCollection />
|
||||
{ this._createExtraElement() }
|
||||
{ this._renderDialogContainer() }
|
||||
</Fragment>
|
||||
</Provider>
|
||||
</I18nextProvider>
|
||||
@@ -236,11 +235,4 @@ export default class BaseApp extends Component<*, State> {
|
||||
this.setState({ route }, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific dialog container.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderDialogContainer: () => React$Element<*>
|
||||
}
|
||||
|
||||
@@ -13,11 +13,12 @@ import {
|
||||
MAX_DISPLAY_NAME_LENGTH,
|
||||
dominantSpeakerChanged,
|
||||
participantConnectionStatusChanged,
|
||||
participantJoined,
|
||||
participantLeft,
|
||||
participantPresenceChanged,
|
||||
participantRoleChanged,
|
||||
participantUpdated
|
||||
} from '../participants';
|
||||
import { endpointMessageReceived } from '../../subtitles';
|
||||
import { getLocalTracks, trackAdded, trackRemoved } from '../tracks';
|
||||
import { getJitsiMeetGlobalNS } from '../util';
|
||||
|
||||
@@ -51,8 +52,6 @@ import {
|
||||
} from './constants';
|
||||
import {
|
||||
_addLocalTracksToConference,
|
||||
commonUserJoinedHandling,
|
||||
commonUserLeftHandling,
|
||||
getCurrentConference,
|
||||
sendLocalParticipant
|
||||
} from './functions';
|
||||
@@ -138,20 +137,24 @@ function _addConferenceListeners(conference, dispatch) {
|
||||
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
||||
id => dispatch(dominantSpeakerChanged(id, conference)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
||||
(...args) => dispatch(endpointMessageReceived(...args)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,
|
||||
(...args) => dispatch(participantConnectionStatusChanged(...args)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.USER_JOINED,
|
||||
(id, user) => commonUserJoinedHandling({ dispatch }, conference, user));
|
||||
(id, user) => !user.isHidden() && dispatch(participantJoined({
|
||||
botType: user.getBotType(),
|
||||
conference,
|
||||
id,
|
||||
name: user.getDisplayName(),
|
||||
presence: user.getStatus(),
|
||||
role: user.getRole()
|
||||
})));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.USER_LEFT,
|
||||
(id, user) => commonUserLeftHandling({ dispatch }, conference, user));
|
||||
(id, user) => !user.isHidden()
|
||||
&& dispatch(participantLeft(id, conference)));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.USER_ROLE_CHANGED,
|
||||
(...args) => dispatch(participantRoleChanged(...args)));
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
// @flow
|
||||
|
||||
import { JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
hiddenParticipantJoined,
|
||||
hiddenParticipantLeft,
|
||||
participantJoined,
|
||||
participantLeft
|
||||
} from '../participants';
|
||||
import { getLocalParticipant } from '../participants';
|
||||
import { toState } from '../redux';
|
||||
|
||||
import {
|
||||
AVATAR_ID_COMMAND,
|
||||
AVATAR_URL_COMMAND,
|
||||
EMAIL_COMMAND,
|
||||
JITSI_CONFERENCE_URL_KEY,
|
||||
VIDEO_QUALITY_LEVELS
|
||||
JITSI_CONFERENCE_URL_KEY
|
||||
} from './constants';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
@@ -50,62 +43,6 @@ export function _addLocalTracksToConference(
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic shared between web and RN which processes the {@code USER_JOINED}
|
||||
* conference event and dispatches either {@link participantJoined} or
|
||||
* {@link hiddenParticipantJoined}.
|
||||
*
|
||||
* @param {Object} store - The redux store.
|
||||
* @param {JitsiMeetConference} conference - The conference for which the
|
||||
* {@code USER_JOINED} event is being processed.
|
||||
* @param {JitsiParticipant} user - The user who has just joined.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function commonUserJoinedHandling(
|
||||
{ dispatch }: Object,
|
||||
conference: Object,
|
||||
user: Object) {
|
||||
const id = user.getId();
|
||||
const displayName = user.getDisplayName();
|
||||
|
||||
if (user.isHidden()) {
|
||||
dispatch(hiddenParticipantJoined(id, displayName));
|
||||
} else {
|
||||
dispatch(participantJoined({
|
||||
botType: user.getBotType(),
|
||||
conference,
|
||||
id,
|
||||
name: displayName,
|
||||
presence: user.getStatus(),
|
||||
role: user.getRole()
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic shared between web and RN which processes the {@code USER_LEFT}
|
||||
* conference event and dispatches either {@link participantLeft} or
|
||||
* {@link hiddenParticipantLeft}.
|
||||
*
|
||||
* @param {Object} store - The redux store.
|
||||
* @param {JitsiMeetConference} conference - The conference for which the
|
||||
* {@code USER_LEFT} event is being processed.
|
||||
* @param {JitsiParticipant} user - The user who has just left.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function commonUserLeftHandling(
|
||||
{ dispatch }: Object,
|
||||
conference: Object,
|
||||
user: Object) {
|
||||
const id = user.getId();
|
||||
|
||||
if (user.isHidden()) {
|
||||
dispatch(hiddenParticipantLeft(id));
|
||||
} else {
|
||||
dispatch(participantLeft(id, conference));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates a specific predicate for each {@link JitsiConference} known to the
|
||||
* redux state features/base/conference while it returns {@code true}.
|
||||
@@ -165,38 +102,6 @@ export function getCurrentConference(stateful: Function | Object) {
|
||||
: joining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the nearest match for the passed in {@link availableHeight} to am
|
||||
* enumerated value in {@code VIDEO_QUALITY_LEVELS}.
|
||||
*
|
||||
* @param {number} availableHeight - The height to which a matching video
|
||||
* quality level should be found.
|
||||
* @returns {number} The closest matching value from
|
||||
* {@code VIDEO_QUALITY_LEVELS}.
|
||||
*/
|
||||
export function getNearestReceiverVideoQualityLevel(availableHeight: number) {
|
||||
const qualityLevels = [
|
||||
VIDEO_QUALITY_LEVELS.HIGH,
|
||||
VIDEO_QUALITY_LEVELS.STANDARD,
|
||||
VIDEO_QUALITY_LEVELS.LOW
|
||||
];
|
||||
|
||||
let selectedLevel = qualityLevels[0];
|
||||
|
||||
for (let i = 1; i < qualityLevels.length; i++) {
|
||||
const previousValue = qualityLevels[i - 1];
|
||||
const currentValue = qualityLevels[i];
|
||||
const diffWithCurrent = Math.abs(availableHeight - currentValue);
|
||||
const diffWithPrevious = Math.abs(availableHeight - previousValue);
|
||||
|
||||
if (diffWithCurrent < diffWithPrevious) {
|
||||
selectedLevel = currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
return selectedLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an error thrown by the backend (i.e. lib-jitsi-meet) while
|
||||
* manipulating a conference participant (e.g. pin or select participant).
|
||||
|
||||
@@ -96,7 +96,6 @@ const WHITELISTED_KEYS = [
|
||||
'disableRtx',
|
||||
'disableSuspendVideo',
|
||||
'displayJids',
|
||||
'e2eping',
|
||||
'enableDisplayNameInStats',
|
||||
'enableLayerSuspension',
|
||||
'enableLipSync',
|
||||
|
||||
@@ -37,6 +37,9 @@ const INITIAL_RN_STATE = {
|
||||
// fastest to merely disable them.
|
||||
disableAudioLevels: true,
|
||||
|
||||
// FIXME flow complains about missing 'locationURL' missing in _setConfig
|
||||
locationURL: undefined,
|
||||
|
||||
p2p: {
|
||||
disableH264: false,
|
||||
preferH264: true
|
||||
@@ -126,8 +129,10 @@ function _setConfig(state, { config }) {
|
||||
|
||||
const newState = _.merge(
|
||||
{},
|
||||
config,
|
||||
{ error: undefined },
|
||||
config, {
|
||||
error: undefined,
|
||||
locationURL: state.locationURL
|
||||
},
|
||||
|
||||
// The config of _getInitialState() is meant to override the config
|
||||
// downloaded from the Jitsi Meet deployment because the former contains
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
getCurrentConference
|
||||
} from '../conference';
|
||||
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
|
||||
import { parseURIString } from '../util';
|
||||
import { parseStandardURIString } from '../util';
|
||||
|
||||
import {
|
||||
CONNECTION_DISCONNECTED,
|
||||
@@ -276,32 +276,18 @@ function _connectionWillConnect(connection) {
|
||||
* {@code JitsiConnection}.
|
||||
*/
|
||||
function _constructOptions(state) {
|
||||
// Deep clone the options to make sure we don't modify the object in the
|
||||
// redux store.
|
||||
const options = _.cloneDeep(state['features/base/config']);
|
||||
const defaultOptions = state['features/base/connection'].options;
|
||||
const options = _.merge(
|
||||
{},
|
||||
defaultOptions,
|
||||
|
||||
// Normalize the BOSH URL.
|
||||
// Lib-jitsi-meet wants the config passed in multiple places and here is
|
||||
// the latest one I have discovered.
|
||||
state['features/base/config'],
|
||||
);
|
||||
let { bosh } = options;
|
||||
|
||||
if (bosh) {
|
||||
if (bosh.startsWith('//')) {
|
||||
// By default our config.js doesn't include the protocol.
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
|
||||
bosh = `${locationURL.protocol}${bosh}`;
|
||||
} else if (bosh.startsWith('/')) {
|
||||
// Handle relative URLs, which won't work on mobile.
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const {
|
||||
protocol,
|
||||
hostname,
|
||||
contextRoot
|
||||
} = parseURIString(locationURL.href);
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
bosh = `${protocol}//${hostname}${contextRoot || '/'}${bosh.substr(1)}`;
|
||||
}
|
||||
|
||||
// Append room to the URL's search.
|
||||
const { room } = state['features/base/conference'];
|
||||
|
||||
@@ -310,6 +296,16 @@ function _constructOptions(state) {
|
||||
// not ignore case themselves.
|
||||
room && (bosh += `?room=${room.toLowerCase()}`);
|
||||
|
||||
// XXX By default, config.js does not add a protocol to the BOSH URL.
|
||||
// Which trips React Native. Make sure there is a protocol in order to
|
||||
// satisfy React Native.
|
||||
if (bosh !== defaultOptions.bosh
|
||||
&& !parseStandardURIString(bosh).protocol) {
|
||||
const { protocol } = parseStandardURIString(defaultOptions.bosh);
|
||||
|
||||
protocol && (bosh = protocol + bosh);
|
||||
}
|
||||
|
||||
options.bosh = bosh;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import { SET_ROOM } from '../conference';
|
||||
import { JitsiConnectionErrors } from '../lib-jitsi-meet';
|
||||
import { assign, set, ReducerRegistry } from '../redux';
|
||||
import { assign, ReducerRegistry } from '../redux';
|
||||
import { parseURIString } from '../util';
|
||||
|
||||
import {
|
||||
CONNECTION_DISCONNECTED,
|
||||
@@ -152,6 +153,50 @@ function _connectionWillConnect(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs options to be passed to the constructor of {@code JitsiConnection}
|
||||
* based on a specific location URL.
|
||||
*
|
||||
* @param {string} locationURL - The location URL with which the returned
|
||||
* options are to be constructed.
|
||||
* @private
|
||||
* @returns {Object} The options to be passed to the constructor of
|
||||
* {@code JitsiConnection} based on the location URL.
|
||||
*/
|
||||
function _constructOptions(locationURL: URL) {
|
||||
const locationURI = parseURIString(locationURL.href);
|
||||
|
||||
// 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
|
||||
// HTTP scheme for the BOSH URL with beta.meet.jit.si on mobile.
|
||||
let { protocol } = locationURI;
|
||||
const domain = locationURI.hostname;
|
||||
|
||||
if (!protocol && domain === 'beta.meet.jit.si') {
|
||||
const windowLocation = window.location;
|
||||
|
||||
windowLocation && (protocol = windowLocation.protocol);
|
||||
protocol || (protocol = 'http:');
|
||||
}
|
||||
|
||||
// Default to the HTTPS scheme for the BOSH URL.
|
||||
protocol || (protocol = 'https:');
|
||||
|
||||
return {
|
||||
bosh:
|
||||
`${String(protocol)}//${domain}${
|
||||
locationURI.contextRoot || '/'}http-bind`,
|
||||
hosts: {
|
||||
domain,
|
||||
|
||||
// Required by:
|
||||
// - lib-jitsi-meet/modules/xmpp/xmpp.js
|
||||
muc: `conference.${domain}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The current (similar to getCurrentConference in base/conference/functions.js)
|
||||
* connection which is {@code connection} or {@code connecting}.
|
||||
@@ -178,7 +223,10 @@ function _getCurrentConnection(baseConnectionState: Object): ?Object {
|
||||
function _setLocationURL(
|
||||
state: Object,
|
||||
{ locationURL }: { locationURL: ?URL }) {
|
||||
return set(state, 'locationURL', locationURL);
|
||||
return assign(state, {
|
||||
locationURL,
|
||||
options: locationURL ? _constructOptions(locationURL) : undefined
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -86,9 +86,10 @@ export default class AbstractDialog<P : Props, S : State>
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancel() {
|
||||
const { cancelDisabled = false, onCancel } = this.props;
|
||||
const { cancelDisabled, onCancel } = this.props;
|
||||
|
||||
if (!cancelDisabled && (!onCancel || onCancel())) {
|
||||
if ((typeof cancelDisabled === 'undefined' || !cancelDisabled)
|
||||
&& (!onCancel || onCancel())) {
|
||||
this._hide();
|
||||
}
|
||||
}
|
||||
@@ -108,9 +109,9 @@ export default class AbstractDialog<P : Props, S : State>
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSubmit(value: ?string) {
|
||||
const { okDisabled = false, onSubmit } = this.props;
|
||||
const { okDisabled, onSubmit } = this.props;
|
||||
|
||||
if (!okDisabled) {
|
||||
if (typeof okDisabled === 'undefined' || !okDisabled) {
|
||||
this.setState({ submitting: true });
|
||||
|
||||
// Invoke the React Compnent prop onSubmit if any.
|
||||
|
||||
@@ -113,7 +113,7 @@ class Dialog extends AbstractDialog<Props, State> {
|
||||
[_TAG_KEY]: _SUBMIT_TEXT_TAG_VALUE
|
||||
};
|
||||
|
||||
let el: ?React$Element<*> = (
|
||||
let el: ?React$Element<*> = ( // eslint-disable-line no-extra-parens
|
||||
<Prompt
|
||||
cancelButtonTextStyle = { cancelButtonTextStyle }
|
||||
cancelText = { t(cancelTitleKey) }
|
||||
|
||||
@@ -22,12 +22,7 @@ export class DialogContainer extends Component {
|
||||
/**
|
||||
* The props to pass to the component that will be rendered.
|
||||
*/
|
||||
_componentProps: PropTypes.object,
|
||||
|
||||
/**
|
||||
* True if the UI is in a compact state where we don't show dialogs.
|
||||
*/
|
||||
_reducedUI: PropTypes.bool
|
||||
_componentProps: PropTypes.object
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -37,13 +32,10 @@ export class DialogContainer extends Component {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_component: component,
|
||||
_reducedUI: reducedUI
|
||||
} = this.props;
|
||||
const { _component: component } = this.props;
|
||||
|
||||
return (
|
||||
component && !reducedUI
|
||||
component
|
||||
? React.createElement(component, this.props._componentProps)
|
||||
: null);
|
||||
}
|
||||
@@ -57,18 +49,15 @@ export class DialogContainer extends Component {
|
||||
* @private
|
||||
* @returns {{
|
||||
* _component: React.Component,
|
||||
* _componentProps: Object,
|
||||
* _reducedUI: boolean
|
||||
* _componentProps: Object
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const stateFeaturesBaseDialog = state['features/base/dialog'];
|
||||
const { reducedUI } = state['features/base/responsive-ui'];
|
||||
|
||||
return {
|
||||
_component: stateFeaturesBaseDialog.component,
|
||||
_componentProps: stateFeaturesBaseDialog.componentProps,
|
||||
_reducedUI: reducedUI
|
||||
_componentProps: stateFeaturesBaseDialog.componentProps
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { dialog as styles } from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Children of the component.
|
||||
*/
|
||||
children: string | React$Node
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic dialog content container to provide the same styling for all custom
|
||||
* dialogs.
|
||||
*/
|
||||
export default class DialogContent extends Component<Props> {
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
|
||||
const childrenComponent = typeof children === 'string'
|
||||
? <Text>{ children }</Text>
|
||||
: children;
|
||||
|
||||
return (
|
||||
<View style = { styles.dialogContainer }>
|
||||
{ childrenComponent }
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -212,7 +212,7 @@ class DialogWithTabs extends Component<Props, State> {
|
||||
const { onSubmit, tabs } = this.props;
|
||||
|
||||
tabs.forEach(({ submit }, idx) => {
|
||||
submit && submit(this.state.tabStates[idx]);
|
||||
submit(this.state.tabStates[idx]);
|
||||
});
|
||||
|
||||
onSubmit();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// @flow
|
||||
|
||||
export { default as BottomSheet } from './BottomSheet';
|
||||
export { default as Dialog } from './Dialog';
|
||||
export { default as DialogContainer } from './DialogContainer';
|
||||
export { default as DialogContent } from './DialogContent';
|
||||
export { default as Dialog } from './Dialog';
|
||||
export { default as StatelessDialog } from './StatelessDialog';
|
||||
export { default as DialogWithTabs } from './DialogWithTabs';
|
||||
export { default as AbstractDialogTab } from './AbstractDialogTab';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import { BoxModel, ColorPalette, createStyleSheet } from '../../styles';
|
||||
import { ColorPalette, createStyleSheet } from '../../styles';
|
||||
|
||||
/**
|
||||
* The React {@code Component} styles of {@code Dialog}.
|
||||
@@ -13,14 +13,6 @@ export const dialog = createStyleSheet({
|
||||
color: ColorPalette.blue
|
||||
},
|
||||
|
||||
/**
|
||||
* Unified container for a consistent Dialog style.
|
||||
*/
|
||||
dialogContainer: {
|
||||
paddingHorizontal: BoxModel.padding,
|
||||
paddingVertical: 1.5 * BoxModel.padding
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the {@code Text} in a {@code Dialog} button which is
|
||||
* disabled.
|
||||
|
||||
@@ -14,7 +14,6 @@ export const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
export const JitsiConnectionEvents = JitsiMeetJS.events.connection;
|
||||
export const JitsiConnectionQualityEvents
|
||||
= JitsiMeetJS.events.connectionQuality;
|
||||
export const JitsiE2ePingEvents = JitsiMeetJS.events.e2eping;
|
||||
export const JitsiMediaDevicesEvents = JitsiMeetJS.events.mediaDevices;
|
||||
export const JitsiParticipantConnectionStatus
|
||||
= JitsiMeetJS.constants.participantConnectionStatus;
|
||||
|
||||
@@ -73,6 +73,14 @@ export default function _RTCPeerConnection(...args: any[]) {
|
||||
_RTCPeerConnection.prototype = Object.create(RTCPeerConnection.prototype);
|
||||
_RTCPeerConnection.prototype.constructor = _RTCPeerConnection;
|
||||
|
||||
_RTCPeerConnection.prototype.addIceCandidate
|
||||
= _makePromiseAware(RTCPeerConnection.prototype.addIceCandidate, 1, 0);
|
||||
|
||||
_RTCPeerConnection.prototype.createAnswer
|
||||
= _makePromiseAware(RTCPeerConnection.prototype.createAnswer, 0, 1);
|
||||
_RTCPeerConnection.prototype.createOffer
|
||||
= _makePromiseAware(RTCPeerConnection.prototype.createOffer, 0, 1);
|
||||
|
||||
_RTCPeerConnection.prototype._invokeOnaddstream = function(...args) {
|
||||
const onaddstream = this._onaddstream;
|
||||
|
||||
@@ -96,14 +104,32 @@ _RTCPeerConnection.prototype._queueOnaddstream = function(...args) {
|
||||
this._onaddstreamQueue.push(Array.from(args));
|
||||
};
|
||||
|
||||
_RTCPeerConnection.prototype.setRemoteDescription = function(description) {
|
||||
_RTCPeerConnection.prototype.setLocalDescription
|
||||
= _makePromiseAware(RTCPeerConnection.prototype.setLocalDescription, 1, 0);
|
||||
|
||||
_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(description)
|
||||
_synthesizeIPv6Addresses(sessionDescription)
|
||||
.catch(reason => {
|
||||
reason && _LOGE(reason);
|
||||
|
||||
return description;
|
||||
return sessionDescription;
|
||||
})
|
||||
.then(value => _setRemoteDescription.bind(this)(value)));
|
||||
|
||||
@@ -119,18 +145,61 @@ function _LOGE(...args) {
|
||||
logger.error(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a {@code Promise}-returning function out of a specific void function
|
||||
* with {@code successCallback} and {@code failureCallback}.
|
||||
*
|
||||
* @param {Function} f - The (void) function with {@code successCallback} and
|
||||
* {@code failureCallback}.
|
||||
* @param {number} beforeCallbacks - The number of arguments before
|
||||
* {@code successCallback} and {@code failureCallback}.
|
||||
* @param {number} afterCallbacks - The number of arguments after
|
||||
* {@code successCallback} and {@code failureCallback}.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function _makePromiseAware(
|
||||
f: Function,
|
||||
beforeCallbacks: number,
|
||||
afterCallbacks: number) {
|
||||
return function(...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (args.length <= beforeCallbacks + afterCallbacks) {
|
||||
args.splice(beforeCallbacks, 0, resolve, reject);
|
||||
}
|
||||
|
||||
let fPromise;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-invalid-this
|
||||
fPromise = f.apply(this, args);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
|
||||
// If the super implementation returns a Promise from the deprecated
|
||||
// invocation by any chance, try to make sense of it.
|
||||
if (fPromise) {
|
||||
const { then } = fPromise;
|
||||
|
||||
typeof then === 'function'
|
||||
&& then.call(fPromise, resolve, reject);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts react-native-webrtc's {@link RTCPeerConnection#setRemoteDescription}
|
||||
* implementation which uses the deprecated, callback-based version to the
|
||||
* {@code Promise}-based version.
|
||||
*
|
||||
* @param {RTCSessionDescription} description - The RTCSessionDescription
|
||||
* @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
|
||||
* which specifies the configuration of the remote end of the connection.
|
||||
* @private
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function _setRemoteDescription(description) {
|
||||
function _setRemoteDescription(sessionDescription) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
/* eslint-disable no-invalid-this */
|
||||
@@ -139,8 +208,10 @@ function _setRemoteDescription(description) {
|
||||
// setRemoteDescription calls. I shouldn't be but... anyway.
|
||||
this._onaddstreamQueue = [];
|
||||
|
||||
RTCPeerConnection.prototype.setRemoteDescription.call(this, description)
|
||||
.then((...args) => {
|
||||
RTCPeerConnection.prototype.setRemoteDescription.call(
|
||||
this,
|
||||
sessionDescription,
|
||||
(...args) => {
|
||||
let q;
|
||||
|
||||
try {
|
||||
@@ -151,7 +222,8 @@ function _setRemoteDescription(description) {
|
||||
}
|
||||
|
||||
this._invokeQueuedOnaddstream(q);
|
||||
}, (...args) => {
|
||||
},
|
||||
(...args) => {
|
||||
this._onaddstreamQueue = undefined;
|
||||
|
||||
reject(...args);
|
||||
|
||||
@@ -126,6 +126,7 @@ function _visitNode(node, callback) {
|
||||
//
|
||||
// Required by:
|
||||
// - jQuery
|
||||
// - lib-jitsi-meet/modules/RTC/adapter.screenshare.js
|
||||
// - Strophe
|
||||
if (typeof global.document === 'undefined') {
|
||||
const document
|
||||
@@ -150,6 +151,14 @@ function _visitNode(node, callback) {
|
||||
document.cookie = '';
|
||||
}
|
||||
|
||||
// document.implementation
|
||||
//
|
||||
// Required by:
|
||||
// - jQuery
|
||||
if (typeof document.implementation === 'undefined') {
|
||||
document.implementation = {};
|
||||
}
|
||||
|
||||
// document.implementation.createHTMLDocument
|
||||
//
|
||||
// Required by:
|
||||
@@ -353,9 +362,26 @@ function _visitNode(node, callback) {
|
||||
const { navigator } = global;
|
||||
|
||||
if (navigator) {
|
||||
// platform
|
||||
//
|
||||
// Required by:
|
||||
// - lib-jitsi-meet/modules/RTC/adapter.screenshare.js
|
||||
if (typeof navigator.platform === 'undefined') {
|
||||
navigator.platform = '';
|
||||
}
|
||||
|
||||
// plugins
|
||||
//
|
||||
// Required by:
|
||||
// - lib-jitsi-meet/modules/RTC/adapter.screenshare.js
|
||||
if (typeof navigator.plugins === 'undefined') {
|
||||
navigator.plugins = [];
|
||||
}
|
||||
|
||||
// userAgent
|
||||
//
|
||||
// Required by:
|
||||
// - lib-jitsi-meet/modules/RTC/adapter.screenshare.js
|
||||
// - lib-jitsi-meet/modules/browser/BrowserDetection.js
|
||||
let userAgent = navigator.userAgent || '';
|
||||
|
||||
|
||||
@@ -3,15 +3,14 @@ import {
|
||||
MediaStreamTrack,
|
||||
RTCSessionDescription,
|
||||
RTCIceCandidate,
|
||||
mediaDevices,
|
||||
permissions
|
||||
getUserMedia
|
||||
} from 'react-native-webrtc';
|
||||
|
||||
import RTCPeerConnection from './RTCPeerConnection';
|
||||
|
||||
(global => {
|
||||
if (typeof global.MediaStream === 'undefined') {
|
||||
global.MediaStream = MediaStream;
|
||||
if (typeof global.webkitMediaStream === 'undefined') {
|
||||
global.webkitMediaStream = MediaStream;
|
||||
}
|
||||
if (typeof global.MediaStreamTrack === 'undefined') {
|
||||
global.MediaStreamTrack = MediaStreamTrack;
|
||||
@@ -22,7 +21,7 @@ import RTCPeerConnection from './RTCPeerConnection';
|
||||
if (typeof global.RTCPeerConnection === 'undefined') {
|
||||
global.RTCPeerConnection = RTCPeerConnection;
|
||||
}
|
||||
if (typeof global.RTCPeerConnection === 'undefined') {
|
||||
if (typeof global.webkitRTCPeerConnection === 'undefined') {
|
||||
global.webkitRTCPeerConnection = RTCPeerConnection;
|
||||
}
|
||||
if (typeof global.RTCSessionDescription === 'undefined') {
|
||||
@@ -32,11 +31,8 @@ import RTCPeerConnection from './RTCPeerConnection';
|
||||
const navigator = global.navigator;
|
||||
|
||||
if (navigator) {
|
||||
if (typeof navigator.mediaDevices === 'undefined') {
|
||||
navigator.mediaDevices = mediaDevices;
|
||||
}
|
||||
if (typeof navigator.permissions === 'undefined') {
|
||||
navigator.permissions = permissions;
|
||||
if (typeof navigator.webkitGetUserMedia === 'undefined') {
|
||||
navigator.webkitGetUserMedia = getUserMedia;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ export default class Video extends Component<*> {
|
||||
? 'contain'
|
||||
: (style && style.objectFit) || 'cover';
|
||||
const rtcView
|
||||
= (
|
||||
= ( // eslint-disable-line no-extra-parens
|
||||
<RTCView
|
||||
mirror = { this.props.mirror }
|
||||
objectFit = { objectFit }
|
||||
|
||||
@@ -12,11 +12,6 @@ export type Item = {
|
||||
*/
|
||||
colorBase: string,
|
||||
|
||||
/**
|
||||
* An optional react element to append to the end of the Item.
|
||||
*/
|
||||
elementAfter?: ?ComponentType<any>,
|
||||
|
||||
/**
|
||||
* Item title
|
||||
*/
|
||||
|
||||
@@ -29,12 +29,6 @@ type Props = {
|
||||
*/
|
||||
onRefresh: Function,
|
||||
|
||||
/**
|
||||
* Function to be invoked when a secondary action is performed on an item.
|
||||
* The item's ID is passed.
|
||||
*/
|
||||
onSecondaryAction: Function,
|
||||
|
||||
/**
|
||||
* Function to override the rendered default empty list component.
|
||||
*/
|
||||
@@ -93,7 +87,7 @@ class NavigateSectionList extends Component<Props> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
renderListEmptyComponent = this._renderListEmptyComponent(),
|
||||
renderListEmptyComponent = this._renderListEmptyComponent,
|
||||
sections
|
||||
} = this.props;
|
||||
|
||||
@@ -134,13 +128,11 @@ class NavigateSectionList extends Component<Props> {
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onPress(url) {
|
||||
const { disabled, onPress } = this.props;
|
||||
return () => {
|
||||
const { disabled, onPress } = this.props;
|
||||
|
||||
if (!disabled && url && typeof onPress === 'function') {
|
||||
return () => onPress(url);
|
||||
}
|
||||
|
||||
return null;
|
||||
!disabled && url && typeof onPress === 'function' && onPress(url);
|
||||
};
|
||||
}
|
||||
|
||||
_onRefresh: () => void;
|
||||
@@ -159,23 +151,6 @@ class NavigateSectionList extends Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
_onSecondaryAction: Object => Function;
|
||||
|
||||
/**
|
||||
* Returns a function that is used in the secondaryAction callback of the
|
||||
* items.
|
||||
*
|
||||
* @param {string} id - The id of the item that secondary action was
|
||||
* performed on.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onSecondaryAction(id) {
|
||||
return () => {
|
||||
this.props.onSecondaryAction(id);
|
||||
};
|
||||
}
|
||||
|
||||
_renderItem: Object => Object;
|
||||
|
||||
/**
|
||||
@@ -188,7 +163,7 @@ class NavigateSectionList extends Component<Props> {
|
||||
*/
|
||||
_renderItem(listItem, key: string = '') {
|
||||
const { item } = listItem;
|
||||
const { id, url } = item;
|
||||
const { url } = item;
|
||||
|
||||
// XXX The value of title cannot be undefined; otherwise, react-native
|
||||
// will throw a TypeError: Cannot read property of undefined. While it's
|
||||
@@ -203,9 +178,7 @@ class NavigateSectionList extends Component<Props> {
|
||||
<NavigateSectionListItem
|
||||
item = { item }
|
||||
key = { key }
|
||||
onPress = { url ? this._onPress(url) : undefined }
|
||||
secondaryAction = {
|
||||
url ? undefined : this._onSecondaryAction(id) } />
|
||||
onPress = { this._onPress(url) } />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,7 @@ type Props = {
|
||||
/**
|
||||
* Function to be invoked when an Item is pressed. The Item's URL is passed.
|
||||
*/
|
||||
onPress: ?Function,
|
||||
|
||||
/**
|
||||
* Function to be invoked when secondary action was performed on an Item.
|
||||
*/
|
||||
secondaryAction: ?Function
|
||||
onPress: Function
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,24 +100,6 @@ export default class NavigateSectionListItem extends Component<Props> {
|
||||
return lines && lines.length ? lines.map(this._renderItemLine) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the secondary action label.
|
||||
*
|
||||
* @private
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderSecondaryAction() {
|
||||
const { secondaryAction } = this.props;
|
||||
|
||||
return (
|
||||
<Container
|
||||
onClick = { secondaryAction }
|
||||
style = { styles.secondaryActionContainer }>
|
||||
<Text style = { styles.secondaryActionLabel }>+</Text>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the content of this component.
|
||||
*
|
||||
@@ -158,7 +135,6 @@ export default class NavigateSectionListItem extends Component<Props> {
|
||||
</Text>
|
||||
{this._renderItemLines(lines)}
|
||||
</Container>
|
||||
{ this.props.secondaryAction && this._renderSecondaryAction() }
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ const HEADER_COLOR = ColorPalette.blue;
|
||||
// Header height is from Android guidelines. Also, this looks good.
|
||||
const HEADER_HEIGHT = 56;
|
||||
const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
|
||||
const SECONDARY_ACTION_BUTTON_SIZE = 30;
|
||||
|
||||
export const HEADER_PADDING = BoxModel.padding;
|
||||
export const STATUSBAR_COLOR = ColorPalette.blueHighlight;
|
||||
@@ -267,21 +266,6 @@ const SECTION_LIST_STYLES = {
|
||||
color: OVERLAY_FONT_COLOR
|
||||
},
|
||||
|
||||
secondaryActionContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: ColorPalette.blue,
|
||||
borderRadius: 3,
|
||||
height: SECONDARY_ACTION_BUTTON_SIZE,
|
||||
justifyContent: 'center',
|
||||
margin: BoxModel.margin * 0.5,
|
||||
marginRight: BoxModel.margin,
|
||||
width: SECONDARY_ACTION_BUTTON_SIZE
|
||||
},
|
||||
|
||||
secondaryActionLabel: {
|
||||
color: ColorPalette.white
|
||||
},
|
||||
|
||||
touchableView: {
|
||||
flexDirection: 'row'
|
||||
}
|
||||
|
||||
@@ -22,8 +22,11 @@ export default class Container extends AbstractContainer {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { visible = true } = this.props;
|
||||
const { visible } = this.props;
|
||||
|
||||
return visible ? super._render('div') : null;
|
||||
return (
|
||||
typeof visible === 'undefined' || visible
|
||||
? super._render('div')
|
||||
: null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class InlineDialogFailure extends Component<*> {
|
||||
const supportString = t('inlineDialogFailure.supportMsg');
|
||||
const supportLinkElem
|
||||
= supportLink
|
||||
? (
|
||||
? ( // eslint-disable-line no-extra-parens
|
||||
<div className = 'inline-dialog-error-text'>
|
||||
<span>{ supportString.padEnd(supportString.length + 1) }
|
||||
</span>
|
||||
|
||||
@@ -244,7 +244,7 @@ class MultiSelectAutocomplete extends Component {
|
||||
if (!this.state.error) {
|
||||
return null;
|
||||
}
|
||||
const content = (
|
||||
const content = ( // eslint-disable-line no-extra-parens
|
||||
<div className = 'autocomplete-error'>
|
||||
<InlineDialogFailure
|
||||
onRetry = { this._onRetry } />
|
||||
|
||||
@@ -6,22 +6,18 @@ import Container from './Container';
|
||||
import Text from './Text';
|
||||
import type { Item } from '../../Types';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link NavigateSectionListItem}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Function to be invoked when an item is pressed. The item's URL is passed.
|
||||
*/
|
||||
onPress: ?Function,
|
||||
onPress: Function,
|
||||
|
||||
/**
|
||||
* A item containing data to be rendered
|
||||
*/
|
||||
item: Item
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a React/Web {@link Component} for displaying an item in a
|
||||
@@ -29,16 +25,14 @@ type Props = {
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class NavigateSectionListItem<P: Props>
|
||||
extends Component<P> {
|
||||
|
||||
export default class NavigateSectionListItem extends Component<Props> {
|
||||
/**
|
||||
* Renders the content of this component.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { elementAfter, lines, title } = this.props.item;
|
||||
const { lines, title } = this.props.item;
|
||||
const { onPress } = this.props;
|
||||
|
||||
/**
|
||||
@@ -58,30 +52,22 @@ export default class NavigateSectionListItem<P: Props>
|
||||
duration = lines[1];
|
||||
}
|
||||
|
||||
const rootClassName = `navigate-section-list-tile ${
|
||||
onPress ? 'with-click-handler' : 'without-click-handler'}`;
|
||||
|
||||
return (
|
||||
<Container
|
||||
className = { rootClassName }
|
||||
className = 'navigate-section-list-tile'
|
||||
onClick = { onPress }>
|
||||
<Container className = 'navigate-section-list-tile-info'>
|
||||
<Text
|
||||
className = 'navigate-section-tile-title'>
|
||||
{ title }
|
||||
</Text>
|
||||
<Text
|
||||
className = 'navigate-section-tile-body'>
|
||||
{ date }
|
||||
</Text>
|
||||
<Text
|
||||
className = 'navigate-section-tile-body'>
|
||||
{ duration }
|
||||
</Text>
|
||||
</Container>
|
||||
<Container className = { 'element-after' }>
|
||||
{ elementAfter || null }
|
||||
</Container>
|
||||
<Text
|
||||
className = 'navigate-section-tile-title'>
|
||||
{ title }
|
||||
</Text>
|
||||
<Text
|
||||
className = 'navigate-section-tile-body'>
|
||||
{ date }
|
||||
</Text>
|
||||
<Text
|
||||
className = 'navigate-section-tile-body'>
|
||||
{ duration }
|
||||
</Text>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,11 +7,6 @@ import type { Section } from '../../Types';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Rendered when the list is empty. Should be a rendered element.
|
||||
*/
|
||||
ListEmptyComponent: Object,
|
||||
|
||||
/**
|
||||
* Used to extract a unique key for a given item at the specified index.
|
||||
* Key is used for caching and as the react key to track item re-ordering.
|
||||
@@ -54,7 +49,6 @@ export default class SectionList extends Component<Props> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
ListEmptyComponent,
|
||||
renderSectionHeader,
|
||||
renderItem,
|
||||
sections,
|
||||
@@ -62,34 +56,34 @@ export default class SectionList extends Component<Props> {
|
||||
} = this.props;
|
||||
|
||||
/**
|
||||
* If there are no recent items we don't want to display anything
|
||||
* If there are no recent items we dont want to display anything
|
||||
*/
|
||||
if (sections) {
|
||||
return (
|
||||
/* eslint-disable no-extra-parens */
|
||||
<Container
|
||||
className = 'navigate-section-list'>
|
||||
{
|
||||
sections.length === 0
|
||||
? ListEmptyComponent
|
||||
: sections.map((section, sectionIndex) => (
|
||||
<Container
|
||||
key = { sectionIndex }>
|
||||
{ renderSectionHeader(section) }
|
||||
{ section.data
|
||||
.map((item, listIndex) => {
|
||||
const listItem = {
|
||||
item
|
||||
};
|
||||
sections.map((section, sectionIndex) => (
|
||||
<Container
|
||||
key = { sectionIndex }>
|
||||
{ renderSectionHeader(section) }
|
||||
{ section.data
|
||||
.map((item, listIndex) => {
|
||||
const listItem = {
|
||||
item
|
||||
};
|
||||
|
||||
return renderItem(listItem,
|
||||
keyExtractor(section,
|
||||
listIndex));
|
||||
}) }
|
||||
</Container>
|
||||
)
|
||||
)
|
||||
return renderItem(listItem,
|
||||
keyExtractor(section,
|
||||
listIndex));
|
||||
}) }
|
||||
</Container>
|
||||
)
|
||||
)
|
||||
}
|
||||
</Container>
|
||||
/* eslint-enable no-extra-parens */
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ class Watermarks extends Component<*, *> {
|
||||
let reactElement = null;
|
||||
|
||||
if (this.state.showBrandWatermark) {
|
||||
reactElement = (
|
||||
reactElement = ( // eslint-disable-line no-extra-parens
|
||||
<div
|
||||
className = 'watermark rightwatermark'
|
||||
style = { _RIGHT_WATERMARK_STYLE } />
|
||||
@@ -114,7 +114,7 @@ class Watermarks extends Component<*, *> {
|
||||
const { brandWatermarkLink } = this.state;
|
||||
|
||||
if (brandWatermarkLink) {
|
||||
reactElement = (
|
||||
reactElement = ( // eslint-disable-line no-extra-parens
|
||||
<a
|
||||
href = { brandWatermarkLink }
|
||||
target = '_new'>
|
||||
@@ -144,7 +144,7 @@ class Watermarks extends Component<*, *> {
|
||||
const { jitsiWatermarkLink } = this.state;
|
||||
|
||||
if (jitsiWatermarkLink) {
|
||||
reactElement = (
|
||||
reactElement = ( // eslint-disable-line no-extra-parens
|
||||
<a
|
||||
href = { jitsiWatermarkLink }
|
||||
target = '_new'>
|
||||
|
||||
14
react/features/base/session/actionTypes.js
Normal file
14
react/features/base/session/actionTypes.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* FIXME.
|
||||
*
|
||||
* {
|
||||
* type: SET_SESSION,
|
||||
* session: {
|
||||
* url: {string},
|
||||
* state: {string},
|
||||
* ...data
|
||||
* }
|
||||
* }
|
||||
* @public
|
||||
*/
|
||||
export const SET_SESSION = Symbol('SET_SESSION');
|
||||
16
react/features/base/session/actions.js
Normal file
16
react/features/base/session/actions.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { SET_SESSION } from './actionTypes';
|
||||
|
||||
/**
|
||||
* FIXME.
|
||||
*
|
||||
* @param {string} session - FIXME.
|
||||
* @returns {{
|
||||
* type: SET_SESSION
|
||||
* }}
|
||||
*/
|
||||
export function setSession(session) {
|
||||
return {
|
||||
type: SET_SESSION,
|
||||
session
|
||||
};
|
||||
}
|
||||
12
react/features/base/session/constants.js
Normal file
12
react/features/base/session/constants.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
export const SESSION_CONFIGURED = Symbol('SESSION_CONFIGURED');
|
||||
|
||||
export const SESSION_ENDED = Symbol('SESSION_ENDED');
|
||||
|
||||
export const SESSION_FAILED = Symbol('SESSION_FAILED');
|
||||
|
||||
export const SESSION_STARTED = Symbol('SESSION_STARTED');
|
||||
|
||||
export const SESSION_WILL_END = Symbol('SESSION_WILL_END');
|
||||
|
||||
export const SESSION_WILL_START = Symbol('SESSION_WILL_START');
|
||||
36
react/features/base/session/functions.js
Normal file
36
react/features/base/session/functions.js
Normal file
@@ -0,0 +1,36 @@
|
||||
// @flow
|
||||
|
||||
import { toState } from '../redux';
|
||||
import { toURLString } from '../util';
|
||||
|
||||
/**
|
||||
* FIXME.
|
||||
*
|
||||
* @param {Function|Object} stateful - FIXME.
|
||||
* @param {string} url - FIXME.
|
||||
* @returns {*}
|
||||
*/
|
||||
export function getSession(stateful: Function | Object, url: string): ?Object {
|
||||
const state = toState(stateful);
|
||||
|
||||
const session = state['features/base/session'].get(url);
|
||||
|
||||
if (!session) {
|
||||
console.info(`SESSION NOT FOUND FOR URL: ${url}`);
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME.
|
||||
*
|
||||
* @param {Function | Object} stateful - FIXME.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getCurrentSession(stateful: Function | Object): ?Object {
|
||||
const state = toState(stateful);
|
||||
const { locationURL } = state['features/base/config'];
|
||||
|
||||
return getSession(state, toURLString(locationURL));
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
export * from './controller';
|
||||
export * from './constants';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
349
react/features/base/session/middleware.js
Normal file
349
react/features/base/session/middleware.js
Normal file
@@ -0,0 +1,349 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_WILL_JOIN,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
JITSI_CONFERENCE_URL_KEY,
|
||||
isRoomValid
|
||||
} from '../../base/conference';
|
||||
import {
|
||||
CONNECTION_DISCONNECTED,
|
||||
CONNECTION_FAILED,
|
||||
CONNECTION_WILL_CONNECT
|
||||
} from '../../base/connection';
|
||||
import {
|
||||
MiddlewareRegistry,
|
||||
toState
|
||||
} from '../../base/redux';
|
||||
import { parseURIString, toURLString } from '../../base/util';
|
||||
|
||||
import {
|
||||
SESSION_CONFIGURED,
|
||||
SESSION_ENDED,
|
||||
SESSION_FAILED,
|
||||
SESSION_STARTED,
|
||||
SESSION_WILL_END,
|
||||
SESSION_WILL_START
|
||||
} from './constants';
|
||||
import { setSession } from './actions';
|
||||
import { CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from '../config';
|
||||
import { getCurrentSession, getSession } from './functions';
|
||||
|
||||
/**
|
||||
* Middleware that maintains conference sessions. The features spans across
|
||||
* three features strictly related to the conference lifecycle.
|
||||
* The first one is the base/config which configures the session. It's
|
||||
* 'locationURL' state is used to tell what's the current conference URL the app
|
||||
* is working with. The session starts as soon as {@link CONFIG_WILL_LOAD} event
|
||||
* arrives. The {@code locationURL} instance is stored in the session to
|
||||
* associate the load config request with the session and be able to distinguish
|
||||
* between the current and outdated load config request failures. After the
|
||||
* config is loaded the lifecycle moves to the base/connection feature which
|
||||
* creates a {@code JitsiConnection} and tries to connect to the server. On
|
||||
* {@code CONNECTION_WILL_CONNECT} the connection instance is stored in the
|
||||
* session and used later to filter the events similar to what's done for
|
||||
* the load config requests. The base/conference feature adds the last part to
|
||||
* the session's lifecycle. A {@code JitsiConference} instance is stored in the
|
||||
* session on the {@code CONFERENCE_WILL_JOIN} action. A session is considered
|
||||
* alive as long as either connection or conference is available and
|
||||
* operational.
|
||||
*
|
||||
* @param {Store} store - Redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const result = next(action);
|
||||
const { type } = action;
|
||||
|
||||
switch (type) {
|
||||
case CONFERENCE_WILL_JOIN: {
|
||||
const { conference } = action;
|
||||
const { locationURL } = store.getState()['features/base/connection'];
|
||||
const url = toURLString(locationURL);
|
||||
const session = getSession(store, url);
|
||||
|
||||
if (session) {
|
||||
store.dispatch(setSession({
|
||||
url: session.url,
|
||||
conference
|
||||
}));
|
||||
} else {
|
||||
console.info(`IGNORED WILL_JOIN FOR: ${url}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_JOINED: {
|
||||
const { conference } = action;
|
||||
const session = findSessionForConference(store, conference);
|
||||
const state = session && session.state;
|
||||
|
||||
if (state === SESSION_CONFIGURED) {
|
||||
store.dispatch(
|
||||
setSession({
|
||||
|
||||
// Flow complains that the session can be undefined, but it
|
||||
// can't if the state is defined.
|
||||
// $FlowExpectedError
|
||||
url: session.url,
|
||||
state: SESSION_STARTED
|
||||
}));
|
||||
} else {
|
||||
// eslint-disable-next-line max-len
|
||||
console.info(`IGNORED CONF JOINED FOR: ${toURLString(conference[JITSI_CONFERENCE_URL_KEY])}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_LEFT:
|
||||
case CONFERENCE_FAILED: {
|
||||
const { conference, error } = action;
|
||||
const session = findSessionForConference(store, conference);
|
||||
|
||||
// FIXME update comments
|
||||
// XXX Certain CONFERENCE_FAILED errors are recoverable i.e. they have
|
||||
// prevented the user from joining a specific conference but the app may
|
||||
// be able to eventually join the conference. For example, the app will
|
||||
// ask the user for a password upon
|
||||
// JitsiConferenceErrors.PASSWORD_REQUIRED and will retry joining the
|
||||
// conference afterwards. Such errors are to not reach the native
|
||||
// counterpart of the External API (or at least not in the
|
||||
// fatality/finality semantics attributed to
|
||||
// conferenceFailed:/onConferenceFailed).
|
||||
if (session) {
|
||||
if (!error || isGameOver(store, session, error)) {
|
||||
if (session.connection) {
|
||||
store.dispatch(
|
||||
setSession({
|
||||
url: session.url,
|
||||
conference: undefined
|
||||
}));
|
||||
} else {
|
||||
store.dispatch(
|
||||
setSession({
|
||||
url: session.url,
|
||||
state: error ? SESSION_FAILED : SESSION_ENDED,
|
||||
error
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line max-len
|
||||
console.info(`IGNORED FAILED/LEFT for ${toURLString(conference[JITSI_CONFERENCE_URL_KEY])}`, error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// NOTE WILL_JOIN is fired on SET_ROOM
|
||||
// case CONFERENCE_WILL_JOIN:
|
||||
case CONFERENCE_WILL_LEAVE: {
|
||||
const { conference } = action;
|
||||
const url = toURLString(conference[JITSI_CONFERENCE_URL_KEY]);
|
||||
const session = findSessionForConference(store, conference);
|
||||
const state = session && session.state;
|
||||
|
||||
if (state && state !== SESSION_WILL_END) {
|
||||
store.dispatch(
|
||||
setSession({
|
||||
|
||||
// Flow complains that the session can be undefined, but it
|
||||
// can't if the state is defined.
|
||||
// $FlowExpectedError
|
||||
url: session.url,
|
||||
state: SESSION_WILL_END
|
||||
}));
|
||||
} else {
|
||||
console.info(`IGNORED WILL LEAVE FOR ${url}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CONNECTION_WILL_CONNECT: {
|
||||
const { connection } = action;
|
||||
const { locationURL } = store.getState()['features/base/connection'];
|
||||
const url = toURLString(locationURL);
|
||||
const session = getSession(store, url);
|
||||
|
||||
if (session) {
|
||||
store.dispatch(
|
||||
setSession({
|
||||
url: session.url,
|
||||
connection,
|
||||
conference: undefined // Detach from the old conference
|
||||
}));
|
||||
} else {
|
||||
console.info(`IGNORED CONNECTION_WILL_CONNECT FOR: ${url}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CONNECTION_DISCONNECTED:
|
||||
case CONNECTION_FAILED: {
|
||||
const { connection, error } = action;
|
||||
const session = findSessionForConnection(store, connection);
|
||||
|
||||
if (session) {
|
||||
// Remove connection from the session, but wait for
|
||||
// the conference to be removed as well.
|
||||
if (!error || isGameOver(store, session, error)) {
|
||||
if (session.conference) {
|
||||
store.dispatch(
|
||||
setSession({
|
||||
url: session.url,
|
||||
connection: undefined
|
||||
}));
|
||||
} else {
|
||||
store.dispatch(
|
||||
setSession({
|
||||
url: session.url,
|
||||
state: error ? SESSION_FAILED : SESSION_ENDED,
|
||||
error
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.info('Ignored DISCONNECTED/FAILED for connection');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_CONFIG: {
|
||||
// XXX SET_CONFIG IS ALWAYS RELEVANT
|
||||
const { locationURL } = store.getState()['features/base/config'];
|
||||
const url = toURLString(locationURL);
|
||||
const session = getSession(store, url);
|
||||
const state = session && session.state;
|
||||
|
||||
if (state === SESSION_WILL_START) {
|
||||
store.dispatch(
|
||||
setSession({
|
||||
url,
|
||||
state: SESSION_CONFIGURED
|
||||
}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFIG_WILL_LOAD: {
|
||||
const { locationURL } = action;
|
||||
const url = toURLString(locationURL);
|
||||
const session = getSession(store, url);
|
||||
|
||||
// The back and forth to string conversion is here, because there's no
|
||||
// guarantee that the locationURL will be the exact custom structure
|
||||
// which contains the room property.
|
||||
let { room } = parseURIString(url);
|
||||
|
||||
// Validate the room
|
||||
room = isRoomValid(room) ? room : undefined;
|
||||
|
||||
if (room && !session) {
|
||||
store.dispatch(
|
||||
setSession({
|
||||
url,
|
||||
state: SESSION_WILL_START,
|
||||
locationURL,
|
||||
room
|
||||
}));
|
||||
} else if (room && session) {
|
||||
// Update to the new locationURL instance
|
||||
store.dispatch(
|
||||
setSession({
|
||||
url,
|
||||
locationURL
|
||||
}));
|
||||
} else {
|
||||
console.info(`IGNORED CFG WILL LOAD FOR ${url}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case LOAD_CONFIG_ERROR: {
|
||||
const { error, locationURL } = action;
|
||||
const url = toURLString(locationURL);
|
||||
const session = getSession(store, url);
|
||||
|
||||
if (session && session.locationURL === locationURL) {
|
||||
if (isGameOver(store, session, error)) {
|
||||
store.dispatch(
|
||||
setSession({
|
||||
url,
|
||||
state: SESSION_FAILED,
|
||||
error
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
console.info(`IGNORED LOAD_CONFIG_ERROR FOR: ${url}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* FIXME A session is to be terminated either when the recoverable flag is
|
||||
* explicitly set to {@code false} or if the error arrives for a session which
|
||||
* is no longer current (the app has started working with another session).
|
||||
* This can happen when a conference which is being disconnected fails in which
|
||||
* case the session needs to be ended even if the flag is not {@code false}
|
||||
* because we know that there's no fatal error handling. This is kind of
|
||||
* a contract between the fatal error feature and the session which probably
|
||||
* indicates that the fatal error detection and handling should be incorporated
|
||||
* into the session feature.
|
||||
*
|
||||
* @param {Object | Function} stateful - FIXME.
|
||||
* @param {Object} session - FIXME.
|
||||
* @param {Object} error - FIXME.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isGameOver(stateful, session, error) {
|
||||
return getCurrentSession(stateful) !== session
|
||||
|| error.recoverable === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME.
|
||||
*
|
||||
* @param {Object | Function} stateful - FIXME.
|
||||
* @param {JitsiConnection} connection - FIXME.
|
||||
* @returns {Object|undefined}
|
||||
*/
|
||||
function findSessionForConnection(stateful, connection) {
|
||||
const state = toState(stateful);
|
||||
|
||||
for (const session of state['features/base/session'].values()) {
|
||||
if (session.connection === connection) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
console.info('Session not found for a connection');
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME.
|
||||
*
|
||||
* @param {Object | Function} stateful - FIXME.
|
||||
* @param {JitsiConference} conference - FIXME.
|
||||
* @returns {Object|undefined}
|
||||
*/
|
||||
function findSessionForConference(stateful, conference) {
|
||||
const state = toState(stateful);
|
||||
|
||||
for (const session of state['features/base/session'].values()) {
|
||||
if (session.conference === conference) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
console.info('Session not found for a conference');
|
||||
|
||||
return undefined;
|
||||
}
|
||||
71
react/features/base/session/reducer.js
Normal file
71
react/features/base/session/reducer.js
Normal file
@@ -0,0 +1,71 @@
|
||||
// @flow
|
||||
|
||||
import { assign, ReducerRegistry } from '../../base/redux';
|
||||
import { getSymbolDescription } from '../util';
|
||||
|
||||
import { SET_SESSION } from './actionTypes';
|
||||
import {
|
||||
SESSION_FAILED,
|
||||
SESSION_ENDED,
|
||||
SESSION_WILL_START
|
||||
} from './constants';
|
||||
|
||||
ReducerRegistry.register('features/base/session',
|
||||
(state = new Map(), action) => {
|
||||
switch (action.type) {
|
||||
case SET_SESSION:
|
||||
return _setSession(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
|
||||
/**
|
||||
* FIXME.
|
||||
*
|
||||
* @param {Object} featureState - FIXME.
|
||||
* @param {Object} action - FIXME.
|
||||
* @returns {Map<any, any>} - FIXME.
|
||||
* @private
|
||||
*/
|
||||
function _setSession(featureState, action) {
|
||||
const { url, state, ...data } = action.session;
|
||||
const session = featureState.get(url);
|
||||
const nextState = new Map(featureState);
|
||||
|
||||
// Drop the whole action if the url is not defined
|
||||
if (!url) {
|
||||
console.error('SET SESSION - NO URL');
|
||||
|
||||
return nextState;
|
||||
}
|
||||
|
||||
if (session) {
|
||||
if (state === SESSION_ENDED || state === SESSION_FAILED) {
|
||||
nextState.delete(url);
|
||||
} else {
|
||||
nextState.set(
|
||||
url,
|
||||
assign(session, {
|
||||
url,
|
||||
state: state ? state : session.state,
|
||||
...data
|
||||
}));
|
||||
}
|
||||
} else if (state === SESSION_WILL_START) {
|
||||
nextState.set(
|
||||
url, {
|
||||
url,
|
||||
state,
|
||||
...data
|
||||
});
|
||||
}
|
||||
console.info(
|
||||
'SESSION STATE REDUCED: ',
|
||||
new Map(nextState),
|
||||
url,
|
||||
state && getSymbolDescription(state),
|
||||
action.session.error);
|
||||
|
||||
return nextState;
|
||||
}
|
||||
@@ -23,8 +23,6 @@ export const ColorPalette = {
|
||||
buttonUnderlay: '#495258',
|
||||
darkGrey: '#555555',
|
||||
green: '#40b183',
|
||||
lightGrey: '#AAAAAA',
|
||||
lighterGrey: '#EEEEEE',
|
||||
red: '#D00000',
|
||||
white: 'white',
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ export default class ToolboxItem extends AbstractToolboxItem<Props> {
|
||||
// XXX TouchableHighlight requires 1 child. If there's a need to
|
||||
// show both the icon and the label, then these two need to be
|
||||
// wrapped in a View.
|
||||
children = (
|
||||
children = ( // eslint-disable-line no-extra-parens
|
||||
<View style = { style }>
|
||||
{ children }
|
||||
<Text style = { styles && styles.labelStyle }>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user