mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-03 21:32:28 +00:00
Compare commits
147 Commits
jibri-queu
...
4442
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
014f3b615f | ||
|
|
25271d7eec | ||
|
|
7ef4de9c1c | ||
|
|
e6e088d197 | ||
|
|
0e034a686f | ||
|
|
d9f85c70f1 | ||
|
|
de8079cc98 | ||
|
|
2a9805f9b1 | ||
|
|
00ec0f03a6 | ||
|
|
91f636a813 | ||
|
|
fa4df19733 | ||
|
|
1d17cc91e0 | ||
|
|
93f4098dc0 | ||
|
|
11ae187ece | ||
|
|
0f9e01a7cf | ||
|
|
ddbd3f292a | ||
|
|
b153bf2fb8 | ||
|
|
919be21912 | ||
|
|
1a339100ab | ||
|
|
ce4ef96941 | ||
|
|
993ded9936 | ||
|
|
a8b9ae2b12 | ||
|
|
812af33a4d | ||
|
|
7f17c2eceb | ||
|
|
09124ad7e9 | ||
|
|
7a9a6855b7 | ||
|
|
8dcf04897a | ||
|
|
69b7301b9d | ||
|
|
794713b930 | ||
|
|
89cd6e8e3e | ||
|
|
7a7937c072 | ||
|
|
4765ab9d63 | ||
|
|
1d5decc14f | ||
|
|
119b79fd84 | ||
|
|
188771751d | ||
|
|
d2ec0ea6f3 | ||
|
|
ed6e75b241 | ||
|
|
dedd3f4ef0 | ||
|
|
bbb4fbd5f8 | ||
|
|
92235ae535 | ||
|
|
ebb1b8d76b | ||
|
|
42d559de93 | ||
|
|
2838aefccc | ||
|
|
ca306f47b6 | ||
|
|
56da400f19 | ||
|
|
ab21e3cd5e | ||
|
|
2c026754ef | ||
|
|
8dbe3e37b9 | ||
|
|
7f67f78db6 | ||
|
|
312949eef6 | ||
|
|
41ea94c0c2 | ||
|
|
e70adef2ef | ||
|
|
57bbe3f75a | ||
|
|
e2731ce73e | ||
|
|
d5dae945a8 | ||
|
|
4d1dba937f | ||
|
|
b6792db65f | ||
|
|
9815b633fc | ||
|
|
b4bf82429c | ||
|
|
53d485b397 | ||
|
|
0354dbe889 | ||
|
|
7cafa205ee | ||
|
|
2b4f33bef8 | ||
|
|
31dee0bb68 | ||
|
|
fc75d45c6c | ||
|
|
25839b18d2 | ||
|
|
43f36c8cfd | ||
|
|
b02d96231c | ||
|
|
651d713206 | ||
|
|
9e5f469e0c | ||
|
|
493ce8249e | ||
|
|
fdffb688c1 | ||
|
|
4807badac8 | ||
|
|
5e3bd746e9 | ||
|
|
8fa41bebb7 | ||
|
|
cb7c280da6 | ||
|
|
0e50f1887e | ||
|
|
476ca54711 | ||
|
|
70aa19e6d9 | ||
|
|
7778a17b90 | ||
|
|
7ff41217ac | ||
|
|
e8c44c10dd | ||
|
|
b087b22d4f | ||
|
|
e988bf6565 | ||
|
|
d169bd5007 | ||
|
|
ac17db9df5 | ||
|
|
322618357c | ||
|
|
79c1358f4b | ||
|
|
5e85b5f63a | ||
|
|
74f7c4141f | ||
|
|
4866ddc2ad | ||
|
|
71d0577a49 | ||
|
|
b7529863d5 | ||
|
|
4ded94d130 | ||
|
|
eb8b730227 | ||
|
|
4bd57692b7 | ||
|
|
5d012c24a7 | ||
|
|
4f52a29120 | ||
|
|
8a4fb72eae | ||
|
|
6453ceb048 | ||
|
|
e51bbe6125 | ||
|
|
d725c0ab8a | ||
|
|
2c2edace2a | ||
|
|
d3d5847605 | ||
|
|
89ad76142d | ||
|
|
1e76b8b6ea | ||
|
|
55175e2e95 | ||
|
|
453c07cb17 | ||
|
|
af71d80150 | ||
|
|
b765adca75 | ||
|
|
92e6cf7618 | ||
|
|
10c2652a4f | ||
|
|
c3329ec931 | ||
|
|
9cf7199c0e | ||
|
|
d82bb0a89b | ||
|
|
295dd8a45d | ||
|
|
25ae83bcf4 | ||
|
|
82b1408454 | ||
|
|
36565f0c50 | ||
|
|
0c48e205d7 | ||
|
|
5e35b69fc9 | ||
|
|
3fd85720bc | ||
|
|
e439d065b7 | ||
|
|
5dcecdbb54 | ||
|
|
8d2a52d0e8 | ||
|
|
2aa6f7ff4b | ||
|
|
d716665f27 | ||
|
|
4ca4e242b1 | ||
|
|
cdd782a82f | ||
|
|
713ae817c0 | ||
|
|
d05fa32413 | ||
|
|
3da7798e9f | ||
|
|
6fc9606c0d | ||
|
|
c4155575f9 | ||
|
|
b670b29d7f | ||
|
|
9b7e8c98ad | ||
|
|
ad44558153 | ||
|
|
d70f9d6fd6 | ||
|
|
7858f12df2 | ||
|
|
828e578af4 | ||
|
|
4289b23135 | ||
|
|
099820b6ac | ||
|
|
25ded0bdeb | ||
|
|
51fd10278b | ||
|
|
24c75b7332 | ||
|
|
2327a6d0b4 | ||
|
|
b94c357cc2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -84,3 +84,4 @@ android/app/google-services.json
|
||||
ios/app/dropbox.key
|
||||
ios/app/GoogleService-Info.plist
|
||||
|
||||
.vscode
|
||||
|
||||
15
Makefile
15
Makefile
@@ -3,8 +3,9 @@ CLEANCSS = ./node_modules/.bin/cleancss
|
||||
DEPLOY_DIR = libs
|
||||
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
|
||||
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
|
||||
OLM_DIR = node_modules/olm
|
||||
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
|
||||
NODE_SASS = ./node_modules/.bin/node-sass
|
||||
NODE_SASS = ./node_modules/.bin/sass
|
||||
NPM = npm
|
||||
OUTPUT_DIR = .
|
||||
STYLES_BUNDLE = css/all.bundle.css
|
||||
@@ -22,7 +23,7 @@ clean:
|
||||
rm -fr $(BUILD_DIR)
|
||||
|
||||
.NOTPARALLEL:
|
||||
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
|
||||
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-css deploy-local
|
||||
|
||||
deploy-init:
|
||||
rm -fr $(DEPLOY_DIR)
|
||||
@@ -51,12 +52,15 @@ deploy-appbundle:
|
||||
$(BUILD_DIR)/video-blur-effect.min.map \
|
||||
$(BUILD_DIR)/rnnoise-processor.min.js \
|
||||
$(BUILD_DIR)/rnnoise-processor.min.map \
|
||||
$(BUILD_DIR)/close3.min.js \
|
||||
$(BUILD_DIR)/close3.min.map \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-lib-jitsi-meet:
|
||||
cp \
|
||||
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.js \
|
||||
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.map \
|
||||
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.e2ee-worker.js \
|
||||
$(LIBJITSIMEET_DIR)/connection_optimization/external_connect.js \
|
||||
$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
|
||||
$(DEPLOY_DIR)
|
||||
@@ -67,6 +71,11 @@ deploy-libflac:
|
||||
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js.mem \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-olm:
|
||||
cp \
|
||||
$(OLM_DIR)/olm.wasm \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-rnnoise-binary:
|
||||
cp \
|
||||
$(RNNOISE_WASM_DIR)/rnnoise.wasm \
|
||||
@@ -81,7 +90,7 @@ deploy-local:
|
||||
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
|
||||
|
||||
.NOTPARALLEL:
|
||||
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac
|
||||
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-olm
|
||||
$(WEBPACK_DEV_SERVER) --detect-circular-deps
|
||||
|
||||
source-package:
|
||||
|
||||
@@ -3,7 +3,7 @@ apply plugin: 'com.android.application'
|
||||
// Crashlytics integration is done as part of Firebase now, so it gets
|
||||
// automagically activated with google-services.json
|
||||
if (googleServicesEnabled) {
|
||||
apply plugin: 'io.fabric'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
}
|
||||
|
||||
// Use the number of seconds/10 since Jan 1 2019 as the versionCode.
|
||||
@@ -70,16 +70,11 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url 'https://maven.fabric.io/public' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-5'
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
||||
@@ -87,9 +82,9 @@ dependencies {
|
||||
// Firebase
|
||||
// - Crashlytics
|
||||
// - Dynamic Links
|
||||
implementation 'com.google.firebase:firebase-core:16.0.6'
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
|
||||
implementation 'com.google.firebase:firebase-dynamic-links:16.1.5'
|
||||
implementation 'com.google.firebase:firebase-analytics:17.5.0'
|
||||
implementation 'com.google.firebase:firebase-crashlytics:17.2.1'
|
||||
implementation 'com.google.firebase:firebase-dynamic-links:19.1.0'
|
||||
}
|
||||
|
||||
implementation project(':sdk')
|
||||
|
||||
6
android/app/proguard-rules.pro
vendored
6
android/app/proguard-rules.pro
vendored
@@ -85,8 +85,4 @@
|
||||
# ^^^ We added the above when we switched minifyEnabled on.
|
||||
|
||||
# Rule to avoid build errors related to SVGs.
|
||||
-keep public class com.horcrux.svg.** {*;}
|
||||
|
||||
# Hermes
|
||||
-keep class com.facebook.hermes.unicode.** { *; }
|
||||
|
||||
-keep public class com.horcrux.svg.** {*;}
|
||||
@@ -3,9 +3,8 @@ package org.jitsi.meet;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics;
|
||||
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks;
|
||||
import io.fabric.sdk.android.Fabric;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeet;
|
||||
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||
@@ -22,10 +21,7 @@ final class GoogleServicesHelper {
|
||||
if (BuildConfig.GOOGLE_SERVICES_ENABLED) {
|
||||
Log.d(activity.getClass().getSimpleName(), "Initializing Google Services");
|
||||
|
||||
if (!JitsiMeet.isCrashReportingDisabled(activity)) {
|
||||
Fabric.with(activity, new Crashlytics());
|
||||
}
|
||||
|
||||
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!JitsiMeet.isCrashReportingDisabled(activity));
|
||||
FirebaseDynamicLinks.getInstance().getDynamicLink(activity.getIntent())
|
||||
.addOnSuccessListener(activity, pendingDynamicLinkData -> {
|
||||
Uri dynamicLink = null;
|
||||
|
||||
@@ -7,26 +7,46 @@ buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
repositories {
|
||||
maven { url 'https://maven.fabric.io/public' }
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.2'
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
classpath 'com.google.gms:google-services:4.3.3'
|
||||
classpath 'io.fabric.tools:gradle:1.28.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files.
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
buildToolsVersion = "29.0.3"
|
||||
compileSdkVersion = 29
|
||||
minSdkVersion = 23
|
||||
targetSdkVersion = 29
|
||||
supportLibVersion = "28.0.0"
|
||||
|
||||
// The Maven artifact groupdId of the third-party react-native modules which
|
||||
// Jitsi Meet SDK for Android depends on and which are not available in
|
||||
// third-party Maven repositories so we have to deploy to a Maven repository
|
||||
// of ours.
|
||||
moduleGroupId = 'com.facebook.react'
|
||||
|
||||
// Maven repo where artifacts will be published
|
||||
mavenRepo = System.env.MVN_REPO ?: ""
|
||||
mavenUser = System.env.MVN_USER ?: ""
|
||||
mavenPassword = System.env.MVN_PASSWORD ?: ""
|
||||
|
||||
// Libre build
|
||||
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
|
||||
|
||||
googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
// React Native (JS, Obj-C sources, Android binaries) is installed from npm.
|
||||
maven { url "$rootDir/../node_modules/react-native/android" }
|
||||
// Android JSC is installed from npm.
|
||||
maven { url("$rootDir/../node_modules/jsc-android/dist") }
|
||||
}
|
||||
|
||||
// Make sure we use the react-native version in node_modules and not the one
|
||||
@@ -141,30 +161,6 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
buildToolsVersion = "29.0.3"
|
||||
compileSdkVersion = 29
|
||||
minSdkVersion = 23
|
||||
targetSdkVersion = 29
|
||||
supportLibVersion = "28.0.0"
|
||||
|
||||
// The Maven artifact groupdId of the third-party react-native modules which
|
||||
// Jitsi Meet SDK for Android depends on and which are not available in
|
||||
// third-party Maven repositories so we have to deploy to a Maven repository
|
||||
// of ours.
|
||||
moduleGroupId = 'com.facebook.react'
|
||||
|
||||
// Maven repo where artifacts will be published
|
||||
mavenRepo = System.env.MVN_REPO ?: ""
|
||||
mavenUser = System.env.MVN_USER ?: ""
|
||||
mavenPassword = System.env.MVN_PASSWORD ?: ""
|
||||
|
||||
// Libre build
|
||||
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
|
||||
|
||||
googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild
|
||||
}
|
||||
|
||||
// Force the version of the Android build tools we have chosen on all
|
||||
// subprojects. The forcing was introduced for react-native and the third-party
|
||||
// modules that we utilize such as react-native-background-timer.
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
@@ -20,5 +21,5 @@
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
appVersion=20.4.0
|
||||
sdkVersion=2.10.0
|
||||
appVersion=20.5.0
|
||||
sdkVersion=2.11.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#Fri Mar 08 13:36:51 CET 2019
|
||||
#Wed Sep 23 11:48:00 EEST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
|
||||
|
||||
@@ -10,7 +10,7 @@ MVN_HTTP=0
|
||||
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
RN_VERSION=$(jq -r '.version' ${THIS_DIR}/../../node_modules/react-native/package.json)
|
||||
HERMES_VERSION=$(jq -r '.dependencies."hermes-engine"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -c 2-)
|
||||
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1 | cut -c 2-)
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
if [[ $THE_MVN_REPO == http* ]]; then
|
||||
@@ -38,19 +38,17 @@ if [[ $MVN_HTTP == 1 ]]; then
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=react-native-${RN_VERSION}.pom || true
|
||||
popd
|
||||
# Push Hermes
|
||||
echo "Pushing Hermes ${HERMES_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/hermes-engine/android/
|
||||
# Push JSC
|
||||
echo "Pushing JSC ${JSC_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/jsc-android/dist/org/webkit/android-jsc/${JSC_VERSION}
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-DrepositoryId=${MVN_REPO_ID} \
|
||||
-Dfile=hermes-release.aar \
|
||||
-Dfile=android-jsc-${JSC_VERSION}.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgroupId=com.facebook \
|
||||
-DartifactId=hermes \
|
||||
-Dversion=${HERMES_VERSION} \
|
||||
-DgeneratePom=true || true
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=android-jsc-${JSC_VERSION}.pom || true
|
||||
popd
|
||||
else
|
||||
# Push React Native, if necessary
|
||||
@@ -67,19 +65,17 @@ else
|
||||
popd
|
||||
fi
|
||||
|
||||
# Push Hermes, if necessary
|
||||
if [[ ! -d ${MVN_REPO}/com/facebook/hermes/${HERMES_VERSION} ]]; then
|
||||
echo "Pushing Hermes ${HERMES_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/hermes-engine/android/
|
||||
# Push JSC, if necessary
|
||||
if [[ ! -d ${MVN_REPO}/org/webkit/android-jsc/${JSC_VERSION} ]]; then
|
||||
echo "Pushing JSC ${JSC_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/jsc-android/dist/org/webkit/android-jsc/${JSC_VERSION}
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-Dfile=hermes-release.aar \
|
||||
-Dfile=android-jsc-${JSC_VERSION}.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgroupId=com.facebook \
|
||||
-DartifactId=hermes \
|
||||
-Dversion=${HERMES_VERSION} \
|
||||
-DgeneratePom=true
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=android-jsc-${JSC_VERSION}.pom
|
||||
popd
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
@@ -35,26 +33,19 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst '**/libc++_shared.so'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.fragment:fragment:1.2.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.fragment:fragment:1.2.5'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
|
||||
//noinspection GradleDynamicVersion
|
||||
api 'com.facebook.react:react-native:+'
|
||||
|
||||
// Hermes JS engine
|
||||
def hermesPath = "../../node_modules/hermes-engine/android/"
|
||||
debugImplementation files(hermesPath + "hermes-debug.aar")
|
||||
releaseImplementation files(hermesPath + "hermes-release.aar")
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation 'org.webkit:android-jsc:+'
|
||||
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
@@ -153,7 +144,7 @@ android.libraryVariants.all { def variant ->
|
||||
mergeResourcesTask.dependsOn(currentBundleTask)
|
||||
|
||||
mergeAssetsTask.doLast {
|
||||
def assetsDir = mergeAssetsTask.outputDir
|
||||
def assetsDir = mergeAssetsTask.outputDir.get()
|
||||
|
||||
// Bundle sounds
|
||||
//
|
||||
@@ -187,7 +178,7 @@ android.libraryVariants.all { def variant ->
|
||||
if (currentBundleTask.enabled) {
|
||||
copy {
|
||||
from(resourcesDir)
|
||||
into(mergeResourcesTask.outputDir)
|
||||
into(mergeResourcesTask.outputDir.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,14 +218,6 @@ publishing {
|
||||
dependency.appendNode('artifactId', artifactId)
|
||||
dependency.appendNode('version', it.moduleVersion)
|
||||
}
|
||||
|
||||
// Add Hermes dependency.
|
||||
def hermesPkg = new File("$rootDir/../node_modules/hermes-engine/package.json")
|
||||
def hermesVersion = new JsonSlurper().parseText(hermesPkg.text).version
|
||||
def hermesDependency = dependencies.appendNode('dependency')
|
||||
hermesDependency.appendNode('groupId', "com.facebook")
|
||||
hermesDependency.appendNode('artifactId', "hermes")
|
||||
hermesDependency.appendNode('version', hermesVersion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
/**
|
||||
* Helper class to encapsulate the work which needs to be done on
|
||||
* {@link Activity} lifecycle methods in order for the React side to be aware of
|
||||
@@ -177,6 +179,16 @@ public class JitsiMeetActivityDelegate {
|
||||
|
||||
public static void requestPermissions(Activity activity, String[] permissions, int requestCode, PermissionListener listener) {
|
||||
permissionListener = listener;
|
||||
activity.requestPermissions(permissions, requestCode);
|
||||
|
||||
// The RN Permissions module calls this in a non-UI thread. What we observe is a crash in ViewGroup.dispatchCancelPendingInputEvents,
|
||||
// which is called on the calling (ie, non-UI) thread. This doesn't look very safe, so try to avoid a crash by pretending the permission
|
||||
// was denied.
|
||||
|
||||
try {
|
||||
activity.requestPermissions(permissions, requestCode);
|
||||
} catch (Exception e) {
|
||||
JitsiMeetLogger.e(e, "Error requesting permissions");
|
||||
onRequestPermissionsResult(requestCode, permissions, new int[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.app.Activity;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
@@ -28,6 +27,7 @@ import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
import com.facebook.react.devsupport.DevInternalSettings;
|
||||
import com.facebook.react.jscexecutor.JSCExecutorFactory;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
@@ -35,6 +35,7 @@ import com.facebook.soloader.SoLoader;
|
||||
import com.oney.WebRTCModule.RTCVideoViewManager;
|
||||
import com.oney.WebRTCModule.WebRTCModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
import org.webrtc.SoftwareVideoDecoderFactory;
|
||||
import org.webrtc.SoftwareVideoEncoderFactory;
|
||||
import org.webrtc.audio.AudioDeviceModule;
|
||||
@@ -216,8 +217,9 @@ class ReactInstanceManagerHolder {
|
||||
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
||||
}
|
||||
|
||||
// Use the Hermes JavaScript engine.
|
||||
HermesExecutorFactory jsFactory = new HermesExecutorFactory();
|
||||
// Keep on using JSC, the jury is out on Hermes.
|
||||
JSCExecutorFactory jsFactory
|
||||
= new JSCExecutorFactory("", "");
|
||||
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
|
||||
7
app.js
7
app.js
@@ -4,6 +4,8 @@ import 'jquery';
|
||||
import 'jquery-contextmenu';
|
||||
import 'jQuery-Impromptu';
|
||||
|
||||
import 'olm';
|
||||
|
||||
import conference from './conference';
|
||||
import API from './modules/API';
|
||||
import UI from './modules/UI/UI';
|
||||
@@ -11,6 +13,11 @@ import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
|
||||
import remoteControl from './modules/remotecontrol/RemoteControl';
|
||||
import translation from './modules/translation/translation';
|
||||
|
||||
// Initialize Olm as early as possible.
|
||||
if (window.Olm) {
|
||||
window.Olm.init();
|
||||
}
|
||||
|
||||
window.APP = {
|
||||
API,
|
||||
conference,
|
||||
|
||||
@@ -110,7 +110,6 @@ import {
|
||||
} from './react/features/base/util';
|
||||
import { showDesktopPicker } from './react/features/desktop-picker';
|
||||
import { appendSuffix } from './react/features/display-name';
|
||||
import { setE2EEKey } from './react/features/e2ee';
|
||||
import {
|
||||
maybeOpenFeedbackDialog,
|
||||
submitFeedback
|
||||
@@ -121,7 +120,8 @@ import { suspendDetected } from './react/features/power-monitor';
|
||||
import {
|
||||
initPrejoin,
|
||||
isPrejoinPageEnabled,
|
||||
isPrejoinPageVisible
|
||||
isPrejoinPageVisible,
|
||||
makePrecallTest
|
||||
} from './react/features/prejoin';
|
||||
import { createRnnoiseProcessorPromise } from './react/features/rnnoise';
|
||||
import { toggleScreenshotCaptureEffect } from './react/features/screenshot-capture';
|
||||
@@ -745,8 +745,6 @@ export default {
|
||||
|
||||
this.roomName = roomName;
|
||||
|
||||
window.addEventListener('hashchange', this.onHashChange.bind(this), false);
|
||||
|
||||
try {
|
||||
// Initialize the device list first. This way, when creating tracks
|
||||
// based on preferred devices, loose label matching can be done in
|
||||
@@ -759,7 +757,15 @@ export default {
|
||||
}
|
||||
|
||||
if (isPrejoinPageEnabled(APP.store.getState())) {
|
||||
_connectionPromise = connect(roomName);
|
||||
_connectionPromise = connect(roomName).then(c => {
|
||||
// we want to initialize it early, in case of errors to be able
|
||||
// to gather logs
|
||||
APP.connection = c;
|
||||
|
||||
return c;
|
||||
});
|
||||
|
||||
APP.store.dispatch(makePrecallTest(this._getConferenceOptions()));
|
||||
|
||||
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
|
||||
const tracks = await tryCreateLocalTracks;
|
||||
@@ -1206,10 +1212,6 @@ export default {
|
||||
|
||||
// end used by torture
|
||||
|
||||
getLogs() {
|
||||
return room.getLogs();
|
||||
},
|
||||
|
||||
/**
|
||||
* Download logs, a function that can be called from console while
|
||||
* debugging.
|
||||
@@ -1218,7 +1220,7 @@ export default {
|
||||
saveLogs(filename = 'meetlog.json') {
|
||||
// this can be called from console and will not have reference to this
|
||||
// that's why we reference the global var
|
||||
const logs = APP.conference.getLogs();
|
||||
const logs = APP.connection.getLogs();
|
||||
const data = encodeURIComponent(JSON.stringify(logs, null, ' '));
|
||||
|
||||
const elem = document.createElement('a');
|
||||
@@ -1234,34 +1236,6 @@ export default {
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handled location hash change events.
|
||||
*/
|
||||
onHashChange() {
|
||||
const items = {};
|
||||
const parts = window.location.hash.substr(1).split('&');
|
||||
|
||||
for (const part of parts) {
|
||||
const param = part.split('=');
|
||||
const key = param[0];
|
||||
|
||||
if (!key) {
|
||||
continue; // eslint-disable-line no-continue
|
||||
}
|
||||
|
||||
items[key] = param[1];
|
||||
}
|
||||
|
||||
if (typeof items.e2eekey !== 'undefined') {
|
||||
APP.store.dispatch(setE2EEKey(items.e2eekey));
|
||||
|
||||
// Clean URL in browser history.
|
||||
const cleanUrl = window.location.href.split('#')[0];
|
||||
|
||||
history.replaceState(history.state, document.title, cleanUrl);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Exposes a Command(s) API on this instance. It is necessitated by (1) the
|
||||
* desire to keep room private to this instance and (2) the need of other
|
||||
@@ -2856,7 +2830,14 @@ export default {
|
||||
this._room = undefined;
|
||||
room = undefined;
|
||||
|
||||
APP.API.notifyReadyToClose();
|
||||
/**
|
||||
* Don't call {@code notifyReadyToClose} if the promotional page flag is set
|
||||
* and let the page take care of sending the message, since there will be
|
||||
* a redirect to the page regardlessly.
|
||||
*/
|
||||
if (!interfaceConfig.SHOW_PROMOTIONAL_CLOSE_PAGE) {
|
||||
APP.API.notifyReadyToClose();
|
||||
}
|
||||
APP.store.dispatch(maybeRedirectToWelcomePage(values[0]));
|
||||
});
|
||||
},
|
||||
|
||||
78
config.js
78
config.js
@@ -14,9 +14,6 @@ var config = {
|
||||
// Domain for authenticated users. Defaults to <domain>.
|
||||
// authdomain: 'jitsi-meet.example.com',
|
||||
|
||||
// Jirecon recording component domain.
|
||||
// jirecon: 'jirecon.jitsi-meet.example.com',
|
||||
|
||||
// Call control component (Jigasi).
|
||||
// call_control: 'callcontrol.jitsi-meet.example.com',
|
||||
|
||||
@@ -67,6 +64,11 @@ var config = {
|
||||
// adjusted to 2.5 Mbps. This takes a value between 0 and 1 which determines
|
||||
// the probability for this to be enabled.
|
||||
// capScreenshareBitrate: 1 // 0 to disable
|
||||
|
||||
// Enable callstats only for a percentage of users.
|
||||
// This takes a value between 0 and 100 which determines the probability for
|
||||
// the callstats to be enabled.
|
||||
// callStatsThreshold: 5 // enable callstats for 5% of the users.
|
||||
},
|
||||
|
||||
// Disables ICE/UDP by filtering out local and remote UDP candidates in
|
||||
@@ -118,6 +120,9 @@ var config = {
|
||||
// Valid values are in the range 6000 to 510000
|
||||
// opusMaxAverageBitrate: 20000,
|
||||
|
||||
// Enables redundancy for Opus
|
||||
// enableOpusRed: false
|
||||
|
||||
// Video
|
||||
|
||||
// Sets the preferred resolution (height) for local video. Defaults to 720.
|
||||
@@ -125,7 +130,7 @@ var config = {
|
||||
|
||||
// How many participants while in the tile view mode, before the receiving video quality is reduced from HD to SD.
|
||||
// Use -1 to disable.
|
||||
// maxFullResolutionParticipants: 2
|
||||
// maxFullResolutionParticipants: 2,
|
||||
|
||||
// w3c spec-compliant video constraints to use for video capture. Currently
|
||||
// used by browsers that return true from lib-jitsi-meet's
|
||||
@@ -161,6 +166,7 @@ var config = {
|
||||
// Note that it's not recommended to do this because simulcast is not
|
||||
// supported when using H.264. For 1-to-1 calls this setting is enabled by
|
||||
// default and can be toggled in the p2p section.
|
||||
// This option has been deprecated, use preferredCodec under videoQuality section instead.
|
||||
// preferH264: true,
|
||||
|
||||
// If set to true, disable H.264 video codec by stripping it out of the
|
||||
@@ -234,6 +240,18 @@ var config = {
|
||||
|
||||
// Specify the settings for video quality optimizations on the client.
|
||||
// videoQuality: {
|
||||
// // Provides a way to prevent a video codec from being negotiated on the JVB connection. The codec specified
|
||||
// // here will be removed from the list of codecs present in the SDP answer generated by the client. If the
|
||||
// // same codec is specified for both the disabled and preferred option, the disable settings will prevail.
|
||||
// // Note that 'VP8' cannot be disabled since it's a mandatory codec, the setting will be ignored in this case.
|
||||
// disabledCodec: 'H264',
|
||||
//
|
||||
// // Provides a way to set a preferred video codec for the JVB connection. If 'H264' is specified here,
|
||||
// // simulcast will be automatically disabled since JVB doesn't support H264 simulcast yet. This will only
|
||||
// // rearrange the the preference order of the codecs in the SDP answer generated by the browser only if the
|
||||
// // preferred codec specified here is present. Please ensure that the JVB offers the specified codec for this
|
||||
// // to take effect.
|
||||
// preferredCodec: 'VP8',
|
||||
//
|
||||
// // Provides a way to configure the maximum bitrates that will be enforced on the simulcast streams for
|
||||
// // video tracks. The keys in the object represent the type of the stream (LD, SD or HD) and the values
|
||||
@@ -244,6 +262,21 @@ var config = {
|
||||
// low: 200000,
|
||||
// standard: 500000,
|
||||
// high: 1500000
|
||||
// },
|
||||
//
|
||||
// // The options can be used to override default thresholds of video thumbnail heights corresponding to
|
||||
// // the video quality levels used in the application. At the time of this writing the allowed levels are:
|
||||
// // 'low' - for the low quality level (180p at the time of this writing)
|
||||
// // 'standard' - for the medium quality level (360p)
|
||||
// // 'high' - for the high quality level (720p)
|
||||
// // The keys should be positive numbers which represent the minimal thumbnail height for the quality level.
|
||||
// //
|
||||
// // With the default config value below the application will use 'low' quality until the thumbnails are
|
||||
// // at least 360 pixels tall. If the thumbnail height reaches 720 pixels then the application will switch to
|
||||
// // the high quality.
|
||||
// minHeightForQualityLvl: {
|
||||
// 360: 'standard,
|
||||
// 720: 'high'
|
||||
// }
|
||||
// },
|
||||
|
||||
@@ -304,11 +337,15 @@ var config = {
|
||||
// 'datachannel'), undefined (treat it as 'datachannel') and false (don't
|
||||
// open any channel).
|
||||
// openBridgeChannel: true,
|
||||
openBridgeChannel: 'websocket',
|
||||
|
||||
|
||||
// UI
|
||||
//
|
||||
|
||||
// Hides lobby button
|
||||
// hideLobbyButton: false,
|
||||
|
||||
// Require users to always specify a display name.
|
||||
// requireDisplayName: true,
|
||||
|
||||
@@ -357,6 +394,10 @@ var config = {
|
||||
// set or the lobby is not enabled.
|
||||
// enableInsecureRoomNameWarning: false,
|
||||
|
||||
// Whether to automatically copy invitation URL after creating a room.
|
||||
// Document should be focused for this option to work
|
||||
// enableAutomaticUrlCopy: false,
|
||||
|
||||
// Stats
|
||||
//
|
||||
|
||||
@@ -420,13 +461,20 @@ var config = {
|
||||
// iceTransportPolicy: 'all',
|
||||
|
||||
// If set to true, it will prefer to use H.264 for P2P calls (if H.264
|
||||
// is supported).
|
||||
// is supported). This setting is deprecated, use preferredCodec instead.
|
||||
// preferH264: true
|
||||
|
||||
// Provides a way to set the video codec preference on the p2p connection. Acceptable
|
||||
// codec values are 'VP8', 'VP9' and 'H264'.
|
||||
// preferredCodec: 'H264',
|
||||
|
||||
// If set to true, disable H.264 video codec by stripping it out of the
|
||||
// SDP.
|
||||
// SDP. This setting is deprecated, use disabledCodec instead.
|
||||
// disableH264: false,
|
||||
|
||||
// Provides a way to prevent a video codec from being negotiated on the p2p connection.
|
||||
// disabledCodec: '',
|
||||
|
||||
// How long we're going to wait, before going back to P2P after the 3rd
|
||||
// participant has left the conference (to filter out page reload).
|
||||
// backToP2PDelay: 5
|
||||
@@ -444,6 +492,12 @@ var config = {
|
||||
// amplitudeAPPKey: '<APP_KEY>'
|
||||
|
||||
// Configuration for the rtcstats server:
|
||||
// By enabling rtcstats server every time a conference is joined the rtcstats
|
||||
// module connects to the provided rtcstatsEndpoint and sends statistics regarding
|
||||
// PeerConnection states along with getStats metrics polled at the specified
|
||||
// interval.
|
||||
// rtcstatsEnabled: true,
|
||||
|
||||
// In order to enable rtcstats one needs to provide a endpoint url.
|
||||
// rtcstatsEndpoint: wss://rtcstats-server-pilot.jitsi.net/,
|
||||
|
||||
@@ -459,6 +513,9 @@ var config = {
|
||||
// ],
|
||||
},
|
||||
|
||||
// Logs that should go be passed through the 'log' event if a handler is defined for it
|
||||
// apiLogLevels: ['warn', 'log', 'error', 'info', 'debug'],
|
||||
|
||||
// Information about the jitsi-meet instance we are connecting to, including
|
||||
// the user region as seen by the server.
|
||||
deploymentInfo: {
|
||||
@@ -581,8 +638,6 @@ var config = {
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
_immediateReloadThreshold
|
||||
autoRecord
|
||||
autoRecordToken
|
||||
debug
|
||||
debugAudioLevels
|
||||
deploymentInfo
|
||||
@@ -605,6 +660,13 @@ var config = {
|
||||
tokenAuthUrl
|
||||
*/
|
||||
|
||||
/**
|
||||
* This property can be used to alter the generated meeting invite links (in combination with a branding domain
|
||||
* which is retrieved internally by jitsi meet) (e.g. https://meet.jit.si/someMeeting
|
||||
* can become https://brandedDomain/roomAlias)
|
||||
*/
|
||||
// brandingRoomAlias: null,
|
||||
|
||||
// List of undocumented settings used in lib-jitsi-meet
|
||||
/**
|
||||
_peerConnStatusOutOfLastNTimeout
|
||||
|
||||
@@ -82,7 +82,7 @@ function checkForAttachParametersAndConnect(id, password, connection) {
|
||||
*/
|
||||
function connect(id, password, roomName) {
|
||||
const connectionConfig = Object.assign({}, config);
|
||||
const { issuer, jwt } = APP.store.getState()['features/base/jwt'];
|
||||
const { jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
// Use Websocket URL for the web app if configured. Note that there is no 'isWeb' check, because there's assumption
|
||||
// that this code executes only on web browsers/electron. This needs to be changed when mobile and web are unified.
|
||||
@@ -94,11 +94,7 @@ function connect(id, password, roomName) {
|
||||
// in future). It's included for the time being for Jitsi Meet and lib-jitsi-meet versions interoperability.
|
||||
connectionConfig.serviceUrl = connectionConfig.bosh = serviceUrl;
|
||||
|
||||
const connection
|
||||
= new JitsiMeetJS.JitsiConnection(
|
||||
null,
|
||||
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
|
||||
connectionConfig);
|
||||
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, connectionConfig);
|
||||
|
||||
if (config.iAmRecorder) {
|
||||
connection.addFeature(DISCO_JIBRI_FEATURE);
|
||||
@@ -211,10 +207,9 @@ export function openConnection({ id, password, retry, roomName }) {
|
||||
|
||||
return connect(id, password, roomName).catch(err => {
|
||||
if (retry) {
|
||||
const { issuer, jwt } = APP.store.getState()['features/base/jwt'];
|
||||
const { jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED
|
||||
&& (!jwt || issuer === 'anonymous')) {
|
||||
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED && !jwt) {
|
||||
return AuthHandler.requestAuth(roomName, connect);
|
||||
}
|
||||
}
|
||||
|
||||
60
css/_connection-status.scss
Normal file
60
css/_connection-status.scss
Normal file
@@ -0,0 +1,60 @@
|
||||
.con-status {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
width: 100%;
|
||||
z-index: $toolbarZ + 3;
|
||||
|
||||
&-container {
|
||||
background: rgba(28, 32, 37, .5);
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
margin: 0 auto;
|
||||
width: 304px;
|
||||
}
|
||||
|
||||
&-header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
&-circle {
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
&--good {
|
||||
background: #31B76A;
|
||||
}
|
||||
|
||||
&--poor {
|
||||
background: #E12D2D;
|
||||
}
|
||||
|
||||
&--non-optimal {
|
||||
background: #E39623;
|
||||
}
|
||||
|
||||
&-arrow {
|
||||
&--up {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&>svg {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&-text {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-details {
|
||||
border-top: 1px solid #5E6D7A;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
#e2ee-section {
|
||||
.title {
|
||||
font-weight: 700;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
font-size: 13px;
|
||||
@@ -13,29 +12,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.key-field {
|
||||
align-items: center;
|
||||
.control-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-top: 15px;
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
color: inherit;
|
||||
flex: 1;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #6FB1EA;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,16 @@
|
||||
margin-bottom: 14px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-error {
|
||||
color: white;
|
||||
background-color: rgba(229, 75, 75, 0.5);
|
||||
width: 100%;
|
||||
padding: 3px;
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin name-placeholder {
|
||||
|
||||
@@ -197,16 +197,9 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.preview-avatar-container {
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
background: #A4B8D1;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
video {
|
||||
|
||||
@@ -102,5 +102,6 @@ $flagsImagePath: "../images/";
|
||||
@import 'premeeting-screens';
|
||||
@import 'e2ee';
|
||||
@import 'responsive';
|
||||
@import 'connection-status';
|
||||
|
||||
/* Modules END */
|
||||
|
||||
2
debian/control
vendored
2
debian/control
vendored
@@ -47,7 +47,7 @@ Description: Prosody configuration for Jitsi Meet
|
||||
|
||||
Package: jitsi-meet-tokens
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, prosody-trunk (>= 1nightly747) | prosody-0.11 | prosody (>= 0.11.2), libssl-dev, luarocks, jitsi-meet-prosody
|
||||
Depends: ${misc:Depends}, prosody-trunk (>= 1nightly747) | prosody-0.11 | prosody (>= 0.11.2), libssl1.0-dev | libssl-dev, luarocks, jitsi-meet-prosody, git
|
||||
Description: Prosody token authentication plugin for Jitsi Meet
|
||||
|
||||
Package: jitsi-meet-turnserver
|
||||
|
||||
8
debian/jitsi-meet-prosody.postrm
vendored
8
debian/jitsi-meet-prosody.postrm
vendored
@@ -45,8 +45,12 @@ case "$1" in
|
||||
rm -rf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.*
|
||||
rm -rf /var/lib/prosody/$JVB_HOSTNAME.*
|
||||
|
||||
# clean created users
|
||||
rm -rf /var/lib/prosody/`echo $JICOFO_AUTH_DOMAIN | sed -e "s/\./%2e/g"`
|
||||
# clean created users, replace '.' with '%2e', replace '-' with '%2d'
|
||||
rm -rf /var/lib/prosody/`echo $JICOFO_AUTH_DOMAIN | sed -e "s/\./%2e/g"| sed -e "s/-/%2d/g"`
|
||||
|
||||
# clean the prosody cert from the trust store
|
||||
rm -rf /usr/local/share/ca-certificates/$JICOFO_AUTH_DOMAIN.*
|
||||
update-ca-certificates -f
|
||||
fi
|
||||
|
||||
# Clear the debconf variable
|
||||
|
||||
11
debian/jitsi-meet-tokens.postinst
vendored
11
debian/jitsi-meet-tokens.postinst
vendored
@@ -48,9 +48,9 @@ case "$1" in
|
||||
db_stop
|
||||
|
||||
if [ -f "$PROSODY_HOST_CONFIG" ] ; then
|
||||
# search for --plugin_paths, if this is not enabled this is the
|
||||
# search for the token auth, if this is not enabled this is the
|
||||
# first time we install tokens package and needs a config change
|
||||
if grep -q "\-\-plugin_paths" "$PROSODY_HOST_CONFIG"; then
|
||||
if ! egrep -q '^\s*authentication\s*=\s*"token"' "$PROSODY_HOST_CONFIG"; then
|
||||
# enable tokens in prosody host config
|
||||
sed -i 's/--plugin_paths/plugin_paths/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/authentication = "anonymous"/authentication = "token"/g' $PROSODY_HOST_CONFIG
|
||||
@@ -58,6 +58,7 @@ case "$1" in
|
||||
sed -i "s/ --app_id=\"example_app_id\"/ app_id=\"$APP_ID\"/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ --app_secret=\"example_app_secret\"/ app_secret=\"$APP_SECRET\"/g" $PROSODY_HOST_CONFIG
|
||||
sed -i 's/ --modules_enabled = { "token_verification" }/ modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
|
||||
sed -i '/^\s*--\s*"token_verification"/ s/--\s*//' $PROSODY_HOST_CONFIG
|
||||
|
||||
# Install luajwt
|
||||
if ! luarocks install luajwtjitsi; then
|
||||
@@ -73,9 +74,9 @@ case "$1" in
|
||||
PRTRUNK_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'prosody-trunk' 2>/dev/null | awk '{print $3}' || true)"
|
||||
PR_VER_INSTALLED=$(dpkg-query -f='${Version}\n' --show prosody 2>/dev/null || true)
|
||||
if [ "$PR10_INSTALL_CHECK" = "installed" ] \
|
||||
|| "$PR10_INSTALL_CHECK" = "unpacked" \
|
||||
|| "$PRTRUNK_INSTALL_CHECK" = "installed" \
|
||||
|| "$PRTRUNK_INSTALL_CHECK" = "unpacked" \
|
||||
|| [ "$PR10_INSTALL_CHECK" = "unpacked" ] \
|
||||
|| [ "$PRTRUNK_INSTALL_CHECK" = "installed" ] \
|
||||
|| [ "$PRTRUNK_INSTALL_CHECK" = "unpacked" ] \
|
||||
|| dpkg --compare-versions "$PR_VER_INSTALLED" lt "0.11" ; then
|
||||
sed -i 's/module:hook_global(/module:hook(/g' /usr/share/jitsi-meet/prosody-plugins/mod_auth_token.lua
|
||||
fi
|
||||
|
||||
3
debian/jitsi-meet-tokens.postrm
vendored
3
debian/jitsi-meet-tokens.postrm
vendored
@@ -37,11 +37,10 @@ case "$1" in
|
||||
APP_SECRET=$RET
|
||||
|
||||
# Revert prosody config
|
||||
sed -i 's/plugin_paths/--plugin_paths/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/authentication = "token"/authentication = "anonymous"/g' $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ app_id=\"$APP_ID\"/ --app_id=\"example_app_id\"/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ app_secret=\"$APP_SECRET\"/ --app_secret=\"example_app_secret\"/g" $PROSODY_HOST_CONFIG
|
||||
sed -i 's/ -- "token_verification"/ "token_verification"/g' $PROSODY_HOST_CONFIG
|
||||
sed -i '/^\s*"token_verification"/ s/"token_verification"/-- "token_verification"/' $PROSODY_HOST_CONFIG
|
||||
|
||||
if [ -x "/etc/init.d/prosody" ]; then
|
||||
invoke-rc.d prosody restart || true
|
||||
|
||||
50
debian/jitsi-meet-turnserver.postinst
vendored
50
debian/jitsi-meet-turnserver.postinst
vendored
@@ -36,26 +36,6 @@ case "$1" in
|
||||
NGINX_CONFIG="/etc/nginx/sites-available/$JVB_HOSTNAME.conf"
|
||||
JITSI_MEET_CONFIG="/etc/jitsi/meet/$JVB_HOSTNAME-config.js"
|
||||
|
||||
NGINX_SITES_ENABLED="/etc/nginx/sites-enabled/"
|
||||
NGINX_CONFIG_ENABLED="${NGINX_SITES_ENABLED}${JVB_HOSTNAME}.conf"
|
||||
NGINX_MULTIPLEXING="true"
|
||||
for site in ${NGINX_SITES_ENABLED}*; do
|
||||
# if it is not a file continue
|
||||
[ -f "${site}" ] || continue
|
||||
# if it is our config skip
|
||||
[ "${site}" != "${NGINX_CONFIG_ENABLED}" ] || continue
|
||||
# check whether other enabled hosts has listen 443
|
||||
if cat ${site} | grep -v "^[[:space:]]*#" | grep listen | grep -q "^.*[[:space:]:]443[;[:space:]].*" ; then
|
||||
# nothing to do
|
||||
echo "------------------------------------------------"
|
||||
echo ""
|
||||
echo "turnserver is listening on tcp 5349 as other nginx sites use port 443"
|
||||
echo ""
|
||||
echo "------------------------------------------------"
|
||||
NGINX_MULTIPLEXING="false"
|
||||
fi
|
||||
done
|
||||
|
||||
# if there was a turn config backup it so we can configure
|
||||
# we cannot recognize at the moment is this a user config or default config when installing coturn
|
||||
if [[ -f $TURN_CONFIG ]] && ! grep -q "jitsi-meet coturn config" "$TURN_CONFIG" ; then
|
||||
@@ -133,19 +113,9 @@ denied-peer-ip=240.0.0.0-255.255.255.255" >> $TURN_CONFIG
|
||||
TURN_SECRET="$RET"
|
||||
|
||||
# no turn config exists, lt's copy template and fill it in
|
||||
PUBLIC_IP=$(dig -4 +short myip.opendns.com a @resolver1.opendns.com) || true
|
||||
if [ -z "$PUBLIC_IP" ] ; then
|
||||
PUBLIC_IP="127.0.0.1"
|
||||
echo "------------------------------------------------"
|
||||
echo "Warning! Could not resolve your external ip address! Error:^"
|
||||
echo "Your turn server will not work till you edit your $TURN_CONFIG config file."
|
||||
echo "You need to set your external ip address in external-ip and restart coturn service."
|
||||
echo "------------------------------------------------"
|
||||
fi
|
||||
cp /usr/share/jitsi-meet-turnserver/turnserver.conf $TURN_CONFIG
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" $TURN_CONFIG
|
||||
sed -i "s/__turnSecret__/$TURN_SECRET/g" $TURN_CONFIG
|
||||
sed -i "s/__external_ip_address__/$PUBLIC_IP/g" $TURN_CONFIG
|
||||
|
||||
# SSL for nginx
|
||||
db_get jitsi-meet/cert-choice
|
||||
@@ -170,18 +140,14 @@ denied-peer-ip=240.0.0.0-255.255.255.255" >> $TURN_CONFIG
|
||||
invoke-rc.d coturn restart || true
|
||||
|
||||
NGINX_STREAM_CONFIG="/etc/nginx/modules-enabled/60-jitsi-meet.conf"
|
||||
if [ $NGINX_MULTIPLEXING = "true" ] && [ ! -f $NGINX_STREAM_CONFIG ] && [ -f $NGINX_CONFIG ] ; then
|
||||
ln -s /usr/share/jitsi-meet-turnserver/jitsi-meet.conf $NGINX_STREAM_CONFIG
|
||||
sed -i "s/listen 443 ssl/listen 4444 ssl http2/g" $NGINX_CONFIG
|
||||
sed -i "s/listen \[\:\:\]\:443 ssl/listen \[\:\:\]\:4444 ssl http2/g" $NGINX_CONFIG
|
||||
invoke-rc.d nginx reload || true
|
||||
else
|
||||
PROSODY_HOST_CONFIG="/etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua"
|
||||
if [ -f $PROSODY_HOST_CONFIG ] ; then
|
||||
# If we are not multiplexing we need to change the port in prosody config
|
||||
sed -i 's/"443"/"5349"/g' $PROSODY_HOST_CONFIG
|
||||
invoke-rc.d prosody restart || true
|
||||
fi
|
||||
if [ -f $NGINX_STREAM_CONFIG ] ; then
|
||||
echo "------------------------------------------------"
|
||||
echo ""
|
||||
echo "You have multiplexing enabled, it is recommended to disable it and migrate to using websockets for the bridge channel."
|
||||
echo "The support for sctp data channels is deprecated and will be dropped at some point."
|
||||
echo "How to do it at: https://jitsi.org/multiplexing-to-bridge-ws-howto"
|
||||
echo ""
|
||||
echo "------------------------------------------------"
|
||||
fi
|
||||
|
||||
# Enable turn server in config.js
|
||||
|
||||
2
debian/jitsi-meet-turnserver.postrm
vendored
2
debian/jitsi-meet-turnserver.postrm
vendored
@@ -24,7 +24,6 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
remove)
|
||||
rm -rf /etc/nginx/modules-enabled/60-jitsi-meet.conf
|
||||
if [ -x "/etc/init.d/nginx" ]; then
|
||||
invoke-rc.d nginx reload || true
|
||||
fi
|
||||
@@ -33,7 +32,6 @@ case "$1" in
|
||||
fi
|
||||
;;
|
||||
purge)
|
||||
rm -rf /etc/nginx/modules-enabled/60-jitsi-meet.conf
|
||||
rm -rf /etc/turnserver.conf
|
||||
if [ -x "/etc/init.d/nginx" ]; then
|
||||
invoke-rc.d nginx reload || true
|
||||
|
||||
@@ -8,7 +8,7 @@ turncredentials_secret = "__turnSecret__";
|
||||
turncredentials = {
|
||||
{ type = "stun", host = "jitmeet.example.com", port = "3478" },
|
||||
{ type = "turn", host = "jitmeet.example.com", port = "3478", transport = "udp" },
|
||||
{ type = "turns", host = "jitmeet.example.com", port = "443", transport = "tcp" }
|
||||
{ type = "turns", host = "jitmeet.example.com", port = "5349", transport = "tcp" }
|
||||
};
|
||||
|
||||
cross_domain_bosh = false;
|
||||
|
||||
@@ -12,7 +12,6 @@ no-tcp-relay
|
||||
no-tcp
|
||||
listening-port=3478
|
||||
tls-listening-port=5349
|
||||
external-ip=__external_ip_address__
|
||||
no-tlsv1
|
||||
no-tlsv1_1
|
||||
# https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=intermediate&openssl=1.1.0g&guideline=5.4
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
# this is jitsi-meet nginx module configuration
|
||||
# this forward all http traffic to the nginx virtual host port
|
||||
# and the rest to the turn server
|
||||
|
||||
#
|
||||
# Multiplexing based on ALPN is DEPRECATED. ALPN does not play well with websockets on some browsers and reverse proxies.
|
||||
# To migrate away from using it read: https://jitsi.org/multiplexing-to-bridge-ws-howto
|
||||
# This file will be removed at some point and if deployment is still using it, will break.
|
||||
#
|
||||
stream {
|
||||
upstream web {
|
||||
server 127.0.0.1:4444;
|
||||
|
||||
@@ -45,8 +45,10 @@ server {
|
||||
error_page 404 /static/404.html;
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/javascript application/json;
|
||||
gzip_types text/plain text/css application/javascript application/json image/x-icon application/octet-stream application/wasm;
|
||||
gzip_vary on;
|
||||
gzip_proxied no-cache no-store private expired auth;
|
||||
gzip_min_length 512;
|
||||
|
||||
location = /config.js {
|
||||
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
|
||||
@@ -61,6 +63,11 @@ server {
|
||||
{
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
alias /usr/share/jitsi-meet/$1/$2;
|
||||
|
||||
# cache all versioned files
|
||||
if ($arg_v) {
|
||||
expires 1y;
|
||||
}
|
||||
}
|
||||
|
||||
# BOSH
|
||||
@@ -80,6 +87,15 @@ server {
|
||||
tcp_nodelay on;
|
||||
}
|
||||
|
||||
# colibri (JVB) websockets for jvb1
|
||||
location ~ ^/colibri-ws/default-id/(.*) {
|
||||
proxy_pass http://127.0.0.1:9090/colibri-ws/default-id/$1$is_args$args;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
tcp_nodelay on;
|
||||
}
|
||||
|
||||
location ~ ^/([^/?&:'"]+)$ {
|
||||
try_files $uri @root_path;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,12 @@ server {
|
||||
ssi on;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/javascript application/json image/x-icon application/octet-stream application/wasm;
|
||||
gzip_vary on;
|
||||
gzip_proxied no-cache no-store private expired auth;
|
||||
gzip_min_length 512;
|
||||
|
||||
# BOSH
|
||||
location /http-bind {
|
||||
proxy_pass http://localhost:5280/http-bind;
|
||||
|
||||
@@ -28,6 +28,12 @@ server {
|
||||
tcp_nodelay on;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/javascript application/json image/x-icon application/octet-stream application/wasm;
|
||||
gzip_vary on;
|
||||
gzip_proxied no-cache no-store private expired auth;
|
||||
gzip_min_length 512;
|
||||
|
||||
location ~ ^/([^/?&:'"]+)$ {
|
||||
try_files $uri @root_path;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@ require_relative '../node_modules/@react-native-community/cli-platform-ios/nativ
|
||||
target 'jitsi-meet' do
|
||||
project 'app/app.xcodeproj'
|
||||
|
||||
pod 'Crashlytics', '~> 3.14.0'
|
||||
pod 'Fabric', '~> 1.10.2'
|
||||
pod 'Firebase/Core', '~> 6.16.0'
|
||||
pod 'Firebase/DynamicLinks', '~> 6.16.0'
|
||||
pod 'Firebase/Analytics', '~> 6.33.0'
|
||||
pod 'Firebase/Crashlytics', '~> 6.33.0'
|
||||
pod 'Firebase/DynamicLinks', '~> 6.33.0'
|
||||
end
|
||||
|
||||
target 'JitsiMeet' do
|
||||
|
||||
185
ios/Podfile.lock
185
ios/Podfile.lock
@@ -11,10 +11,7 @@ PODS:
|
||||
- CocoaLumberjack (3.5.3):
|
||||
- CocoaLumberjack/Core (= 3.5.3)
|
||||
- CocoaLumberjack/Core (3.5.3)
|
||||
- Crashlytics (3.14.0):
|
||||
- Fabric (~> 1.10.2)
|
||||
- DoubleConversion (1.1.6)
|
||||
- Fabric (1.10.2)
|
||||
- FBLazyVector (0.61.5-jitsi.1)
|
||||
- FBReactNativeSpec (0.61.5-jitsi.1):
|
||||
- Folly (= 2018.10.22.00)
|
||||
@@ -23,48 +20,50 @@ PODS:
|
||||
- React-Core (= 0.61.5-jitsi.1)
|
||||
- React-jsi (= 0.61.5-jitsi.1)
|
||||
- ReactCommon/turbomodule/core (= 0.61.5-jitsi.1)
|
||||
- Firebase/Core (6.16.0):
|
||||
- Firebase/Analytics (6.33.0):
|
||||
- Firebase/Core
|
||||
- Firebase/Core (6.33.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseAnalytics (= 6.2.2)
|
||||
- Firebase/CoreOnly (6.16.0):
|
||||
- FirebaseCore (= 6.6.1)
|
||||
- Firebase/DynamicLinks (6.16.0):
|
||||
- FirebaseAnalytics (= 6.8.3)
|
||||
- Firebase/CoreOnly (6.33.0):
|
||||
- FirebaseCore (= 6.10.3)
|
||||
- Firebase/Crashlytics (6.33.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseDynamicLinks (~> 4.0.6)
|
||||
- FirebaseAnalytics (6.2.2):
|
||||
- FirebaseCore (~> 6.6)
|
||||
- FirebaseInstanceID (~> 4.3)
|
||||
- GoogleAppMeasurement (= 6.2.2)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 6.0)
|
||||
- GoogleUtilities/MethodSwizzler (~> 6.0)
|
||||
- GoogleUtilities/Network (~> 6.0)
|
||||
- "GoogleUtilities/NSData+zlib (~> 6.0)"
|
||||
- nanopb (= 0.3.9011)
|
||||
- FirebaseAnalyticsInterop (1.5.0)
|
||||
- FirebaseCore (6.6.1):
|
||||
- FirebaseCoreDiagnostics (~> 1.2)
|
||||
- FirebaseCoreDiagnosticsInterop (~> 1.2)
|
||||
- GoogleUtilities/Environment (~> 6.5)
|
||||
- GoogleUtilities/Logger (~> 6.5)
|
||||
- FirebaseCoreDiagnostics (1.2.2):
|
||||
- FirebaseCoreDiagnosticsInterop (~> 1.2)
|
||||
- GoogleDataTransportCCTSupport (~> 2.0)
|
||||
- GoogleUtilities/Environment (~> 6.5)
|
||||
- GoogleUtilities/Logger (~> 6.5)
|
||||
- nanopb (~> 0.3.901)
|
||||
- FirebaseCoreDiagnosticsInterop (1.2.0)
|
||||
- FirebaseDynamicLinks (4.0.8):
|
||||
- FirebaseAnalyticsInterop (~> 1.3)
|
||||
- FirebaseCore (~> 6.2)
|
||||
- FirebaseInstallations (1.1.1):
|
||||
- FirebaseCore (~> 6.6)
|
||||
- GoogleUtilities/UserDefaults (~> 6.5)
|
||||
- FirebaseCrashlytics (~> 4.6.1)
|
||||
- Firebase/DynamicLinks (6.33.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseDynamicLinks (~> 4.3.1)
|
||||
- FirebaseAnalytics (6.8.3):
|
||||
- FirebaseCore (~> 6.10)
|
||||
- FirebaseInstallations (~> 1.6)
|
||||
- GoogleAppMeasurement (= 6.8.3)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 6.7)
|
||||
- GoogleUtilities/MethodSwizzler (~> 6.7)
|
||||
- GoogleUtilities/Network (~> 6.7)
|
||||
- "GoogleUtilities/NSData+zlib (~> 6.7)"
|
||||
- nanopb (~> 1.30906.0)
|
||||
- FirebaseCore (6.10.3):
|
||||
- FirebaseCoreDiagnostics (~> 1.6)
|
||||
- GoogleUtilities/Environment (~> 6.7)
|
||||
- GoogleUtilities/Logger (~> 6.7)
|
||||
- FirebaseCoreDiagnostics (1.7.0):
|
||||
- GoogleDataTransport (~> 7.4)
|
||||
- GoogleUtilities/Environment (~> 6.7)
|
||||
- GoogleUtilities/Logger (~> 6.7)
|
||||
- nanopb (~> 1.30906.0)
|
||||
- FirebaseCrashlytics (4.6.1):
|
||||
- FirebaseCore (~> 6.10)
|
||||
- FirebaseInstallations (~> 1.6)
|
||||
- GoogleDataTransport (~> 7.2)
|
||||
- nanopb (~> 1.30906.0)
|
||||
- PromisesObjC (~> 1.2)
|
||||
- FirebaseDynamicLinks (4.3.1):
|
||||
- FirebaseCore (~> 6.10)
|
||||
- FirebaseInstallations (1.7.0):
|
||||
- FirebaseCore (~> 6.10)
|
||||
- GoogleUtilities/Environment (~> 6.7)
|
||||
- GoogleUtilities/UserDefaults (~> 6.7)
|
||||
- PromisesObjC (~> 1.2)
|
||||
- FirebaseInstanceID (4.3.2):
|
||||
- FirebaseCore (~> 6.6)
|
||||
- FirebaseInstallations (~> 1.0)
|
||||
- GoogleUtilities/Environment (~> 6.5)
|
||||
- GoogleUtilities/UserDefaults (~> 6.5)
|
||||
- Folly (2018.10.22.00):
|
||||
- boost-for-react-native
|
||||
- DoubleConversion
|
||||
@@ -75,37 +74,36 @@ PODS:
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- glog (0.3.5)
|
||||
- GoogleAppMeasurement (6.2.2):
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 6.0)
|
||||
- GoogleUtilities/MethodSwizzler (~> 6.0)
|
||||
- GoogleUtilities/Network (~> 6.0)
|
||||
- "GoogleUtilities/NSData+zlib (~> 6.0)"
|
||||
- nanopb (= 0.3.9011)
|
||||
- GoogleDataTransport (5.1.0)
|
||||
- GoogleDataTransportCCTSupport (2.0.1):
|
||||
- GoogleDataTransport (~> 5.1)
|
||||
- nanopb (~> 0.3.901)
|
||||
- GoogleAppMeasurement (6.8.3):
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 6.7)
|
||||
- GoogleUtilities/MethodSwizzler (~> 6.7)
|
||||
- GoogleUtilities/Network (~> 6.7)
|
||||
- "GoogleUtilities/NSData+zlib (~> 6.7)"
|
||||
- nanopb (~> 1.30906.0)
|
||||
- GoogleDataTransport (7.4.0):
|
||||
- nanopb (~> 1.30906.0)
|
||||
- GoogleSignIn (5.0.1):
|
||||
- AppAuth (~> 1.2)
|
||||
- GTMAppAuth (~> 1.0)
|
||||
- GTMSessionFetcher/Core (~> 1.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (6.5.2):
|
||||
- GoogleUtilities/AppDelegateSwizzler (6.7.2):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Network
|
||||
- GoogleUtilities/Environment (6.5.2)
|
||||
- GoogleUtilities/Logger (6.5.2):
|
||||
- GoogleUtilities/Environment (6.7.2):
|
||||
- PromisesObjC (~> 1.2)
|
||||
- GoogleUtilities/Logger (6.7.2):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/MethodSwizzler (6.5.2):
|
||||
- GoogleUtilities/MethodSwizzler (6.7.2):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Network (6.5.2):
|
||||
- GoogleUtilities/Network (6.7.2):
|
||||
- GoogleUtilities/Logger
|
||||
- "GoogleUtilities/NSData+zlib"
|
||||
- GoogleUtilities/Reachability
|
||||
- "GoogleUtilities/NSData+zlib (6.5.2)"
|
||||
- GoogleUtilities/Reachability (6.5.2):
|
||||
- "GoogleUtilities/NSData+zlib (6.7.2)"
|
||||
- GoogleUtilities/Reachability (6.7.2):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/UserDefaults (6.5.2):
|
||||
- GoogleUtilities/UserDefaults (6.7.2):
|
||||
- GoogleUtilities/Logger
|
||||
- GTMAppAuth (1.0.0):
|
||||
- AppAuth/Core (~> 1.0)
|
||||
@@ -115,13 +113,13 @@ PODS:
|
||||
- GTMSessionFetcher/Core (1.2.2)
|
||||
- GTMSessionFetcher/Full (1.2.2):
|
||||
- GTMSessionFetcher/Core (= 1.2.2)
|
||||
- nanopb (0.3.9011):
|
||||
- nanopb/decode (= 0.3.9011)
|
||||
- nanopb/encode (= 0.3.9011)
|
||||
- nanopb/decode (0.3.9011)
|
||||
- nanopb/encode (0.3.9011)
|
||||
- nanopb (1.30906.0):
|
||||
- nanopb/decode (= 1.30906.0)
|
||||
- nanopb/encode (= 1.30906.0)
|
||||
- nanopb/decode (1.30906.0)
|
||||
- nanopb/encode (1.30906.0)
|
||||
- ObjectiveDropboxOfficial (3.9.4)
|
||||
- PromisesObjC (1.2.8)
|
||||
- PromisesObjC (1.2.10)
|
||||
- RCTRequired (0.61.5-jitsi.1)
|
||||
- RCTTypeSafety (0.61.5-jitsi.1):
|
||||
- FBLazyVector (= 0.61.5-jitsi.1)
|
||||
@@ -285,7 +283,7 @@ PODS:
|
||||
- React-cxxreact (= 0.61.5-jitsi.1)
|
||||
- React-jsi (= 0.61.5-jitsi.1)
|
||||
- React-jsinspector (0.61.5-jitsi.1)
|
||||
- react-native-background-timer (2.1.1):
|
||||
- react-native-background-timer (2.4.0):
|
||||
- React
|
||||
- react-native-calendar-events (2.0.0):
|
||||
- React
|
||||
@@ -295,7 +293,7 @@ PODS:
|
||||
- React
|
||||
- react-native-webrtc (1.84.0):
|
||||
- React
|
||||
- react-native-webview (7.4.1):
|
||||
- react-native-webview (10.9.0):
|
||||
- React
|
||||
- React-RCTActionSheet (0.61.5-jitsi.1):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.61.5-jitsi.1)
|
||||
@@ -373,13 +371,12 @@ DEPENDENCIES:
|
||||
- Amplitude-iOS (~> 4.0.4)
|
||||
- BVLinearGradient (from `../node_modules/react-native-linear-gradient`)
|
||||
- CocoaLumberjack (~> 3.5.3)
|
||||
- Crashlytics (~> 3.14.0)
|
||||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||
- Fabric (~> 1.10.2)
|
||||
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector/`)
|
||||
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec/`)
|
||||
- Firebase/Core (~> 6.16.0)
|
||||
- Firebase/DynamicLinks (~> 6.16.0)
|
||||
- Firebase/Analytics (~> 6.33.0)
|
||||
- Firebase/Crashlytics (~> 6.33.0)
|
||||
- Firebase/DynamicLinks (~> 6.33.0)
|
||||
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||
- ObjectiveDropboxOfficial (~> 3.9.4)
|
||||
@@ -424,20 +421,15 @@ SPEC REPOS:
|
||||
- AppAuth
|
||||
- boost-for-react-native
|
||||
- CocoaLumberjack
|
||||
- Crashlytics
|
||||
- Fabric
|
||||
- Firebase
|
||||
- FirebaseAnalytics
|
||||
- FirebaseAnalyticsInterop
|
||||
- FirebaseCore
|
||||
- FirebaseCoreDiagnostics
|
||||
- FirebaseCoreDiagnosticsInterop
|
||||
- FirebaseCrashlytics
|
||||
- FirebaseDynamicLinks
|
||||
- FirebaseInstallations
|
||||
- FirebaseInstanceID
|
||||
- GoogleAppMeasurement
|
||||
- GoogleDataTransport
|
||||
- GoogleDataTransportCCTSupport
|
||||
- GoogleSignIn
|
||||
- GoogleUtilities
|
||||
- GTMAppAuth
|
||||
@@ -530,32 +522,27 @@ SPEC CHECKSUMS:
|
||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
|
||||
CocoaLumberjack: 2f44e60eb91c176d471fdba43b9e3eae6a721947
|
||||
Crashlytics: 540b7e5f5da5a042647227a5e3ac51d85eed06df
|
||||
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
|
||||
Fabric: 706c8b8098fff96c33c0db69cbf81f9c551d0d74
|
||||
FBLazyVector: 4a5251159a3ed05dc11cc8b74cf937869935814b
|
||||
FBReactNativeSpec: 6fa602a20993212cc9877a81838578ffb0008bc9
|
||||
Firebase: 497158b816d0a86fc31babbd05546fcd7e6083ff
|
||||
FirebaseAnalytics: cf95d3aab897612783020fbd98401d5366f135ee
|
||||
FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae
|
||||
FirebaseCore: 85064903ed6c28e47fec9c7bd149d94ba1b6b6e7
|
||||
FirebaseCoreDiagnostics: e9b4cd8ba60dee0f2d13347332e4b7898cca5b61
|
||||
FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850
|
||||
FirebaseDynamicLinks: 417dc6dbb6013233c77558290d73296f429656a6
|
||||
FirebaseInstallations: acb3216eb9784d3b1d2d2d635ff74fa892cc0c44
|
||||
FirebaseInstanceID: 7ee0d6777013bb952f377b41965bf132b6a075be
|
||||
Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5
|
||||
FirebaseAnalytics: 5dd088bd2e67bb9d13dbf792d1164ceaf3052193
|
||||
FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd
|
||||
FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1
|
||||
FirebaseCrashlytics: 5777d3462fb8c3ab9e80a2473bd7d667a2e8411c
|
||||
FirebaseDynamicLinks: 6eac37d86910382eafb6315d952cc44c9e176094
|
||||
FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2
|
||||
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
|
||||
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
|
||||
GoogleAppMeasurement: d0560d915abf15e692e8538ba1d58442217b6aff
|
||||
GoogleDataTransport: b29a21d813e906014ca16c00897827e40e4a24ab
|
||||
GoogleDataTransportCCTSupport: 6f15a89b0ca35d6fa523e1f752ef818588885988
|
||||
GoogleAppMeasurement: 966e88df9d19c15715137bb2ddaf52373f111436
|
||||
GoogleDataTransport: b7f406340a291370045a270c599e53c6fa6ec20f
|
||||
GoogleSignIn: 3a51b9bb8e48b635fd7f4272cee06ca260345b86
|
||||
GoogleUtilities: ad0f3b691c67909d03a3327cc205222ab8f42e0e
|
||||
GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3
|
||||
GTMAppAuth: 4deac854479704f348309e7b66189e604cf5e01e
|
||||
GTMSessionFetcher: 61bb0f61a4cb560030f1222021178008a5727a23
|
||||
nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd
|
||||
nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc
|
||||
ObjectiveDropboxOfficial: a5afefc83f6467c42c45f2253f583f2ad1ffc701
|
||||
PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6
|
||||
PromisesObjC: b14b1c6b68e306650688599de8a45e49fae81151
|
||||
RCTRequired: f63dd90a89a60602acdd44c42e5d2645ca60ab79
|
||||
RCTTypeSafety: 24a3c6d55684046ed550b1d0ef083a9bf71c8bd4
|
||||
React: 71c5a51135f291c3b32c0b558e167b858ae50e84
|
||||
@@ -565,12 +552,12 @@ SPEC CHECKSUMS:
|
||||
React-jsi: 4f35c1a2273d193a80c1c3831c808413840c260c
|
||||
React-jsiexecutor: de1c37cf59ae9adcbf2be82eea0e090dc3f3205e
|
||||
React-jsinspector: b76c4e84a7833bb4c90549d59ed53ec299ff912b
|
||||
react-native-background-timer: 0d34748e53a972507c66963490c775321a88f6f2
|
||||
react-native-background-timer: e0384ea2fa5a98f67f84f9c4dc274260ddd674ed
|
||||
react-native-calendar-events: 1442fad71a00388f933cfa25512588fec300fcf8
|
||||
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
||||
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
|
||||
react-native-webrtc: 9268ae9a2bc9730796b0968d012327e92c392adf
|
||||
react-native-webview: 4dbc1d2a4a6b9c5e9e723c62651917aa2b5e579e
|
||||
react-native-webview: 6ee7868ca8eba635dbf7963986d1ab7959da0391
|
||||
React-RCTActionSheet: b72ddbfbe15b44ce691d128e4b582f4bb9abb540
|
||||
React-RCTAnimation: cfaefba5024499d336b76ab850e6bd33b232b5e3
|
||||
React-RCTBlob: c427e643bef82999deeab97489ba43298ecfbe24
|
||||
@@ -589,6 +576,6 @@ SPEC CHECKSUMS:
|
||||
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
|
||||
Yoga: 7b4209fda2441f99d54dd6cf4c82b094409bb68f
|
||||
|
||||
PODFILE CHECKSUM: 082858daebbe170e7a490de433e7f2a99e0c3701
|
||||
PODFILE CHECKSUM: 224e84629bf45ae487c4ebc66faf33ec8304fb67
|
||||
|
||||
COCOAPODS: 1.9.1
|
||||
COCOAPODS: 1.9.3
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
#import "Types.h"
|
||||
#import "ViewController.h"
|
||||
|
||||
@import Crashlytics;
|
||||
@import Fabric;
|
||||
@import Firebase;
|
||||
@import JitsiMeet;
|
||||
|
||||
@@ -48,10 +46,11 @@
|
||||
}];
|
||||
|
||||
// Initialize Crashlytics and Firebase if a valid GoogleService-Info.plist file was provided.
|
||||
if ([FIRUtilities appContainsRealServiceInfoPlist] && ![jitsiMeet isCrashReportingDisabled]) {
|
||||
NSLog(@"Enabling Crashlytics and Firebase");
|
||||
if ([FIRUtilities appContainsRealServiceInfoPlist]) {
|
||||
NSLog(@"Enabling Firebase");
|
||||
[FIRApp configure];
|
||||
[Fabric with:@[[Crashlytics class]]];
|
||||
// Crashlytics defaults to disabled wirth the FirebaseCrashlyticsCollectionEnabled Info.plist key.
|
||||
[[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:![jitsiMeet isCrashReportingDisabled]];
|
||||
}
|
||||
|
||||
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>20.4.0</string>
|
||||
<string>20.5.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -66,10 +66,10 @@
|
||||
<string>See your scheduled meetings in the app.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Participate in meetings with video.</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Participate in meetings with voice.</string>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Local network is used for establishing Peer-to-Peer connections.</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>org.jitsi.JitsiMeet.ios.conference</string>
|
||||
@@ -99,7 +99,7 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>firebase_crashlytics_collection_enabled</key>
|
||||
<key>FirebaseCrashlyticsCollectionEnabled</key>
|
||||
<string>false</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>20.4.0</string>
|
||||
<string>20.5.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>20.4.0</string>
|
||||
<string>20.5.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.10.0</string>
|
||||
<string>2.11.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
module.exports = {
|
||||
moduleFileExtensions: [
|
||||
'js'
|
||||
],
|
||||
testMatch: [
|
||||
'<rootDir>/react/**/?(*.)+(test)?(.web).js?(x)'
|
||||
],
|
||||
verbose: true
|
||||
};
|
||||
@@ -234,7 +234,7 @@
|
||||
"micPermissionDeniedError": "Die Berechtigung zur Verwendung des Mikrofons wurde nicht erteilt. Sie können trotzdem an der Konferenz teilnehmen, aber die anderen Teilnehmer können Sie nicht hören. Verwenden Sie die Kamera-Schaltfläche in der Adressleiste, um die Berechtigungen zu erteilen.",
|
||||
"micUnknownError": "Das Mikrofon kann aus einem unbekannten Grund nicht verwendet werden.",
|
||||
"muteEveryoneElseDialog": "Einmal stummgeschaltet, können Sie deren Stummschaltung nicht mehr beenden, aber sie können ihre Stummschaltung jederzeit selbst beenden.",
|
||||
"muteEveryoneElseTitle": "Alle außer {{whom}} stummschaten?",
|
||||
"muteEveryoneElseTitle": "Alle außer {{whom}} stummschalten?",
|
||||
"muteEveryoneDialog": "Wollen Sie wirklich alle stummschalten? Sie können deren Stummschaltung nicht mehr beenden, aber sie können ihre Stummschaltung jederzeit selbst beenden.",
|
||||
"muteEveryoneTitle": "Alle stummschalten?",
|
||||
"muteEveryoneSelf": "sich selbst",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"addContacts": "Inviter vos contacts",
|
||||
"copyInvite": "Copier l'invitation à la réunion",
|
||||
"copyLink": "Copier le lien de la réunion",
|
||||
"copyStream": "Copier le lien de diffision en direct",
|
||||
"copyStream": "Copier le lien de diffusion en direct",
|
||||
"countryNotSupported": "Cette destination n'est pas actuellement supportée.",
|
||||
"countryReminder": "Appel hors des États-Unis ? Veuillez débuter par le code du pays !",
|
||||
"defaultEmail": "Votre email par défaut",
|
||||
@@ -139,6 +139,9 @@
|
||||
"description": "Rien ne s'est passé ? Nous avons essayé de lancer votre réunion dans l'application de bureau {{app}}. Essayez à nouveau ou lancez-la dans l'application web {{app}}.",
|
||||
"descriptionWithoutWeb": "Rien ne s'est passé ? Nous avons essayé de démarrer votre réunion dans l'application bureau {{app}}.",
|
||||
"downloadApp": "Télécharger l'application",
|
||||
"ifDoNotHaveApp": "Si vous n'avez pas encore l'application:",
|
||||
"ifHaveApp": "Si vous avez déjà installé l'application:",
|
||||
"joinInApp": "Rejoindre la réunion en utilisant l'application",
|
||||
"launchWebButton": "Lancer dans le navigateur",
|
||||
"openApp": "Continuer vers l'application",
|
||||
"title": "Lancement de votre réunion dans {{app}} en cours...",
|
||||
@@ -194,7 +197,7 @@
|
||||
"done": "Terminé",
|
||||
"enterDisplayName": "Merci de saisir votre nom ici",
|
||||
"error": "Erreur",
|
||||
"grantModeratorDialog": "Êtes vous sûr de vouloir rendre ce participant modérateur?",
|
||||
"grantModeratorDialog": "Êtes-vous sûr de vouloir rendre ce participant modérateur?",
|
||||
"grantModeratorTitle": "Nommer modérateur",
|
||||
"externalInstallationMsg": "Vous devez installer notre extension de partage de bureau.",
|
||||
"externalInstallationTitle": "Extension requise",
|
||||
@@ -230,7 +233,7 @@
|
||||
"micUnknownError": "Vous ne pouvez pas utiliser le microphone pour une raison inconnue.",
|
||||
"muteEveryoneElseDialog": "Une fois leur micro coupé, vous ne pourrez plus le réactiver, mais ils pourront l'activer par eux-mêmes à tout moment.",
|
||||
"muteEveryoneElseTitle": "Couper le micro de tout le monde sauf de {{whom}} ?",
|
||||
"muteEveryoneDialog": "Etes-vous sûr de vouloir couper les micros de tout le monde ? Vous ne pourrez plus réactiver leur micro, mais ils pourront l'activer par eux-mêmes à tout moment.",
|
||||
"muteEveryoneDialog": "Êtes-vous sûr de vouloir couper les micros de tout le monde ? Vous ne pourrez plus réactiver leur micro, mais ils pourront l'activer par eux-mêmes à tout moment.",
|
||||
"muteEveryoneTitle": "Couper le micro de tout le monde ?",
|
||||
"muteEveryoneSelf": "vous",
|
||||
"muteEveryoneStartMuted": "Tout le monde démarre avec le micro coupé",
|
||||
@@ -697,6 +700,8 @@
|
||||
"hangup": "Quitter",
|
||||
"help": "Aide",
|
||||
"invite": "Inviter des participants",
|
||||
"lobbyButtonDisable": "Désactiver le contrôle des participant·e·s",
|
||||
"lobbyButtonEnable": "Activer le contrôle des participant·e·s",
|
||||
"login": "Connexion",
|
||||
"logout": "Déconnexion",
|
||||
"lowerYourHand": "Baisser la main",
|
||||
|
||||
1025
lang/main-kab.json
1025
lang/main-kab.json
File diff suppressed because it is too large
Load Diff
@@ -1,60 +1,86 @@
|
||||
{
|
||||
"addPeople": {
|
||||
"add": "초대",
|
||||
"countryNotSupported": "아직 해당 지역을 지원하지 않습니다",
|
||||
"addContacts": "연락처로 초대하세요",
|
||||
"copyInvite": "호의 초대 복사",
|
||||
"copyLink": "회의 링크 복사",
|
||||
"copyStream": "라이브 스트리밍 링크 복사",
|
||||
"countryNotSupported": "아직 해당 지역을 지원하지 않습니다.",
|
||||
"countryReminder": "미국 이외의 지역으로 전화하시겠습니까? 국가 번호로 시작해야합니다!",
|
||||
"disabled": "사람들을 초대 할 수 없습니다",
|
||||
"failedToAdd": "",
|
||||
"footerText": "",
|
||||
"defaultEmail": "기본 이메일",
|
||||
"disabled": "사람들을 초대 할 수 없습니다.",
|
||||
"failedToAdd": "참가자를 추가하지 못했습니다.",
|
||||
"footerText": "전화 걸기가 비활성화되었습니다.",
|
||||
"googleEmail": "Google 이메일",
|
||||
"inviteMoreHeader": "회의에 혼자 참여하고 있습니다.",
|
||||
"inviteMoreMailSubject": "{{appName}} 회의에 참여하세요",
|
||||
"inviteMorePrompt": "더 많은 사람을 초대하세요",
|
||||
"linkCopied": "링크가 클립보드에 복사되었습니다.",
|
||||
"loading": "사람 및 전화번호 검색",
|
||||
"loadingNumber": "전화번호 확인 중",
|
||||
"loadingPeople": "초대할 사람 찾기",
|
||||
"noResults": "일치하는 검색 결과 없음",
|
||||
"noValidNumbers": "전화 번호를 입력하십시오.",
|
||||
"outlookEmail": "Outlook 이메일",
|
||||
"searchNumbers": "전화번호 추가",
|
||||
"searchPeople": "인명 검색",
|
||||
"searchPeopleAndNumbers": "인명 검색 또는 전화번호 추가",
|
||||
"shareInvite": "회의 초대 공유",
|
||||
"shareLink": "다른 사람을 초대하려면 회의 링크를 공유하세요.",
|
||||
"shareStream": "라이브 스트리밍 링크 공유",
|
||||
"telephone": "전화: {{number}}",
|
||||
"title": "이 회의에 사람들을 초대하십시오"
|
||||
"title": "이 회의에 사람들을 초대하십시오",
|
||||
"yahooEmail": "Yahoo 이메일"
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "블루투스",
|
||||
"headphones": "헤드폰",
|
||||
"phone": "폰",
|
||||
"speaker": "스피커"
|
||||
"speaker": "스피커",
|
||||
"none": "사용 가능한 오디오 장치가 없습니다."
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "음성 전용"
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "",
|
||||
"confirmAddLink": "",
|
||||
"addMeetingURL": "회의 링크 추가",
|
||||
"confirmAddLink": "이 이벤트에 Jitsi 링크를 추가 하시겠습니까?",
|
||||
"error": {
|
||||
"appConfiguration": "",
|
||||
"generic": "",
|
||||
"notSignedIn": ""
|
||||
"appConfiguration": "캘린더가 제대로 구성되지 않았습니다.",
|
||||
"generic": "오류가 발생했습니다. 캘린더 설정을 확인하거나 캘린더를 새로 고침 해보세요.",
|
||||
"notSignedIn": "캘린더 이벤트를 보기 위해 인증하는 동안 오류가 발생했습니다. 캘린더 설정을 확인하고 다시 로그인하십시오."
|
||||
},
|
||||
"join": "",
|
||||
"joinTooltip": "",
|
||||
"join": "참여",
|
||||
"joinTooltip": "회의에 참여하세요",
|
||||
"nextMeeting": "다음 회의",
|
||||
"noEvents": "",
|
||||
"ongoingMeeting": "",
|
||||
"noEvents": "예정된 예정된 이벤트가 없습니다.",
|
||||
"ongoingMeeting": "진행중인 회의",
|
||||
"permissionButton": "설정 열기",
|
||||
"permissionMessage": "앱에 회의를 나열하려면 캘린더 권한이 필요합니다",
|
||||
"refresh": "",
|
||||
"today": ""
|
||||
"refresh": "달력 새로고침",
|
||||
"today": "오늘"
|
||||
},
|
||||
"chat": {
|
||||
"error": "",
|
||||
"messagebox": "",
|
||||
"error": "오류 : 메시지가 전송되지 않았습니다. 이유 : {{error}}",
|
||||
"fieldPlaceHolder": "메세지를 여기에 입력하세요",
|
||||
"messagebox": "메시지 입력",
|
||||
"messageTo": "{{recipient}}에게 보내는 비공개 메시지",
|
||||
"noMessagesMessage": "아직 회의에 메시지가 없습니다. 여기서 대화를 시작하세요!",
|
||||
"nickname": {
|
||||
"popover": "닉네임을 선택하세요",
|
||||
"title": ""
|
||||
"title": "채팅에서 사용할 닉네임을 입력하세요"
|
||||
},
|
||||
"title": ""
|
||||
"privateNotice": "{{recipient}}에게 보내는 비공개 메시지",
|
||||
"title": "채팅",
|
||||
"you": "당신"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"installExtensionText": "Google 캘린더 및 Office 365 확장 프로그램을 설치합니다.",
|
||||
"buttonText": "Chrome 확장 프로그램을 설치합니다.",
|
||||
"dontShowAgain": "다시 보지 않기"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": ""
|
||||
"joiningRoom": "회의에 연결 중 ..."
|
||||
},
|
||||
"connection": {
|
||||
"ATTACHED": "첨부",
|
||||
@@ -66,7 +92,10 @@
|
||||
"DISCONNECTED": "연결 끊김",
|
||||
"DISCONNECTING": "연결 종료 중",
|
||||
"ERROR": "에러",
|
||||
"RECONNECTING": "네트워크 문제가 발생했습니다. 다시 연결 중..."
|
||||
"RECONNECTING": "네트워크 문제가 발생했습니다. 다시 연결 중...",
|
||||
"GET_SESSION_ID_ERROR": "세션 ID 가져 오기 오류 : {{code}}",
|
||||
"GOT_SESSION_ID": "세션 ID를 가져 오는 중 ... 완료",
|
||||
"LOW_BANDWIDTH": "대역폭을 절약하기 위해 {{displayName}}의 동영상이 중지되었습니다."
|
||||
},
|
||||
"connectionindicator": {
|
||||
"address": "주소:",
|
||||
@@ -95,15 +124,18 @@
|
||||
"turn": " (turn)"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "",
|
||||
"today": "",
|
||||
"yesterday": ""
|
||||
"earlier": "일찍이",
|
||||
"today": "오늘",
|
||||
"yesterday": "어제"
|
||||
},
|
||||
"deepLinking": {
|
||||
"appNotInstalled": "중계 서비스에 참여하려면 모바일 앱 설치가 필요합니다",
|
||||
"appNotInstalled": "회의에 참여하려면 모바일 앱 설치가 필요합니다",
|
||||
"description": "{{app}} 데스크톱 앱에서 회의를 시작했습니다. {{app}} 웹 응용 프로그램에서 다시 시도하거나 실행하십시오.",
|
||||
"descriptionWithoutWeb": "",
|
||||
"downloadApp": "앱 다운로드",
|
||||
"ifDoNotHaveApp": "앱이 설치되지 않은 경우:",
|
||||
"ifHaveApp": "앱이 설치되어 있는 경우:",
|
||||
"joinInApp": "앱을 사용하여 회의에 참여하세요.",
|
||||
"launchWebButton": "웹에서 실행",
|
||||
"openApp": "방으로 이동하기",
|
||||
"title": "{{app}}에서 회의 시작…",
|
||||
@@ -126,8 +158,9 @@
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "실시간 스트리밍:"
|
||||
},
|
||||
"add": "추가",
|
||||
"allow": "허락",
|
||||
"alreadySharedVideoMsg": "",
|
||||
"alreadySharedVideoMsg": "다른 참가자가 이미 비디오를 공유하고 있습니다. 이 회의는 한 번에 하나의 공유 비디오 만 허용합니다.",
|
||||
"alreadySharedVideoTitle": "한 번에 하나의 공유 비디오 만 허용됩니다",
|
||||
"applicationWindow": "응용 프로그램 창",
|
||||
"Back": "뒤로가기",
|
||||
@@ -145,64 +178,64 @@
|
||||
"conferenceReloadMsg": "문제를 해결하려고 노력하고 있습니다. {{seconds}} 초 안에 다시 연결중입니다.",
|
||||
"conferenceReloadTitle": "불행하게도 문제가 발생했습니다",
|
||||
"confirm": "확인",
|
||||
"confirmNo": "",
|
||||
"confirmYes": "",
|
||||
"confirmNo": "아니요",
|
||||
"confirmYes": "예",
|
||||
"connectError": "죄송합니다. 문제가 발생하여 회의에 연결할 수 없습니다",
|
||||
"connectErrorWithMsg": "죄송합니다. 뭔가 잘못되어 회의에 연결할 수 없습니다: {{msg}}",
|
||||
"connecting": "연결 중",
|
||||
"contactSupport": "지원 연락처",
|
||||
"copy": "복사",
|
||||
"dismiss": "",
|
||||
"displayNameRequired": "",
|
||||
"displayNameRequired": "당신의 이름은 무엇입니까?",
|
||||
"done": "완료",
|
||||
"enterDisplayName": "",
|
||||
"enterDisplayName": "당신의 이름을 입력해주세요.",
|
||||
"error": "에러",
|
||||
"externalInstallationMsg": "데스크톱 공유 확장 프로그램을 설치해야합니다",
|
||||
"externalInstallationTitle": "확장 프로그램이 필요합니다",
|
||||
"goToStore": "웹 스토어로 이동",
|
||||
"gracefulShutdown": "서비스는 현재 유지 관리를 위해 중단되었습니다. 나중에 다시 시도 해주십시오.",
|
||||
"IamHost": "내가 호스트",
|
||||
"incorrectRoomLockPassword": "",
|
||||
"incorrectRoomLockPassword": "잘못된 비밀번호",
|
||||
"incorrectPassword": "잘못된 사용자 이름 또는 비밀번호",
|
||||
"inlineInstallationMsg": "데스크톱 공유 확장 프로그램을 설치해야합니다",
|
||||
"inlineInstallExtension": "지금 설치",
|
||||
"internalError": "죄송합니다. 뭔가 잘못 됐습니다. 다음 오류가 발생했습니다: {{error}}",
|
||||
"internalErrorTitle": "내부 에러",
|
||||
"kickMessage": "",
|
||||
"kickParticipantButton": "",
|
||||
"kickParticipantDialog": "",
|
||||
"kickParticipantTitle": "",
|
||||
"kickTitle": "",
|
||||
"kickMessage": "자세한 내용은 {{participantDisplayName}}에 문의하세요.",
|
||||
"kickParticipantButton": "추방",
|
||||
"kickParticipantDialog": "이 참가자를 정말 추방 하시겠습니까?",
|
||||
"kickParticipantTitle": "이 참가자를 추방 하시겠습니까?",
|
||||
"kickTitle": "{{participantDisplayName}} 님이 회의에서 퇴장했습니다.",
|
||||
"liveStreaming": "실시간 스트리밍",
|
||||
"liveStreamingDisabledForGuestTooltip": "",
|
||||
"liveStreamingDisabledTooltip": "",
|
||||
"liveStreamingDisabledForGuestTooltip": "게스트는 라이브 스트리밍을 시작할 수 없습니다.",
|
||||
"liveStreamingDisabledTooltip": "라이브 스트림 시작이 비활성화되었습니다.",
|
||||
"lockMessage": "회의를 비공개하지 못했습니다",
|
||||
"lockRoom": "",
|
||||
"lockRoom": "회의 추가 $t(lockRoomPasswordUppercase)",
|
||||
"lockTitle": "비공개 실패",
|
||||
"logoutQuestion": "로그 아웃하고 컨퍼런스를 중지하시겠습니까?",
|
||||
"logoutTitle": "로그아웃",
|
||||
"maxUsersLimitReached": "",
|
||||
"maxUsersLimitReachedTitle": "",
|
||||
"maxUsersLimitReached": "회의의 최대 참가자 수에 도달했습니다. 회의 소유자에게 연락하거나 나중에 다시 시도하십시오!",
|
||||
"maxUsersLimitReachedTitle": "최대 참가자 수에 도달했습니다.",
|
||||
"micConstraintFailedError": "마이크가 필요한 제약 조건 중 일부를 충족하지 못합니다",
|
||||
"micNotFoundError": "마이크를 찾을 수 없습니다",
|
||||
"micNotSendingData": "",
|
||||
"micNotSendingDataTitle": "",
|
||||
"micNotSendingData": "컴퓨터의 설정으로 이동하여 마이크 음소거를 해제하고 레벨을 조정하세요.",
|
||||
"micNotSendingDataTitle": "시스템 설정에 의해 마이크가 음소거되었습니다.",
|
||||
"micPermissionDeniedError": "마이크를 사용할 수있는 권한을 부여하지 않았습니다. 회의에 계속 참여할 수는 있지만 다른 사람들은 듣지 않습니다. 검색 주소창의 카메라 버튼을 사용하여 문제를 해결하십시오.",
|
||||
"micUnknownError": "알 수 없는 이유로 마이크를 사용할 수 없습니다",
|
||||
"muteParticipantBody": "당신이 다른 사람들의 음소거를 해제 할 수는 없지만 언제든지 다른 사람들은 스스로 음소거를 해제할 수 있습니다.",
|
||||
"muteParticipantButton": "음소거",
|
||||
"muteParticipantDialog": "",
|
||||
"muteParticipantTitle": "",
|
||||
"muteParticipantTitle": "이 참가자를 음소거 하시겠습니까?",
|
||||
"Ok": "확인",
|
||||
"passwordLabel": "",
|
||||
"passwordNotSupported": "미팅 비밀번호 설정은 지원되지 않습니다",
|
||||
"passwordNotSupportedTitle": "",
|
||||
"passwordRequired": "",
|
||||
"passwordLabel": "잠긴 회의 입니다. 회의에 참여하려면 비밀번호를 입력하세요.",
|
||||
"passwordNotSupported": "회의 비밀번호 설정은 지원되지 않습니다",
|
||||
"passwordNotSupportedTitle": "비밀번호 미지원",
|
||||
"passwordRequired": "비밀번호 필수",
|
||||
"popupError": "브라우저가이 사이트의 팝업 창을 차단하고 있습니다. 브라우저의 보안 설정에서 팝업을 활성화하고 다시 시도하십시오.",
|
||||
"popupErrorTitle": "팝업 차단됨",
|
||||
"recording": "레코딩",
|
||||
"recordingDisabledForGuestTooltip": "",
|
||||
"recordingDisabledTooltip": "",
|
||||
"recordingDisabledForGuestTooltip": "게스트는 녹음을 시작할 수 없습니다.",
|
||||
"recordingDisabledTooltip": "녹화이 비활성화 되었습니다.",
|
||||
"rejoinNow": "지금 재가입",
|
||||
"remoteControlAllowedMessage": "{{user}}이(가) 원격 제어 요청을 수락했습니다",
|
||||
"remoteControlDeniedMessage": "{{user}}이(가) 원격 제어 요청을 거부했습니다",
|
||||
@@ -212,25 +245,30 @@
|
||||
"remoteControlStopMessage": "원격 제어 세션이 종료되었습니다",
|
||||
"remoteControlTitle": "원격 데스크탑 컨트롤",
|
||||
"Remove": "제거",
|
||||
"removePassword": "",
|
||||
"removePassword": "비밀번호 제거",
|
||||
"removeSharedVideoMsg": "공유한 동영상을 삭제하시겠습니까?",
|
||||
"removeSharedVideoTitle": "공유된 동영상 삭제",
|
||||
"reservationError": "예약 시스템 오류",
|
||||
"reservationErrorMsg": "오류 코드: {{code}}, 메시지: {{msg}}",
|
||||
"retry": "재시도",
|
||||
"screenSharingAudio": "오디오 공유",
|
||||
"screenSharingFailedToInstall": "죄송합니다. 화면 공유 확장 프로그램을 설치하지 못했습니다.",
|
||||
"screenSharingFailedToInstallTitle": "화면 공유 확장 프로그램을 설치하지 못했습니다",
|
||||
"screenSharingFirefoxPermissionDeniedError": "화면을 공유하는 동안 문제가 발생했습니다. 그렇게 할 수 있는 권한을 부여했는지 확인하십시오.",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "죄송합니다. 화면 공유를 시작할 수 없었습니다!",
|
||||
"screenSharingPermissionDeniedError": "죄송합니다. 화면 공유 확장 권한으로 문제가 발생했습니다. 다시 로드하고 재시도하십시오.",
|
||||
"sendPrivateMessage": "최근에 비공개 메시지를 받았습니다. 비공개로 답장을 보내시겠습니까, 아니면 그룹에 메시지를 보내시겠습니까?",
|
||||
"sendPrivateMessageCancel": "그룹에 보내기",
|
||||
"sendPrivateMessageOk": "비공개로 보내기",
|
||||
"sendPrivateMessageTitle": "비공개로 보낼까요?",
|
||||
"serviceUnavailable": "서비스를 사용할 수 없음",
|
||||
"sessTerminated": "통화 종료",
|
||||
"Share": "공유",
|
||||
"shareVideoLinkError": "올바른 YouTube 링크를 제공하십시오",
|
||||
"shareVideoTitle": "비디오 공유",
|
||||
"shareYourScreen": "화면공유",
|
||||
"shareYourScreenDisabled": "",
|
||||
"shareYourScreenDisabledForGuest": "",
|
||||
"shareYourScreenDisabled": "화면 공유가 비활성화 되었습니다.",
|
||||
"shareYourScreenDisabledForGuest": "게스트는 화면을 공유 할 수 없습니다.",
|
||||
"startLiveStreaming": "라이브 스트리밍 시작",
|
||||
"startRecording": "레코딩 시작",
|
||||
"startRemoteControlErrorMessage": "원격 제어 세션을 시작하는 동안 오류가 발생했습니다",
|
||||
@@ -245,17 +283,20 @@
|
||||
"tokenAuthFailed": "죄송합니다. 통화에 참여하실 수 없습니다.",
|
||||
"tokenAuthFailedTitle": "인증 실패",
|
||||
"transcribing": "",
|
||||
"unlockRoom": "",
|
||||
"unlockRoom": "회의 비밀번호 제거",
|
||||
"userPassword": "사용자 비밀번호",
|
||||
"WaitForHostMsg": "",
|
||||
"WaitForHostMsgWOk": "",
|
||||
"WaitForHostMsg": "<b>{{room}}</b> 회의가 시작되지 않았습니다. 호스트 인 경우 인증하십시오. 그렇지 않으면 호스트가 도착할 때까지 기다리십시오.",
|
||||
"WaitForHostMsgWOk": "<b>{{room}}</b> 회의가 아직 시작되지 않았습니다. 호스트 인 경우 확인을 눌러 인증하십시오. 그렇지 않으면 호스트가 도착할 때까지 기다리십시오.",
|
||||
"WaitingForHost": "호스트를 기다리는 중입니다…",
|
||||
"Yes": "",
|
||||
"Yes": "예",
|
||||
"yourEntireScreen": "전체 화면"
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "지금은 {{status}}입니다"
|
||||
},
|
||||
"documentSharing": {
|
||||
"title": "문서 공유"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "보통",
|
||||
"bad": "나쁨",
|
||||
@@ -266,49 +307,49 @@
|
||||
"veryGood": "매우 좋음"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "",
|
||||
"audioCallTitle": "",
|
||||
"decline": "",
|
||||
"productLabel": "",
|
||||
"videoCallTitle": ""
|
||||
"answer": "응답",
|
||||
"audioCallTitle": "수신 전화",
|
||||
"decline": "거절",
|
||||
"productLabel": "Jitsi Meet에서",
|
||||
"videoCallTitle": "수신 화상 전화"
|
||||
},
|
||||
"info": {
|
||||
"accessibilityLabel": "",
|
||||
"addPassword": "",
|
||||
"cancelPassword": "",
|
||||
"accessibilityLabel": "정보 보기",
|
||||
"addPassword": "$t(lockRoomPassword) 추가",
|
||||
"cancelPassword": "$t(lockRoomPassword) 취소",
|
||||
"conferenceURL": "링크:",
|
||||
"country": "지역",
|
||||
"dialANumber": "",
|
||||
"dialANumber": "회의에 참여하려면이 번호 중 하나를 누른 다음 PIN을 입력하십시오.",
|
||||
"dialInConferenceID": "PIN:",
|
||||
"dialInNotSupported": "죄송합니다. 현재 전화를 걸 수 없습니다.",
|
||||
"dialInNumber": "Dial-in:",
|
||||
"dialInSummaryError": "",
|
||||
"dialInSummaryError": "지금 전화 접속 정보를 가져 오는 중에 오류가 발생했습니다. 나중에 다시 시도하십시오.",
|
||||
"dialInTollFree": "",
|
||||
"genericError": "일반적인 오류가 발생했습니다",
|
||||
"inviteLiveStream": "이 회의의 실시간 스트림을 보려면이 링크를 클릭하십시오: {{url}}",
|
||||
"invitePhone": "",
|
||||
"invitePhoneAlternatives": "",
|
||||
"inviteURLFirstPartGeneral": "",
|
||||
"inviteURLFirstPartPersonal": "",
|
||||
"inviteURLSecondPart": "",
|
||||
"inviteURLFirstPartGeneral": "회의에 초대되었습니다.",
|
||||
"inviteURLFirstPartPersonal": "{{name}}이 회의에 초대하였습니다.\n",
|
||||
"inviteURLSecondPart": "\n회의에 참여하기:\n{{url}}\n",
|
||||
"liveStreamURL": "실시간 스트리밍:",
|
||||
"moreNumbers": "더 많은 번호",
|
||||
"noNumbers": "전화 접속 번호 없음",
|
||||
"noPassword": "없음",
|
||||
"noRoom": "전화 접속이 가능한 방을 지정하지 않았습니다",
|
||||
"numbers": "전화 접속 번호",
|
||||
"password": "",
|
||||
"password": "비밀번호",
|
||||
"title": "공유",
|
||||
"tooltip": "링크 공유 및 회의에 대한 정보",
|
||||
"label": ""
|
||||
"label": "회의 정보"
|
||||
},
|
||||
"inviteDialog": {
|
||||
"alertText": "",
|
||||
"alertText": "일부 참가자를 초대하지 못했습니다.",
|
||||
"header": "초대",
|
||||
"searchCallOnlyPlaceholder": "",
|
||||
"searchPeopleOnlyPlaceholder": "",
|
||||
"searchPlaceholder": "",
|
||||
"send": ""
|
||||
"searchCallOnlyPlaceholder": "전화 번호 입력",
|
||||
"searchPeopleOnlyPlaceholder": "참가자 검색",
|
||||
"searchPlaceholder": "참가자 또는 전화 번호",
|
||||
"send": "전송"
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "약간의 문제가 있습니다",
|
||||
@@ -321,7 +362,7 @@
|
||||
"focusRemote": "다른 발신자의 동영상에 포커스",
|
||||
"fullScreen": "전체화면 표시 또는 종료",
|
||||
"keyboardShortcuts": "키보드 단축키",
|
||||
"localRecording": "",
|
||||
"localRecording": "로컬 녹음 컨트롤 표시 또는 숨기기",
|
||||
"mute": "마이크 음소거 또는 음소거 해제",
|
||||
"pushToTalk": "대화 요청",
|
||||
"raiseHand": "말하기 요청/해제",
|
||||
@@ -341,24 +382,26 @@
|
||||
"enterStreamKey": "YouTube 실시간 스트리밍 키를 입력하십시오",
|
||||
"error": "실시간 스트리밍에 실패했습니다. 다시 시도하십시오.",
|
||||
"errorAPI": "YouTube 방송에 액세스하는 중에 오류가 발생했습니다. 다시 로그인하십시오.",
|
||||
"errorLiveStreamNotEnabled": "",
|
||||
"expandedOff": "",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"errorLiveStreamNotEnabled": "{{email}}에 의해 라이브 스트리밍이 활성화되지 않았습니다. 라이브 스트리밍을 활성화하거나 라이브 스트리밍이 활성화 된 계정으로 로그인하십시오.",
|
||||
"expandedOff": "라이브 스트리밍이 중지되었습니다",
|
||||
"expandedOn": "현재 회의가 YouTube로 스트리밍되고 있습니다.",
|
||||
"expandedPending": "라이브 스트리밍이 시작됩니다 ...",
|
||||
"failedToStart": "실시간 스트리밍 시작 실패",
|
||||
"getStreamKeyManually": "",
|
||||
"invalidStreamKey": "",
|
||||
"getStreamKeyManually": "실시간 스트림을 가져올 수 없습니다. YouTube에서 실시간 스트림 키를 받아보세요.",
|
||||
"invalidStreamKey": "라이브 스트림 키가 잘못되었을 수 있습니다.",
|
||||
"off": "실시간 스트리밍이 중지됨",
|
||||
"on": "실시간 스트리밍",
|
||||
"pending": "실시간 스트리밍 시작…",
|
||||
"serviceName": "실시간 스트리밍 서비스",
|
||||
"signedInAs": "",
|
||||
"signedInAs": "현재 다음 계정으로 로그인되어 있습니다.",
|
||||
"signIn": "Google로 로그인",
|
||||
"signInCTA": "YouTube에서 로그인하거나 실시간 스트리밍 키를 입력하십시오",
|
||||
"signOut": "",
|
||||
"signOut": "로그아웃",
|
||||
"start": "실시간 스트리밍 시작",
|
||||
"streamIdHelp": "도움말?",
|
||||
"unavailableTitle": "실시간 스트리밍을 사용할 수 없음"
|
||||
"unavailableTitle": "실시간 스트리밍을 사용할 수 없음",
|
||||
"youtubeTerms": "YouTube 서비스 약관",
|
||||
"googlePrivacyPolicy": "Google 개인 정보 보호 정책"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -381,50 +424,50 @@
|
||||
"notModerator": ""
|
||||
},
|
||||
"moderator": "",
|
||||
"no": "",
|
||||
"no": "아니요",
|
||||
"participant": "",
|
||||
"participantStats": "",
|
||||
"sessionToken": "",
|
||||
"start": "레코딩 시작",
|
||||
"stop": "레코딩 종료",
|
||||
"yes": ""
|
||||
"yes": "예"
|
||||
},
|
||||
"lockRoomPassword": "패스워드",
|
||||
"lockRoomPasswordUppercase": "패스워드",
|
||||
"me": "Me",
|
||||
"lockRoomPassword": "비밀번호",
|
||||
"lockRoomPasswordUppercase": "비밀번호",
|
||||
"me": "나",
|
||||
"notify": {
|
||||
"connectedOneMember": "",
|
||||
"connectedThreePlusMembers": "",
|
||||
"connectedTwoMembers": "",
|
||||
"connectedOneMember": "{{name}}님이 회의에 참여했습니다.",
|
||||
"connectedThreePlusMembers": "{{name}}님 외 {{count}}명이 회의에 참여했습니다.",
|
||||
"connectedTwoMembers": "{{first}}님과 {{second}}님이 회의에 참여했습니다.",
|
||||
"disconnected": "연결이 끊김",
|
||||
"focus": "컨퍼런스 포커스",
|
||||
"focusFail": "{{component}}을 사용할 수 없음 - {{ms}} 초 후에 다시 시도하십시오",
|
||||
"grantedTo": "{{to}}에게 방장 권한이 부여되었습니다!",
|
||||
"invitedOneMember": "",
|
||||
"invitedThreePlusMembers": "",
|
||||
"invitedTwoMembers": "",
|
||||
"kickParticipant": "",
|
||||
"me": "",
|
||||
"invitedOneMember": "{{name}}님이 초대되었습니다.",
|
||||
"invitedThreePlusMembers": "{{name}}님 외 {{count}}명이 초대되었습니다.",
|
||||
"invitedTwoMembers": "{{first}}님과 {{second}}님이 초대되었습니다.",
|
||||
"kickParticipant": "{{kicker}}님이 {{kicked}}님을 추방했습니다.",
|
||||
"me": "나",
|
||||
"moderator": "방장 권한이 부여되었습니다!",
|
||||
"muted": "음소거로 대화가 시작되었습니다",
|
||||
"mutedTitle": "음소거 상태입니다!",
|
||||
"mutedRemotelyTitle": "",
|
||||
"mutedRemotelyDescription": "",
|
||||
"passwordRemovedRemotely": "",
|
||||
"passwordSetRemotely": "",
|
||||
"raisedHand": "",
|
||||
"mutedRemotelyTitle": "{{participantDisplayName}}에 의해 음소거되었습니다!",
|
||||
"mutedRemotelyDescription": "말할 준비가되면 언제든지 음소거를 해제 할 수 있습니다.",
|
||||
"passwordRemovedRemotely": "다른 참가자가 $t(lockRoomPasswordUppercase)를 제거했습니다.",
|
||||
"passwordSetRemotely": "다른 참가자가 $t(lockRoomPasswordUppercase)를 설정했습니다.",
|
||||
"raisedHand": "{{name}}님이 말하고 싶어합니다.",
|
||||
"somebody": "누군가",
|
||||
"startSilentTitle": "",
|
||||
"startSilentDescription": "",
|
||||
"startSilentTitle": "오디오 출력없이 참여했습니다!",
|
||||
"startSilentDescription": "오디오를 사용하려면 회의에 다시 참여하세요.",
|
||||
"suboptimalExperienceDescription": "{{appName}}에 대한 귀하의 경험이 없으시다면 <a href='{{recommendedBrowserPageLink}}' target='_blank'>완벽하게 지원되는 브라우저</a> 중 하나를 사용해보십시오.",
|
||||
"suboptimalExperienceTitle": "브라우저 경고",
|
||||
"unmute": "",
|
||||
"newDeviceCameraTitle": "",
|
||||
"newDeviceAudioTitle": "",
|
||||
"newDeviceAction": ""
|
||||
"unmute": "음소거 해제",
|
||||
"newDeviceCameraTitle": "새 카메라 감지",
|
||||
"newDeviceAudioTitle": "새 오디오 장치 감지",
|
||||
"newDeviceAction": "사용"
|
||||
},
|
||||
"passwordSetRemotely": "",
|
||||
"passwordDigitsOnly": "",
|
||||
"passwordSetRemotely": "다른 참가자가 설정",
|
||||
"passwordDigitsOnly": "최대 {{number}} 자리",
|
||||
"poweredby": "powered by",
|
||||
"presenceStatus": {
|
||||
"busy": "바쁨",
|
||||
@@ -447,27 +490,27 @@
|
||||
"title": "프로필"
|
||||
},
|
||||
"recording": {
|
||||
"authDropboxText": "",
|
||||
"availableSpace": "",
|
||||
"authDropboxText": "Dropbox에 업로드",
|
||||
"availableSpace": "사용 가능한 공간 : {{spaceLeft}}MB (약 {{duration}}분 녹화)",
|
||||
"beta": "베타",
|
||||
"busy": "레코딩 자원을 확보하고 있습니다. 몇 분 후에 다시 시도하십시오.",
|
||||
"busyTitle": "모든 레코더가 현재 사용 중입니다",
|
||||
"error": "레코딩이 실패했습니다. 다시 시도하십시오.",
|
||||
"expandedOff": "레코딩이 중지됨",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"expandedOn": "회의가 현재 녹화 중입니다.",
|
||||
"expandedPending": "녹화가 시작됩니다 ...",
|
||||
"failedToStart": "레코딩을 시작하지 못했습니다",
|
||||
"fileSharingdescription": "",
|
||||
"fileSharingdescription": "회의 참가자와 녹음 공유",
|
||||
"live": "라이브",
|
||||
"loggedIn": "",
|
||||
"loggedIn": "{{userName}}으로 로그인했습니다.",
|
||||
"off": "레코딩이 중지됨",
|
||||
"on": "레코딩",
|
||||
"pending": "참석할 멤버를 기다리는 중입니다…",
|
||||
"rec": "REC",
|
||||
"serviceDescription": "",
|
||||
"rec": "녹음",
|
||||
"serviceDescription": "녹음은 녹음 서비스에 의해 저장됩니다.",
|
||||
"serviceName": "레코딩 서비스",
|
||||
"signIn": "",
|
||||
"signOut": "",
|
||||
"signIn": "로그인",
|
||||
"signOut": "로그아웃",
|
||||
"unavailable": "죄송합니다. {{serviceName}}은 현재 사용할 수 없습니다. 저희는 문제를 해결하기 위해 노력하고 있습니다. 나중에 다시 시도 해주십시오.",
|
||||
"unavailableTitle": "레코딩을 사용할 수 없습니다"
|
||||
},
|
||||
@@ -476,18 +519,18 @@
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "",
|
||||
"about": "{{appName}} 캘린더 통합은 예정된 일정을 읽을 수 있도록 캘린더에 안전하게 액세스하는 데 사용됩니다.",
|
||||
"disconnect": "연결 끊김",
|
||||
"microsoftSignIn": "",
|
||||
"signedIn": "",
|
||||
"title": ""
|
||||
"microsoftSignIn": "Microsoft로 로그인",
|
||||
"signedIn": "현재 {{email}}의 캘린더 일정에 액세스하고 있습니다. 캘린더 이벤트 액세스를 중지하려면 아래 연결 해제 버튼을 클릭하세요.",
|
||||
"title": "캘린더"
|
||||
},
|
||||
"devices": "",
|
||||
"devices": "장치",
|
||||
"followMe": "모두 나와 같은 설정 상태로",
|
||||
"language": "",
|
||||
"loggedIn": "",
|
||||
"moderator": "",
|
||||
"more": "",
|
||||
"language": "언어",
|
||||
"loggedIn": "{{name}}으로 로그인",
|
||||
"moderator": "마이크",
|
||||
"more": "더보기",
|
||||
"name": "이름",
|
||||
"noDevice": "없음",
|
||||
"selectAudioOutput": "오디오 출력",
|
||||
@@ -495,26 +538,28 @@
|
||||
"selectMic": "오디오",
|
||||
"startAudioMuted": "모두가 음소거를 시작합니다",
|
||||
"startVideoMuted": "모두가 비디오 비활성화로 시작합니다",
|
||||
"title": "세티"
|
||||
"title": "설정"
|
||||
},
|
||||
"settingsView": {
|
||||
"advanced": "고급",
|
||||
"alertOk": "확인",
|
||||
"alertCancel": "취소",
|
||||
"alertTitle": "경고",
|
||||
"alertURLText": "입력된 서버 URL이 잘못되었습니다",
|
||||
"buildInfoSection": "",
|
||||
"buildInfoSection": "빌드 정보",
|
||||
"conferenceSection": "회의",
|
||||
"displayName": "유저이름",
|
||||
"email": "이메일",
|
||||
"header": "세티",
|
||||
"header": "설정",
|
||||
"profileSection": "프로필",
|
||||
"serverURL": "서버 URL",
|
||||
"startWithAudioMuted": "오디오 음소거 상태로 시작",
|
||||
"startWithVideoMuted": "비디오 비활성화 상태로 시작",
|
||||
"version": ""
|
||||
"version": "버전"
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "",
|
||||
"mainText": ""
|
||||
"mainText": "회의에 참여하려면 다음 링크를 클릭하십시오.\n{{roomUrl}}"
|
||||
},
|
||||
"speaker": "스피커",
|
||||
"speakerStats": {
|
||||
@@ -561,11 +606,11 @@
|
||||
"sharedvideo": "",
|
||||
"shareRoom": "",
|
||||
"shareYourScreen": "",
|
||||
"shortcuts": "단축키 토그",
|
||||
"shortcuts": "단축키 전환",
|
||||
"show": "",
|
||||
"speakerStats": "",
|
||||
"tileView": "",
|
||||
"toggleCamera": "카메라 토ㄱ",
|
||||
"toggleCamera": "카메라 전환",
|
||||
"videomute": "",
|
||||
"videoblur": ""
|
||||
},
|
||||
@@ -575,54 +620,58 @@
|
||||
"audioRoute": "음성 장비 선택하기",
|
||||
"authenticate": "인증 중",
|
||||
"callQuality": "품질 설정하기",
|
||||
"chat": "",
|
||||
"closeChat": "",
|
||||
"documentClose": "",
|
||||
"documentOpen": "",
|
||||
"chat": "대화 열기/닫기",
|
||||
"closeChat": "대화 닫기",
|
||||
"documentClose": "문서 공유 닫기",
|
||||
"documentOpen": "문서 공유 열기",
|
||||
"download": "앱 다운로드",
|
||||
"enterFullScreen": "전체화면 보기",
|
||||
"enterTileView": "",
|
||||
"enterTileView": "타일보기 시작",
|
||||
"exitFullScreen": "전체화면 취소",
|
||||
"exitTileView": "",
|
||||
"exitTileView": "타일보기 종료",
|
||||
"feedback": "피드백 남기기",
|
||||
"hangup": "",
|
||||
"invite": "",
|
||||
"login": "",
|
||||
"hangup": "떠나기",
|
||||
"invite": "초대",
|
||||
"login": "로그인",
|
||||
"logout": "로그아웃",
|
||||
"lowerYourHand": "",
|
||||
"lowerYourHand": "손을 내려주세요",
|
||||
"moreActions": "추가 액션",
|
||||
"mute": "마이크",
|
||||
"openChat": "",
|
||||
"moreOptions": "옵션 더보기",
|
||||
"mute": "음소거 설정/해제",
|
||||
"muteEveryone": "모두 음소거",
|
||||
"openChat": "대화 열기",
|
||||
"pip": "",
|
||||
"profile": "",
|
||||
"privateMessage": "비공개 메시지 보내기",
|
||||
"profile": "프로필 수정",
|
||||
"raiseHand": "말하기 요청/해제",
|
||||
"raiseYourHand": "",
|
||||
"Settings": "세티",
|
||||
"sharedvideo": "",
|
||||
"shareRoom": "",
|
||||
"shortcuts": "",
|
||||
"raiseYourHand": "손 들어주세요",
|
||||
"Settings": "설정",
|
||||
"sharedvideo": "YouTube 비디오 공유",
|
||||
"shareRoom": "초대하기",
|
||||
"shortcuts": "단축키보기",
|
||||
"speakerStats": "접속자 통계",
|
||||
"startScreenSharing": "",
|
||||
"startSubtitles": "",
|
||||
"stopScreenSharing": "",
|
||||
"stopSubtitles": "",
|
||||
"stopSharedVideo": "",
|
||||
"talkWhileMutedPopup": "",
|
||||
"tileViewToggle": "",
|
||||
"toggleCamera": "카메라 토ㄱ",
|
||||
"videomute": "",
|
||||
"startvideoblur": "",
|
||||
"stopvideoblur": ""
|
||||
"startScreenSharing": "화면 공유 시작",
|
||||
"startSubtitles": "자막 시작",
|
||||
"stopScreenSharing": "화면 공유 중지",
|
||||
"stopSubtitles": "자막 중지",
|
||||
"stopSharedVideo": "UouTube 비디오 공유 중지",
|
||||
"talkWhileMutedPopup": "음소거 상태입니다.",
|
||||
"tileViewToggle": "타일뷰 전환",
|
||||
"toggleCamera": "카메라 전환",
|
||||
"videomute": "카메라 시작/중지",
|
||||
"startvideoblur": "내 배경을 흐리게",
|
||||
"stopvideoblur": "배경 흐림 비활성화"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "",
|
||||
"ccButtonTooltip": "자막 시작/종료",
|
||||
"error": "레코딩이 실패했습니다. 다시 시도하십시오.",
|
||||
"expandedLabel": "",
|
||||
"failedToStart": "",
|
||||
"labelToolTip": "",
|
||||
"off": "",
|
||||
"expandedLabel": "현재 스크립트 작성 중",
|
||||
"failedToStart": "스크립트 작성을 시작하지 못했습니다.",
|
||||
"labelToolTip": "회의가 기록되고 있습니다.",
|
||||
"off": "스크립트 작성이 중지되었습니다.",
|
||||
"pending": "참석할 멤버를 기다리는 중입니다…",
|
||||
"start": "",
|
||||
"stop": "",
|
||||
"start": "자막 표시 시작",
|
||||
"stop": "자막 표시 중지",
|
||||
"tr": ""
|
||||
},
|
||||
"userMedia": {
|
||||
@@ -649,8 +698,8 @@
|
||||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "오디오 전용",
|
||||
"audioOnlyExpanded": "",
|
||||
"callQuality": "",
|
||||
"audioOnlyExpanded": "낮은 대역폭 모드에 있습니다. 이 모드에서는 오디오 및 화면 공유 만 수신합니다.",
|
||||
"callQuality": "비디오 품질",
|
||||
"hd": "HD",
|
||||
"highDefinition": "고해상도",
|
||||
"labelTooiltipNoVideo": "비디오 없음",
|
||||
@@ -666,12 +715,12 @@
|
||||
"domute": "음소거",
|
||||
"flip": "플립",
|
||||
"kick": "내보내기",
|
||||
"moderator": "",
|
||||
"mute": "",
|
||||
"moderator": "중재자",
|
||||
"mute": "참가자 음소거",
|
||||
"muted": "음소거됨",
|
||||
"remoteControl": "원격 제어",
|
||||
"show": "",
|
||||
"videomute": ""
|
||||
"show": "화면에 표시",
|
||||
"videomute": "참가자가 카메라를 중지했습니다."
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
@@ -683,22 +732,33 @@
|
||||
"audio": "음성",
|
||||
"video": "비디오"
|
||||
},
|
||||
"calendar": "",
|
||||
"connectCalendarButton": "",
|
||||
"connectCalendarText": "",
|
||||
"enterRoomTitle": "",
|
||||
"calendar": "캘린더",
|
||||
"connectCalendarButton": "캘린더를 연결하세요",
|
||||
"connectCalendarText": "{{app}}에서 모든 회의를 보려면 캘린더를 연결하세요. 또한 캘린더에 {{provider}} 회의를 추가하고 클릭 한 번으로 시작하세요.",
|
||||
"enterRoomTitle": "새 회의 시작",
|
||||
"getHelp": "도움 받기",
|
||||
"roomNameAllowedChars": "회의 이름은 이러한 문자를 포함 할 수 없습니다.: ?, &, :, ', \", %, #.",
|
||||
"go": "계속",
|
||||
"goSmall": "계속",
|
||||
"join": "가입",
|
||||
"info": "",
|
||||
"info": "정보",
|
||||
"privacy": "개인정보",
|
||||
"recentList": "",
|
||||
"recentListDelete": "",
|
||||
"recentListEmpty": "",
|
||||
"reducedUIText": "",
|
||||
"recentList": "최근",
|
||||
"recentListDelete": "삭제",
|
||||
"recentListEmpty": "최근 목록이 현재 비어 있습니다. 팀과 채팅하면 여기에서 최근 회의를 모두 찾을 수 있습니다.",
|
||||
"reducedUIText": "{{app}}에 오신 것을 환영합니다!",
|
||||
"roomname": "방 이름 입력",
|
||||
"roomnameHint": "",
|
||||
"sendFeedback": "",
|
||||
"roomnameHint": "참여하려는 방의 이름 또는 URL을 입력하십시오. 이름을 정하고 만나는 사람들에게 같은 이름을 입력하도록 알리면됩니다.",
|
||||
"sendFeedback": "피드백 보내기",
|
||||
"terms": "이용약관",
|
||||
"title": ""
|
||||
"title": "안전하고 모든 기능을 갖춘 완전 무료 화상 회의"
|
||||
},
|
||||
"lonelyMeetingExperience": {
|
||||
"button": "초대하기",
|
||||
"youAreAlone": "회의에 참여자가 없습니다."
|
||||
},
|
||||
"helpView": {
|
||||
"header": "지원 센터"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
"kickParticipantButton": "Verwijderen",
|
||||
"kickParticipantDialog": "Weet u zeker dat u deze deelnemer wilt verwijderen?",
|
||||
"kickParticipantTitle": "Deze deelnemer verwijderen?",
|
||||
"kickTitle": "Oei! {{ParticipantDisplayName}} heeft u uit de vergadering verwijderd",
|
||||
"kickTitle": "Oei! {{participantDisplayName}} heeft u uit de vergadering verwijderd",
|
||||
"liveStreaming": "Livestreamen",
|
||||
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Niet mogelijk tijdens opnemen",
|
||||
"liveStreamingDisabledForGuestTooltip": "Gasten kunnen geen livestream starten.",
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"bandwidth": "Estimated bandwidth:",
|
||||
"bitrate": "Bitrate:",
|
||||
"bridgeCount": "Server count: ",
|
||||
"codecs": "Codecs (A/V): ",
|
||||
"connectedTo": "Connected to:",
|
||||
"e2e_rtt": "E2E RTT:",
|
||||
"framerate": "Frame rate:",
|
||||
@@ -196,10 +197,7 @@
|
||||
"displayNameRequired": "Hi! What’s your name?",
|
||||
"done": "Done",
|
||||
"e2eeDescription": "End-to-End Encryption is currently EXPERIMENTAL. Please keep in mind that turning on end-to-end encryption will effectively disable server-side provided services such as: recording, live streaming and phone participation. Also keep in mind that the meeting will only work for people joining from browsers with support for insertable streams.",
|
||||
"e2eeLabel": "E2EE key",
|
||||
"e2eeNoKey": "None",
|
||||
"e2eeToggleSet": "Set key",
|
||||
"e2eeSet": "Set",
|
||||
"e2eeLabel": "Enable End-to-End Encryption",
|
||||
"e2eeWarning": "WARNING: Not all participants in this meeting seem to have support for End-to-End encryption. If you enable it they won't be able to see nor hear you.",
|
||||
"enterDisplayName": "Please enter your name here",
|
||||
"error": "Error",
|
||||
@@ -503,6 +501,7 @@
|
||||
"poweredby": "powered by",
|
||||
"prejoin": {
|
||||
"audioAndVideoError": "Audio and video error:",
|
||||
"audioDeviceProblem": "There is a problem with your audio device",
|
||||
"audioOnlyError": "Audio error:",
|
||||
"audioTrackError": "Could not create audio track.",
|
||||
"calling": "Calling",
|
||||
@@ -510,6 +509,25 @@
|
||||
"callMeAtNumber": "Call me at this number:",
|
||||
"configuringDevices": "Configuring devices...",
|
||||
"connectedWithAudioQ": "You’re connected with audio?",
|
||||
"connection": {
|
||||
"good": "Your internet connection looks good!",
|
||||
"nonOptimal": "Your internet connection is not optimal",
|
||||
"poor": "You have a poor internet connection"
|
||||
},
|
||||
"connectionDetails": {
|
||||
"audioClipping": "We expect your audio to be clipped.",
|
||||
"audioHighQuality": "We expect your audio to have excellent quality.",
|
||||
"audioLowNoVideo": "We expect your audio quality to be low and no video.",
|
||||
"goodQuality": "Awesome! Your media quality is going to be great.",
|
||||
"noMediaConnectivity": "We could not find a way to establish media connectivity for this test. This is typically caused by a firewall or NAT.",
|
||||
"noVideo": "We expect that your video will be terrible.",
|
||||
"undetectable": "If you still can not make calls in browser, we recommend that you make sure your speakers, microphone and camera are properly set up, that you have granted your browser rights to use your microphone and camera, and that your browser version is up-to-date. If you still have trouble calling, you should contact the web application developer.",
|
||||
"veryPoorConnection": "We expect your call quality to be really terrible.",
|
||||
"videoFreezing": "We expect your video to freeze, turn black, and be pixelated.",
|
||||
"videoHighQuality": "We expect your video to have good quality.",
|
||||
"videoLowQuality": "We expect your video to have low quality in terms of frame rate and resolution.",
|
||||
"videoTearing": "We expect your video to be pixelated or have visual artefacts."
|
||||
},
|
||||
"copyAndShare": "Copy & share meeting link",
|
||||
"dialInMeeting": "Dial into the meeting",
|
||||
"dialInPin": "Dial into the meeting and enter PIN code:",
|
||||
@@ -519,6 +537,7 @@
|
||||
"errorDialOutDisconnected": "Could not dial out. Disconnected",
|
||||
"errorDialOutFailed": "Could not dial out. Call failed",
|
||||
"errorDialOutStatus": "Error getting dial out status",
|
||||
"errorMissingName": "Please enter your name to join the meeting",
|
||||
"errorStatusCode": "Error dialing out, status code: {{status}}",
|
||||
"errorValidation": "Number validation failed",
|
||||
"iWantToDialIn": "I want to dial in",
|
||||
@@ -675,7 +694,6 @@
|
||||
"document": "Toggle shared document",
|
||||
"download": "Download our apps",
|
||||
"embedMeeting": "Embed meeting",
|
||||
"e2ee": "End-to-End Encryption",
|
||||
"feedback": "Leave feedback",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"grantModerator": "Grant Moderator",
|
||||
|
||||
@@ -15,12 +15,18 @@ import {
|
||||
} from '../../react/features/base/conference';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
|
||||
import { pinParticipant } from '../../react/features/base/participants';
|
||||
import {
|
||||
processExternalDeviceRequest
|
||||
} from '../../react/features/device-selection/functions';
|
||||
import { isEnabled as isDropboxEnabled } from '../../react/features/dropbox';
|
||||
import { setE2EEKey } from '../../react/features/e2ee';
|
||||
import { toggleE2EE } from '../../react/features/e2ee/actions';
|
||||
import { invite } from '../../react/features/invite';
|
||||
import {
|
||||
captureLargeVideoScreenshot,
|
||||
resizeLargeVideo,
|
||||
selectParticipantInLargeVideo
|
||||
} from '../../react/features/large-video/actions';
|
||||
import { toggleLobbyMode } from '../../react/features/lobby/actions.web';
|
||||
import { RECORDING_TYPES } from '../../react/features/recording/constants';
|
||||
import { getActiveSession } from '../../react/features/recording/functions';
|
||||
@@ -115,14 +121,29 @@ function initCommands() {
|
||||
));
|
||||
}
|
||||
},
|
||||
'pin-participant': id => {
|
||||
logger.debug('Pin participant command received');
|
||||
sendAnalytics(createApiEvent('participant.pinned'));
|
||||
APP.store.dispatch(pinParticipant(id));
|
||||
},
|
||||
'proxy-connection-event': event => {
|
||||
APP.conference.onProxyConnectionEvent(event);
|
||||
},
|
||||
'resize-large-video': (width, height) => {
|
||||
logger.debug('Resize large video command received');
|
||||
sendAnalytics(createApiEvent('largevideo.resized'));
|
||||
APP.store.dispatch(resizeLargeVideo(width, height));
|
||||
},
|
||||
'send-tones': (options = {}) => {
|
||||
const { duration, tones, pause } = options;
|
||||
|
||||
APP.store.dispatch(sendTones(tones, duration, pause));
|
||||
},
|
||||
'set-large-video-participant': participantId => {
|
||||
logger.debug('Set large video participant command received');
|
||||
sendAnalytics(createApiEvent('largevideo.participant.set'));
|
||||
APP.store.dispatch(selectParticipantInLargeVideo(participantId));
|
||||
},
|
||||
'subject': subject => {
|
||||
sendAnalytics(createApiEvent('subject.changed'));
|
||||
APP.store.dispatch(setSubject(subject));
|
||||
@@ -191,9 +212,9 @@ function initCommands() {
|
||||
logger.error('Failed sending endpoint text message', err);
|
||||
}
|
||||
},
|
||||
'e2ee-key': key => {
|
||||
logger.debug('Set E2EE key command received');
|
||||
APP.store.dispatch(setE2EEKey(key));
|
||||
'toggle-e2ee': enabled => {
|
||||
logger.debug('Toggle E2EE key command received');
|
||||
APP.store.dispatch(toggleE2EE(enabled));
|
||||
},
|
||||
'set-video-quality': frameHeight => {
|
||||
logger.debug('Set video quality command received');
|
||||
@@ -328,6 +349,21 @@ function initCommands() {
|
||||
const { name } = request;
|
||||
|
||||
switch (name) {
|
||||
case 'capture-largevideo-screenshot' :
|
||||
APP.store.dispatch(captureLargeVideoScreenshot())
|
||||
.then(dataURL => {
|
||||
let error;
|
||||
|
||||
if (!dataURL) {
|
||||
error = new Error('No large video found!');
|
||||
}
|
||||
|
||||
callback({
|
||||
error,
|
||||
dataURL
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'invite': {
|
||||
const { invitees } = request;
|
||||
|
||||
@@ -524,6 +560,19 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application that the video quality setting has changed.
|
||||
*
|
||||
* @param {number} videoQuality - The video quality. The number represents the maximum height of the video streams.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyVideoQualityChanged(videoQuality: number) {
|
||||
this._sendEvent({
|
||||
name: 'video-quality-changed',
|
||||
videoQuality
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that message was
|
||||
* received.
|
||||
@@ -677,6 +726,21 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that the an error has been logged.
|
||||
*
|
||||
* @param {string} logLevel - The message log level.
|
||||
* @param {Array} args - Array of strings composing the log message.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyLog(logLevel: string, args: Array<string>) {
|
||||
this._sendEvent({
|
||||
name: 'log',
|
||||
logLevel,
|
||||
args
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that the conference has
|
||||
* been joined.
|
||||
@@ -697,8 +761,7 @@ class API {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that user changed their
|
||||
* nickname.
|
||||
* Notify external application (if API is enabled) that local user has left the conference.
|
||||
*
|
||||
* @param {string} roomName - User id.
|
||||
* @returns {void}
|
||||
|
||||
117
modules/API/external/external_api.js
vendored
117
modules/API/external/external_api.js
vendored
@@ -35,8 +35,11 @@ const commands = {
|
||||
hangup: 'video-hangup',
|
||||
muteEveryone: 'mute-everyone',
|
||||
password: 'password',
|
||||
pinParticipant: 'pin-participant',
|
||||
resizeLargeVideo: 'resize-large-video',
|
||||
sendEndpointTextMessage: 'send-endpoint-text-message',
|
||||
sendTones: 'send-tones',
|
||||
setLargeVideoParticipant: 'set-large-video-participant',
|
||||
setVideoQuality: 'set-video-quality',
|
||||
startRecording: 'start-recording',
|
||||
stopRecording: 'stop-recording',
|
||||
@@ -67,6 +70,7 @@ const events = {
|
||||
'feedback-prompt-displayed': 'feedbackPromptDisplayed',
|
||||
'filmstrip-display-changed': 'filmstripDisplayChanged',
|
||||
'incoming-message': 'incomingMessage',
|
||||
'log': 'log',
|
||||
'mic-error': 'micError',
|
||||
'outgoing-message': 'outgoingMessage',
|
||||
'participant-joined': 'participantJoined',
|
||||
@@ -80,6 +84,7 @@ const events = {
|
||||
'video-conference-left': 'videoConferenceLeft',
|
||||
'video-availability-changed': 'videoAvailabilityChanged',
|
||||
'video-mute-status-changed': 'videoMuteStatusChanged',
|
||||
'video-quality-changed': 'videoQualityChanged',
|
||||
'screen-sharing-status-changed': 'screenSharingStatusChanged',
|
||||
'dominant-speaker-changed': 'dominantSpeakerChanged',
|
||||
'subject-change': 'subjectChange',
|
||||
@@ -355,6 +360,19 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted display name of a participant.
|
||||
*
|
||||
* @param {string} participantId - The id of the participant.
|
||||
* @returns {string} The formatted display name.
|
||||
*/
|
||||
_getFormattedDisplayName(participantId) {
|
||||
const { formattedDisplayName }
|
||||
= this._participants[participantId] || {};
|
||||
|
||||
return formattedDisplayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the on stage participant.
|
||||
*
|
||||
@@ -421,10 +439,12 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
const parsedWidth = parseSizeParam(width);
|
||||
|
||||
if (parsedHeight !== undefined) {
|
||||
this._height = height;
|
||||
this._frame.style.height = parsedHeight;
|
||||
}
|
||||
|
||||
if (parsedWidth !== undefined) {
|
||||
this._width = width;
|
||||
this._frame.style.width = parsedWidth;
|
||||
}
|
||||
}
|
||||
@@ -503,6 +523,9 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
changeParticipantNumber(this, -1);
|
||||
delete this._participants[this._myUserID];
|
||||
break;
|
||||
case 'video-quality-changed':
|
||||
this._videoQuality = data.videoQuality;
|
||||
break;
|
||||
}
|
||||
|
||||
const eventName = events[name];
|
||||
@@ -538,6 +561,13 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* the event and value - the listener.
|
||||
* Currently we support the following
|
||||
* events:
|
||||
* {@code log} - receives event notifications whenever information has
|
||||
* been logged and has a log level specified within {@code config.apiLogLevels}.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* logLevel: the message log level
|
||||
* arguments: an array of strings that compose the actual log message
|
||||
* }}
|
||||
* {@code incomingMessage} - receives event notifications about incoming
|
||||
* messages. The listener will receive object with the following structure:
|
||||
* {{
|
||||
@@ -607,6 +637,18 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures the screenshot of the large video.
|
||||
*
|
||||
* @returns {Promise<string>} - Resolves with a base64 encoded image data of the screenshot
|
||||
* if large video is detected, an error otherwise.
|
||||
*/
|
||||
captureLargeVideoScreenshot() {
|
||||
return this._transport.sendRequest({
|
||||
name: 'capture-largevideo-screenshot'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listeners and removes the Jitsi Meet frame.
|
||||
*
|
||||
@@ -689,6 +731,32 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
return getCurrentDevices(this._transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conference participants information.
|
||||
*
|
||||
* @returns {Array<Object>} - Returns an array containing participants
|
||||
* information like participant id, display name, avatar URL and email.
|
||||
*/
|
||||
getParticipantsInfo() {
|
||||
const participantIds = Object.keys(this._participants);
|
||||
const participantsInfo = Object.values(this._participants);
|
||||
|
||||
participantsInfo.forEach((participant, idx) => {
|
||||
participant.participantId = participantIds[idx];
|
||||
});
|
||||
|
||||
return participantsInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current video quality setting.
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
getVideoQuality() {
|
||||
return this._videoQuality;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the audio is available.
|
||||
*
|
||||
@@ -809,19 +877,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
return email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted display name of a participant.
|
||||
*
|
||||
* @param {string} participantId - The id of the participant.
|
||||
* @returns {string} The formatted display name.
|
||||
*/
|
||||
_getFormattedDisplayName(participantId) {
|
||||
const { formattedDisplayName }
|
||||
= this._participants[participantId] || {};
|
||||
|
||||
return formattedDisplayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the iframe that loads Jitsi Meet.
|
||||
*
|
||||
@@ -865,6 +920,17 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pins a participant's video on to the stage view.
|
||||
*
|
||||
* @param {string} participantId - Participant id (JID) of the participant
|
||||
* that needs to be pinned on the stage view.
|
||||
* @returns {void}
|
||||
*/
|
||||
pinParticipant(participantId) {
|
||||
this.executeCommand('pinParticipant', participantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes event listener.
|
||||
*
|
||||
@@ -891,6 +957,19 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
eventList.forEach(event => this.removeEventListener(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the large video container as per the dimensions provided.
|
||||
*
|
||||
* @param {number} width - Width that needs to be applied on the large video container.
|
||||
* @param {number} height - Height that needs to be applied on the large video container.
|
||||
* @returns {void}
|
||||
*/
|
||||
resizeLargeVideo(width, height) {
|
||||
if (width <= this._width && height <= this._height) {
|
||||
this.executeCommand('resizeLargeVideo', width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes an event along to the local conference participant to establish
|
||||
* or update a direct peer connection. This is currently used for developing
|
||||
@@ -934,6 +1013,18 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
return setAudioOutputDevice(this._transport, label, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the given participant on the large video. If no participant id is specified,
|
||||
* dominant and pinned speakers will be taken into consideration while selecting the
|
||||
* the large video participant.
|
||||
*
|
||||
* @param {string} participantId - Jid of the participant to be displayed on the large video.
|
||||
* @returns {void}
|
||||
*/
|
||||
setLargeVideoParticipant(participantId) {
|
||||
this.executeCommand('setLargeVideoParticipant', participantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the video input device to the one with the label or id that is
|
||||
* passed.
|
||||
|
||||
@@ -5,13 +5,6 @@
|
||||
*/
|
||||
const UIUtil = {
|
||||
|
||||
/**
|
||||
* Returns the available video width.
|
||||
*/
|
||||
getAvailableVideoWidth() {
|
||||
return window.innerWidth;
|
||||
},
|
||||
|
||||
/**
|
||||
* Escapes the given text.
|
||||
*/
|
||||
|
||||
@@ -15,13 +15,12 @@ import { VIDEO_TYPE } from '../../../react/features/base/media';
|
||||
import { CHAT_SIZE } from '../../../react/features/chat';
|
||||
import {
|
||||
updateKnownLargeVideoResolution
|
||||
} from '../../../react/features/large-video';
|
||||
} from '../../../react/features/large-video/actions';
|
||||
import { PresenceLabel } from '../../../react/features/presence-status';
|
||||
/* eslint-enable no-unused-vars */
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
import { createDeferred } from '../../util/helpers';
|
||||
import AudioLevels from '../audio_levels/AudioLevels';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
|
||||
import { VideoContainer, VIDEO_CONTAINER_TYPE } from './VideoContainer';
|
||||
|
||||
@@ -68,7 +67,30 @@ export default class LargeVideoManager {
|
||||
// use the same video container to handle desktop tracks
|
||||
this.addContainer(DESKTOP_CONTAINER_TYPE, this.videoContainer);
|
||||
|
||||
/**
|
||||
* The preferred width passed as an argument to {@link updateContainerSize}.
|
||||
*
|
||||
* @type {number|undefined}
|
||||
*/
|
||||
this.preferredWidth = undefined;
|
||||
|
||||
/**
|
||||
* The preferred height passed as an argument to {@link updateContainerSize}.
|
||||
*
|
||||
* @type {number|undefined}
|
||||
*/
|
||||
this.preferredHeight = undefined;
|
||||
|
||||
/**
|
||||
* The calculated width that will be used for the large video.
|
||||
* @type {number}
|
||||
*/
|
||||
this.width = 0;
|
||||
|
||||
/**
|
||||
* The calculated height that will be used for the large video.
|
||||
* @type {number}
|
||||
*/
|
||||
this.height = 0;
|
||||
|
||||
/**
|
||||
@@ -323,20 +345,32 @@ export default class LargeVideoManager {
|
||||
/**
|
||||
* Update container size.
|
||||
*/
|
||||
updateContainerSize() {
|
||||
let widthToUse = UIUtil.getAvailableVideoWidth();
|
||||
updateContainerSize(width, height) {
|
||||
if (typeof width === 'number') {
|
||||
this.preferredWidth = width;
|
||||
}
|
||||
if (typeof height === 'number') {
|
||||
this.preferredHeight = height;
|
||||
}
|
||||
|
||||
let widthToUse = this.preferredWidth || window.innerWidth;
|
||||
const { isOpen } = APP.store.getState()['features/chat'];
|
||||
|
||||
if (isOpen) {
|
||||
/**
|
||||
* If chat state is open, we re-compute the container width
|
||||
* by subtracting the default width of the chat.
|
||||
*/
|
||||
/**
|
||||
* If chat state is open, we re-compute the container width by subtracting the default width of
|
||||
* the chat. We re-compute the width again after the chat window is closed. This is needed when
|
||||
* custom styling is configured on the large video container through the iFrame API.
|
||||
*/
|
||||
if (isOpen && !this.resizedForChat) {
|
||||
widthToUse -= CHAT_SIZE;
|
||||
this.resizedForChat = true;
|
||||
} else if (this.resizedForChat) {
|
||||
this.resizedForChat = false;
|
||||
widthToUse += CHAT_SIZE;
|
||||
}
|
||||
|
||||
this.width = widthToUse;
|
||||
this.height = window.innerHeight;
|
||||
this.height = this.preferredHeight || window.innerHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -481,7 +481,11 @@ export default class RemoteVideo extends SmallVideo {
|
||||
|
||||
const isVideo = stream.isVideoTrack();
|
||||
|
||||
isVideo ? this.videoStream = stream : this.audioStream = stream;
|
||||
if (isVideo) {
|
||||
this.videoStream = stream;
|
||||
} else {
|
||||
this.audioStream = stream;
|
||||
}
|
||||
|
||||
if (!stream.getOriginalStream()) {
|
||||
logger.debug('Remote video stream has no original stream');
|
||||
|
||||
@@ -83,10 +83,12 @@ export default class SmallVideo {
|
||||
constructor(VideoLayout) {
|
||||
this.isAudioMuted = false;
|
||||
this.isVideoMuted = false;
|
||||
this.isScreenSharing = false;
|
||||
this.videoStream = null;
|
||||
this.audioStream = null;
|
||||
this.VideoLayout = VideoLayout;
|
||||
this.videoIsHovered = false;
|
||||
this.videoType = undefined;
|
||||
|
||||
/**
|
||||
* The current state of the user's bridge connection. The value should be
|
||||
@@ -234,6 +236,22 @@ export default class SmallVideo {
|
||||
this.updateStatusBar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows / hides the screen-share indicator over small videos.
|
||||
*
|
||||
* @param {boolean} isScreenSharing indicates if the screen-share element should be shown
|
||||
* or hidden
|
||||
*/
|
||||
setScreenSharing(isScreenSharing) {
|
||||
if (isScreenSharing === this.isScreenSharing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isScreenSharing = isScreenSharing;
|
||||
this.updateView();
|
||||
this.updateStatusBar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows video muted indicator over small videos and disables/enables avatar
|
||||
* if video muted.
|
||||
@@ -265,6 +283,7 @@ export default class SmallVideo {
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
<StatusIndicators
|
||||
showAudioMutedIndicator = { this.isAudioMuted }
|
||||
showScreenShareIndicator = { this.isScreenSharing }
|
||||
showVideoMutedIndicator = { this.isVideoMuted }
|
||||
participantID = { this.id } />
|
||||
</I18nextProvider>
|
||||
@@ -450,8 +469,10 @@ export default class SmallVideo {
|
||||
* or <tt>DISPLAY_BLACKNESS_WITH_NAME</tt>.
|
||||
*/
|
||||
selectDisplayMode(input) {
|
||||
// Display name is always and only displayed when user is on the stage
|
||||
if (input.isCurrentlyOnLargeVideo && !input.tileViewActive) {
|
||||
if (!input.tileViewActive && input.isScreenSharing) {
|
||||
return input.isHovered ? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
|
||||
} else if (input.isCurrentlyOnLargeVideo && !input.tileViewActive) {
|
||||
// Display name is always and only displayed when user is on the stage
|
||||
return input.isVideoPlayable && !input.isAudioOnly ? DISPLAY_BLACKNESS_WITH_NAME : DISPLAY_AVATAR_WITH_NAME;
|
||||
} else if (input.isVideoPlayable && input.hasVideo && !input.isAudioOnly) {
|
||||
// check hovering and change state to video with name
|
||||
@@ -480,6 +501,7 @@ export default class SmallVideo {
|
||||
canPlayEventReceived: this._canPlayEventReceived,
|
||||
videoStream: Boolean(this.videoStream),
|
||||
isVideoMuted: this.isVideoMuted,
|
||||
isScreenSharing: this.isScreenSharing,
|
||||
videoStreamMuted: this.videoStream ? this.videoStream.isMuted() : 'no stream'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -177,6 +177,7 @@ const VideoLayout = {
|
||||
this.onAudioMute(id, stream.isMuted());
|
||||
} else {
|
||||
this.onVideoMute(id, stream.isMuted());
|
||||
remoteVideo.setScreenSharing(stream.videoType === 'desktop');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -188,6 +189,7 @@ const VideoLayout = {
|
||||
|
||||
if (remoteVideo) {
|
||||
remoteVideo.removeRemoteStreamElement(stream);
|
||||
remoteVideo.setScreenSharing(false);
|
||||
}
|
||||
|
||||
this.updateMutedForNoTracks(id, stream.getType());
|
||||
@@ -485,13 +487,14 @@ const VideoLayout = {
|
||||
},
|
||||
|
||||
onVideoTypeChanged(id, newVideoType) {
|
||||
if (VideoLayout.getRemoteVideoType(id) === newVideoType) {
|
||||
const remoteVideo = remoteVideos[id];
|
||||
|
||||
if (!remoteVideo) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('Peer video type changed: ', id, newVideoType);
|
||||
|
||||
this._updateLargeVideoIfDisplayed(id, true);
|
||||
remoteVideo.setScreenSharing(newVideoType === 'desktop');
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -209,7 +209,7 @@ export default {
|
||||
})
|
||||
.catch(err => {
|
||||
audioTrackError = err;
|
||||
showError && APP.store.disptach(notifyMicError(err));
|
||||
showError && APP.store.dispatch(notifyMicError(err));
|
||||
|
||||
return [];
|
||||
}));
|
||||
|
||||
9121
package-lock.json
generated
9121
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -41,7 +41,7 @@
|
||||
"@tensorflow-models/body-pix": "2.0.4",
|
||||
"@tensorflow/tfjs": "1.5.1",
|
||||
"@webcomponents/url": "0.7.1",
|
||||
"amplitude-js": "4.5.2",
|
||||
"amplitude-js": "7.1.1",
|
||||
"base64-js": "1.3.1",
|
||||
"bc-css-flags": "3.0.0",
|
||||
"dropbox": "4.0.9",
|
||||
@@ -56,20 +56,22 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#f74cd0abe9c696a9c3ca7dbb9ca170e6e84d6756",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#a0b8a9c862f46b0e743b8a750c2160acc473ed49",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.19",
|
||||
"moment": "2.19.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
|
||||
"pixelmatch": "5.1.0",
|
||||
"punycode": "2.1.1",
|
||||
"react": "16.9",
|
||||
"react-dom": "16.9",
|
||||
"react-emoji-render": "1.2.4",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "github:jitsi/react-native#efd2aff5661d75a230e36406b698cfe0ee545be2",
|
||||
"react-native-background-timer": "2.1.1",
|
||||
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#928a80e2ffef0d7e84936d7e7e0acc4f53ee8470",
|
||||
"react-native-background-timer": "2.4.0",
|
||||
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#df48ecdc4e1e90c5352f803ddbab1fa7269b74a7",
|
||||
"react-native-callstats": "3.61.0",
|
||||
"react-native-collapsible": "1.5.1",
|
||||
"react-native-default-preference": "1.4.2",
|
||||
@@ -82,7 +84,7 @@
|
||||
"react-native-swipeout": "2.3.6",
|
||||
"react-native-watch-connectivity": "0.4.3",
|
||||
"react-native-webrtc": "1.84.0",
|
||||
"react-native-webview": "7.4.1",
|
||||
"react-native-webview": "10.9.0",
|
||||
"react-native-youtube-iframe": "1.2.3",
|
||||
"react-redux": "7.1.0",
|
||||
"react-textarea-autosize": "7.1.0",
|
||||
@@ -90,7 +92,8 @@
|
||||
"redux": "4.0.4",
|
||||
"redux-thunk": "2.2.0",
|
||||
"rnnoise-wasm": "github:jitsi/rnnoise-wasm.git#566a16885897704d6e6d67a1d5ac5d39781db2af",
|
||||
"rtcstats": "github:jitsi/rtcstats#v6.1.3",
|
||||
"rtcstats": "github:jitsi/rtcstats#v6.2.0",
|
||||
"stackblur-canvas": "2.3.0",
|
||||
"styled-components": "3.4.9",
|
||||
"util": "0.12.1",
|
||||
"uuid": "3.1.0",
|
||||
@@ -125,10 +128,9 @@
|
||||
"expose-loader": "0.7.5",
|
||||
"flow-bin": "0.104.0",
|
||||
"imports-loader": "0.7.1",
|
||||
"jest": "26.1.0",
|
||||
"jetifier": "1.6.4",
|
||||
"metro-react-native-babel-preset": "0.56.0",
|
||||
"node-sass": "4.14.1",
|
||||
"sass": "1.26.8",
|
||||
"string-replace-loader": "2.1.1",
|
||||
"style-loader": "0.19.0",
|
||||
"unorm": "1.6.0",
|
||||
@@ -145,7 +147,6 @@
|
||||
"scripts": {
|
||||
"lint": "eslint . && flow",
|
||||
"postinstall": "jetify",
|
||||
"test": "jest",
|
||||
"validate": "npm ls"
|
||||
},
|
||||
"browser": {
|
||||
|
||||
@@ -57,12 +57,15 @@ export function resetAnalytics() {
|
||||
* @param {Store} store - The redux store in which the specified {@code action} is being dispatched.
|
||||
* @returns {Promise} Resolves with the handlers that have been successfully loaded.
|
||||
*/
|
||||
export function createHandlers({ getState }: { getState: Function }) {
|
||||
export async function createHandlers({ getState }: { getState: Function }) {
|
||||
getJitsiMeetGlobalNS().analyticsHandlers = [];
|
||||
window.analyticsHandlers = []; // Legacy support.
|
||||
|
||||
if (!isAnalyticsEnabled(getState)) {
|
||||
return Promise.resolve([]);
|
||||
// Avoid all analytics processing if there are no handlers, since no event would be sent.
|
||||
analytics.dispose();
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
@@ -100,43 +103,47 @@ export function createHandlers({ getState }: { getState: Function }) {
|
||||
};
|
||||
const handlers = [];
|
||||
|
||||
try {
|
||||
const amplitude = new AmplitudeHandler(handlerConstructorOptions);
|
||||
if (amplitudeAPPKey) {
|
||||
try {
|
||||
const amplitude = new AmplitudeHandler(handlerConstructorOptions);
|
||||
|
||||
analytics.amplitudeIdentityProps = amplitude.getIdentityProps();
|
||||
analytics.amplitudeIdentityProps = amplitude.getIdentityProps();
|
||||
|
||||
handlers.push(amplitude);
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (e) {}
|
||||
handlers.push(amplitude);
|
||||
} catch (e) {
|
||||
logger.error('Failed to initialize Amplitude handler', e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const matomo = new MatomoHandler(handlerConstructorOptions);
|
||||
if (matomoEndpoint && matomoSiteID) {
|
||||
try {
|
||||
const matomo = new MatomoHandler(handlerConstructorOptions);
|
||||
|
||||
handlers.push(matomo);
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (e) {}
|
||||
handlers.push(matomo);
|
||||
} catch (e) {
|
||||
logger.error('Failed to initialize Matomo handler', e);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
_loadHandlers(scriptURLs, handlerConstructorOptions)
|
||||
.then(externalHandlers => {
|
||||
handlers.push(...externalHandlers);
|
||||
if (handlers.length === 0) {
|
||||
// Throwing an error in order to dispose the analytics in the catch clause due to the lack of any
|
||||
// analytics handlers.
|
||||
throw new Error('No analytics handlers created!');
|
||||
}
|
||||
if (Array.isArray(scriptURLs) && scriptURLs.length > 0) {
|
||||
let externalHandlers;
|
||||
|
||||
return handlers;
|
||||
})
|
||||
.catch(e => {
|
||||
analytics.dispose();
|
||||
if (handlers.length !== 0) {
|
||||
logger.error(e);
|
||||
}
|
||||
try {
|
||||
externalHandlers = await _loadHandlers(scriptURLs, handlerConstructorOptions);
|
||||
handlers.push(...externalHandlers);
|
||||
} catch (e) {
|
||||
logger.error('Failed to initialize external analytics handlers', e);
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}));
|
||||
// Avoid all analytics processing if there are no handlers, since no event would be sent.
|
||||
if (handlers.length === 0) {
|
||||
analytics.dispose();
|
||||
}
|
||||
|
||||
logger.info(`Initialized ${handlers.length} analytics handlers`);
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,7 +235,7 @@ function _inIframe() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load the scripts for the analytics handlers and creates them.
|
||||
* Tries to load the scripts for the external analytics handlers and creates them.
|
||||
*
|
||||
* @param {Array} scriptURLs - The array of script urls to load.
|
||||
* @param {Object} handlerConstructorOptions - The default options to pass when creating handlers.
|
||||
@@ -279,7 +286,7 @@ function _loadHandlers(scriptURLs = [], handlerConstructorOptions) {
|
||||
logger.warn(`Error creating analytics handler: ${error}`);
|
||||
}
|
||||
}
|
||||
logger.debug(`Loaded ${handlers.length} analytics handlers`);
|
||||
logger.debug(`Loaded ${handlers.length} external analytics handlers`);
|
||||
|
||||
return handlers;
|
||||
});
|
||||
|
||||
@@ -72,6 +72,11 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
* @returns {Object}
|
||||
*/
|
||||
getIdentityProps() {
|
||||
// TODO: Remove when web and native Aplitude implementations are unified.
|
||||
if (navigator.product === 'ReactNative') {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
sessionId: amplitude.getInstance(this._amplitudeOptions).getSessionId(),
|
||||
deviceId: amplitude.getInstance(this._amplitudeOptions).options.deviceId,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { API_ID } from '../../../modules/API/constants';
|
||||
import { setRoom } from '../base/conference';
|
||||
import {
|
||||
configWillLoad,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
parseURIString,
|
||||
toURLString
|
||||
} from '../base/util';
|
||||
import { isVpaasMeeting } from '../billing-counter/functions';
|
||||
import { clearNotifications, showNotification } from '../notifications';
|
||||
import { setFatalError } from '../overlay';
|
||||
|
||||
@@ -168,9 +170,11 @@ export function redirectWithStoredParams(pathname: string) {
|
||||
* window.location.pathname. If the specified pathname is relative, the context
|
||||
* root of the Web app will be prepended to the specified pathname before
|
||||
* assigning it to window.location.pathname.
|
||||
* @param {string} hashParam - Optional hash param to assign to
|
||||
* window.location.hash.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function redirectToStaticPage(pathname: string) {
|
||||
export function redirectToStaticPage(pathname: string, hashParam: ?string) {
|
||||
return () => {
|
||||
const windowLocation = window.location;
|
||||
let newPathname = pathname;
|
||||
@@ -184,6 +188,10 @@ export function redirectToStaticPage(pathname: string) {
|
||||
newPathname = getLocationContextRoot(windowLocation) + newPathname;
|
||||
}
|
||||
|
||||
if (hashParam) {
|
||||
windowLocation.hash = hashParam;
|
||||
}
|
||||
|
||||
windowLocation.pathname = newPathname;
|
||||
};
|
||||
}
|
||||
@@ -284,8 +292,16 @@ export function maybeRedirectToWelcomePage(options: Object = {}) {
|
||||
|
||||
// if close page is enabled redirect to it, without further action
|
||||
if (enableClosePage) {
|
||||
if (isVpaasMeeting(getState())) {
|
||||
redirectToStaticPage('/');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { isGuest, jwt } = getState()['features/base/jwt'];
|
||||
|
||||
let hashParam;
|
||||
|
||||
// save whether current user is guest or not, and pass auth token,
|
||||
// before navigating to close page
|
||||
window.sessionStorage.setItem('guest', isGuest);
|
||||
@@ -294,12 +310,15 @@ export function maybeRedirectToWelcomePage(options: Object = {}) {
|
||||
let path = 'close.html';
|
||||
|
||||
if (interfaceConfig.SHOW_PROMOTIONAL_CLOSE_PAGE) {
|
||||
if (Number(API_ID) === API_ID) {
|
||||
hashParam = `#jitsi_meet_external_api_id=${API_ID}`;
|
||||
}
|
||||
path = 'close3.html';
|
||||
} else if (!options.feedbackSubmitted) {
|
||||
path = 'close2.html';
|
||||
}
|
||||
|
||||
dispatch(redirectToStaticPage(`static/${path}`));
|
||||
dispatch(redirectToStaticPage(`static/${path}`, hashParam));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import '../base/sounds/middleware';
|
||||
import '../base/testing/middleware';
|
||||
import '../base/tracks/middleware';
|
||||
import '../base/user-interaction/middleware';
|
||||
import '../billing-counter/middleware';
|
||||
import '../calendar-sync/middleware';
|
||||
import '../chat/middleware';
|
||||
import '../conference/middleware';
|
||||
|
||||
@@ -30,6 +30,7 @@ import '../chat/reducer';
|
||||
import '../deep-linking/reducer';
|
||||
import '../device-selection/reducer';
|
||||
import '../dropbox/reducer';
|
||||
import '../dynamic-branding/reducer';
|
||||
import '../etherpad/reducer';
|
||||
import '../filmstrip/reducer';
|
||||
import '../follow-me/reducer';
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { IconShareDesktop } from '../../icons';
|
||||
import { getParticipantById } from '../../participants';
|
||||
import { connect } from '../../redux';
|
||||
import { getAvatarColor, getInitials } from '../functions';
|
||||
@@ -192,17 +191,10 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
const { colorBase, displayName, participantId } = ownProps;
|
||||
const _participant: ?Object = participantId && getParticipantById(state, participantId);
|
||||
const _initialsBase = _participant?.name ?? displayName;
|
||||
const screenShares = state['features/video-layout'].screenShares || [];
|
||||
|
||||
let _loadableAvatarUrl = _participant?.loadableAvatarUrl;
|
||||
|
||||
if (participantId && screenShares.includes(participantId)) {
|
||||
_loadableAvatarUrl = IconShareDesktop;
|
||||
}
|
||||
|
||||
return {
|
||||
_initialsBase,
|
||||
_loadableAvatarUrl,
|
||||
_loadableAvatarUrl: _participant?.loadableAvatarUrl,
|
||||
colorBase: !colorBase && _participant ? _participant.id : colorBase
|
||||
};
|
||||
}
|
||||
|
||||
@@ -163,19 +163,6 @@ export const SET_DESKTOP_SHARING_ENABLED
|
||||
*/
|
||||
export const SET_FOLLOW_ME = 'SET_FOLLOW_ME';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the maximum video height that should be
|
||||
* received from remote participants, even if the user prefers a larger video
|
||||
* height.
|
||||
*
|
||||
* {
|
||||
* type: SET_MAX_RECEIVER_VIDEO_QUALITY,
|
||||
* maxReceiverVideoQuality: number
|
||||
* }
|
||||
*/
|
||||
export const SET_MAX_RECEIVER_VIDEO_QUALITY
|
||||
= 'SET_MAX_RECEIVER_VIDEO_QUALITY';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the password to join or lock a specific
|
||||
* {@code JitsiConference}.
|
||||
@@ -210,17 +197,6 @@ export const SET_PASSWORD_FAILED = 'SET_PASSWORD_FAILED';
|
||||
*/
|
||||
export const SET_PENDING_SUBJECT_CHANGE = 'SET_PENDING_SUBJECT_CHANGE';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the preferred maximum video height that
|
||||
* should be sent to and received from remote participants.
|
||||
*
|
||||
* {
|
||||
* type: SET_PREFERRED_VIDEO_QUALITY,
|
||||
* preferredVideoQuality: number
|
||||
* }
|
||||
*/
|
||||
export const SET_PREFERRED_VIDEO_QUALITY = 'SET_PREFERRED_VIDEO_QUALITY';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the name of the room of the
|
||||
* conference to be joined.
|
||||
|
||||
@@ -45,10 +45,8 @@ import {
|
||||
SEND_TONES,
|
||||
SET_DESKTOP_SHARING_ENABLED,
|
||||
SET_FOLLOW_ME,
|
||||
SET_MAX_RECEIVER_VIDEO_QUALITY,
|
||||
SET_PASSWORD,
|
||||
SET_PASSWORD_FAILED,
|
||||
SET_PREFERRED_VIDEO_QUALITY,
|
||||
SET_ROOM,
|
||||
SET_PENDING_SUBJECT_CHANGE,
|
||||
SET_START_MUTED_POLICY
|
||||
@@ -615,23 +613,6 @@ export function setFollowMe(enabled: boolean) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the max frame height that should be received from remote videos.
|
||||
*
|
||||
* @param {number} maxReceiverVideoQuality - The max video frame height to
|
||||
* receive.
|
||||
* @returns {{
|
||||
* type: SET_MAX_RECEIVER_VIDEO_QUALITY,
|
||||
* maxReceiverVideoQuality: number
|
||||
* }}
|
||||
*/
|
||||
export function setMaxReceiverVideoQuality(maxReceiverVideoQuality: number) {
|
||||
return {
|
||||
type: SET_MAX_RECEIVER_VIDEO_QUALITY,
|
||||
maxReceiverVideoQuality
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password to join or lock a specific JitsiConference.
|
||||
*
|
||||
@@ -698,24 +679,6 @@ export function setPassword(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the max frame height the user prefers to send and receive from the
|
||||
* remote participants.
|
||||
*
|
||||
* @param {number} preferredVideoQuality - The max video resolution to send and
|
||||
* receive.
|
||||
* @returns {{
|
||||
* type: SET_PREFERRED_VIDEO_QUALITY,
|
||||
* preferredVideoQuality: number
|
||||
* }}
|
||||
*/
|
||||
export function setPreferredVideoQuality(preferredVideoQuality: number) {
|
||||
return {
|
||||
type: SET_PREFERRED_VIDEO_QUALITY,
|
||||
preferredVideoQuality
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets (the name of) the room of the conference to be joined.
|
||||
*
|
||||
|
||||
@@ -34,15 +34,3 @@ export const EMAIL_COMMAND = 'email';
|
||||
* from the outside is not cool but it should suffice for now.
|
||||
*/
|
||||
export const JITSI_CONFERENCE_URL_KEY = Symbol('url');
|
||||
|
||||
/**
|
||||
* The supported remote video resolutions. The values are currently based on
|
||||
* available simulcast layers.
|
||||
*
|
||||
* @type {object}
|
||||
*/
|
||||
export const VIDEO_QUALITY_LEVELS = {
|
||||
HIGH: 720,
|
||||
STANDARD: 360,
|
||||
LOW: 180
|
||||
};
|
||||
|
||||
@@ -17,8 +17,7 @@ import {
|
||||
AVATAR_ID_COMMAND,
|
||||
AVATAR_URL_COMMAND,
|
||||
EMAIL_COMMAND,
|
||||
JITSI_CONFERENCE_URL_KEY,
|
||||
VIDEO_QUALITY_LEVELS
|
||||
JITSI_CONFERENCE_URL_KEY
|
||||
} from './constants';
|
||||
import logger from './logger';
|
||||
|
||||
@@ -214,38 +213,6 @@ export function getCurrentConference(stateful: Function | Object) {
|
||||
return joining || passwordRequired || membersOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stored room name.
|
||||
*
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
PARTICIPANT_UPDATED,
|
||||
PIN_PARTICIPANT
|
||||
} from '../participants';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { TRACK_ADDED, TRACK_REMOVED } from '../tracks';
|
||||
|
||||
import {
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_SUBJECT_CHANGED,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
DATA_CHANNEL_OPENED,
|
||||
SEND_TONES,
|
||||
SET_PENDING_SUBJECT_CHANGE,
|
||||
SET_ROOM
|
||||
@@ -81,9 +80,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
_conferenceWillLeave();
|
||||
break;
|
||||
|
||||
case DATA_CHANNEL_OPENED:
|
||||
return _syncReceiveVideoQuality(store, next, action);
|
||||
|
||||
case PARTICIPANT_UPDATED:
|
||||
return _updateLocalParticipantInConference(store, next, action);
|
||||
|
||||
@@ -104,31 +100,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Registers a change handler for state['features/base/conference'] to update
|
||||
* the preferred video quality levels based on user preferred and internal
|
||||
* settings.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/conference'],
|
||||
/* listener */ (currentState, store, previousState = {}) => {
|
||||
const {
|
||||
conference,
|
||||
maxReceiverVideoQuality,
|
||||
preferredVideoQuality
|
||||
} = currentState;
|
||||
const changedConference = conference !== previousState.conference;
|
||||
const changedPreferredVideoQuality
|
||||
= preferredVideoQuality !== previousState.preferredVideoQuality;
|
||||
const changedMaxVideoQuality = maxReceiverVideoQuality !== previousState.maxReceiverVideoQuality;
|
||||
|
||||
if (changedConference || changedPreferredVideoQuality || changedMaxVideoQuality) {
|
||||
_setReceiverVideoConstraint(conference, preferredVideoQuality, maxReceiverVideoQuality);
|
||||
}
|
||||
if (changedConference || changedPreferredVideoQuality) {
|
||||
_setSenderVideoConstraint(conference, preferredVideoQuality);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Makes sure to leave a failed conference in order to release any allocated
|
||||
@@ -448,44 +419,6 @@ function _sendTones({ getState }, next, action) {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for updating the preferred receiver video constraint, based
|
||||
* on the user preference and the internal maximum.
|
||||
*
|
||||
* @param {JitsiConference} conference - The JitsiConference instance for the
|
||||
* current call.
|
||||
* @param {number} preferred - The user preferred max frame height.
|
||||
* @param {number} max - The maximum frame height the application should
|
||||
* receive.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _setReceiverVideoConstraint(conference, preferred, max) {
|
||||
if (conference) {
|
||||
const value = Math.min(preferred, max);
|
||||
|
||||
conference.setReceiverVideoConstraint(value);
|
||||
logger.info(`setReceiverVideoConstraint: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for updating the preferred sender video constraint, based
|
||||
* on the user preference.
|
||||
*
|
||||
* @param {JitsiConference} conference - The JitsiConference instance for the
|
||||
* current call.
|
||||
* @param {number} preferred - The user preferred max frame height.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _setSenderVideoConstraint(conference, preferred) {
|
||||
if (conference) {
|
||||
conference.setSenderVideoConstraint(preferred)
|
||||
.catch(err => {
|
||||
logger.error(`Changing sender resolution to ${preferred} failed - ${err} `);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature base/conference that the action
|
||||
* {@code SET_ROOM} is being dispatched within a specific
|
||||
@@ -539,33 +472,6 @@ function _syncConferenceLocalTracksWithState({ getState }, action) {
|
||||
return promise || Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum receive video quality.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code DATA_CHANNEL_STATUS_CHANGED}
|
||||
* which is being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _syncReceiveVideoQuality({ getState }, next, action) {
|
||||
const {
|
||||
conference,
|
||||
maxReceiverVideoQuality,
|
||||
preferredVideoQuality
|
||||
} = getState()['features/base/conference'];
|
||||
|
||||
_setReceiverVideoConstraint(
|
||||
conference,
|
||||
preferredVideoQuality,
|
||||
maxReceiverVideoQuality);
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature base/conference that the action {@code TRACK_ADDED}
|
||||
* or {@code TRACK_REMOVED} is being dispatched within a specific redux store.
|
||||
@@ -624,7 +530,7 @@ function _updateLocalParticipantInConference({ dispatch, getState }, next, actio
|
||||
|
||||
// When the local user role is updated to moderator and we have a pending subject change
|
||||
// which was not reflected we need to set it (the first time we tried was before becoming moderator).
|
||||
if (pendingSubjectChange !== subject) {
|
||||
if (typeof pendingSubjectChange !== 'undefined' && pendingSubjectChange !== subject) {
|
||||
dispatch(setSubject(pendingSubjectChange));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,12 @@ import {
|
||||
P2P_STATUS_CHANGED,
|
||||
SET_DESKTOP_SHARING_ENABLED,
|
||||
SET_FOLLOW_ME,
|
||||
SET_MAX_RECEIVER_VIDEO_QUALITY,
|
||||
SET_PASSWORD,
|
||||
SET_PENDING_SUBJECT_CHANGE,
|
||||
SET_PREFERRED_VIDEO_QUALITY,
|
||||
SET_ROOM,
|
||||
SET_SIP_GATEWAY_ENABLED,
|
||||
SET_START_MUTED_POLICY
|
||||
} from './actionTypes';
|
||||
import { VIDEO_QUALITY_LEVELS } from './constants';
|
||||
import { isRoomValid } from './functions';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
@@ -35,11 +32,9 @@ const DEFAULT_STATE = {
|
||||
joining: undefined,
|
||||
leaving: undefined,
|
||||
locked: undefined,
|
||||
maxReceiverVideoQuality: VIDEO_QUALITY_LEVELS.HIGH,
|
||||
membersOnly: undefined,
|
||||
password: undefined,
|
||||
passwordRequired: undefined,
|
||||
preferredVideoQuality: VIDEO_QUALITY_LEVELS.HIGH
|
||||
passwordRequired: undefined
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -90,24 +85,12 @@ ReducerRegistry.register(
|
||||
case SET_LOCATION_URL:
|
||||
return set(state, 'room', undefined);
|
||||
|
||||
case SET_MAX_RECEIVER_VIDEO_QUALITY:
|
||||
return set(
|
||||
state,
|
||||
'maxReceiverVideoQuality',
|
||||
action.maxReceiverVideoQuality);
|
||||
|
||||
case SET_PASSWORD:
|
||||
return _setPassword(state, action);
|
||||
|
||||
case SET_PENDING_SUBJECT_CHANGE:
|
||||
return set(state, 'pendingSubjectChange', action.subject);
|
||||
|
||||
case SET_PREFERRED_VIDEO_QUALITY:
|
||||
return set(
|
||||
state,
|
||||
'preferredVideoQuality',
|
||||
action.preferredVideoQuality);
|
||||
|
||||
case SET_ROOM:
|
||||
return _setRoom(state, action);
|
||||
|
||||
|
||||
@@ -43,8 +43,8 @@ export const SET_CONFIG = 'SET_CONFIG';
|
||||
* and the passed object.
|
||||
*
|
||||
* {
|
||||
* type: _UPDATE_CONFIG,
|
||||
* type: UPDATE_CONFIG,
|
||||
* config: Object
|
||||
* }
|
||||
*/
|
||||
export const _UPDATE_CONFIG = '_UPDATE_CONFIG';
|
||||
export const UPDATE_CONFIG = 'UPDATE_CONFIG';
|
||||
|
||||
@@ -6,10 +6,24 @@ import type { Dispatch } from 'redux';
|
||||
import { addKnownDomains } from '../known-domains';
|
||||
import { parseURIString } from '../util';
|
||||
|
||||
import { CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes';
|
||||
import { CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG, UPDATE_CONFIG } from './actionTypes';
|
||||
import { _CONFIG_STORE_PREFIX } from './constants';
|
||||
import { setConfigFromURLParams } from './functions';
|
||||
|
||||
|
||||
/**
|
||||
* Updates the config with new options.
|
||||
*
|
||||
* @param {Object} config - The new options (to add).
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function updateConfig(config: Object) {
|
||||
return {
|
||||
type: UPDATE_CONFIG,
|
||||
config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the configuration (commonly known in Jitsi Meet as config.js)
|
||||
* for a specific locationURL will be loaded now.
|
||||
|
||||
@@ -15,8 +15,7 @@ export default [
|
||||
'abTesting',
|
||||
'analytics.disabled',
|
||||
'audioLevelsInterval',
|
||||
'autoRecord',
|
||||
'autoRecordToken',
|
||||
'apiLogLevels',
|
||||
'avgRtpStatsN',
|
||||
|
||||
/**
|
||||
@@ -69,6 +68,7 @@ export default [
|
||||
|
||||
'channelLastN',
|
||||
'constraints',
|
||||
'brandingRoomAlias',
|
||||
'debug',
|
||||
'debugAudioLevels',
|
||||
'defaultLanguage',
|
||||
@@ -100,12 +100,14 @@ export default [
|
||||
'enableInsecureRoomNameWarning',
|
||||
'enableLayerSuspension',
|
||||
'enableLipSync',
|
||||
'enableOpusRed',
|
||||
'enableRemb',
|
||||
'enableScreenshotCapture',
|
||||
'enableTalkWhileMuted',
|
||||
'enableNoAudioDetection',
|
||||
'enableNoisyMicDetection',
|
||||
'enableTcc',
|
||||
'enableAutomaticUrlCopy',
|
||||
'etherpad_base',
|
||||
'failICE',
|
||||
'feedbackPercentage',
|
||||
@@ -115,6 +117,7 @@ export default [
|
||||
'gatherStats',
|
||||
'googleApiApplicationClientID',
|
||||
'hiddenDomain',
|
||||
'hideLobbyButton',
|
||||
'hosts',
|
||||
'iAmRecorder',
|
||||
'iAmSipGateway',
|
||||
@@ -148,6 +151,7 @@ export default [
|
||||
'testing',
|
||||
'useStunTurn',
|
||||
'useTurnUdp',
|
||||
'videoQuality.persist',
|
||||
'webrtcIceTcpDisable',
|
||||
'webrtcIceUdpDisable'
|
||||
].concat(extraConfigWhitelist);
|
||||
|
||||
@@ -8,7 +8,8 @@ import { addKnownDomains } from '../known-domains';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { parseURIString } from '../util';
|
||||
|
||||
import { _UPDATE_CONFIG, SET_CONFIG } from './actionTypes';
|
||||
import { SET_CONFIG } from './actionTypes';
|
||||
import { updateConfig } from './actions';
|
||||
import { _CONFIG_STORE_PREFIX } from './constants';
|
||||
|
||||
/**
|
||||
@@ -114,10 +115,7 @@ function _setConfig({ dispatch, getState }, next, action) {
|
||||
config.resolution = resolutionFlag;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: _UPDATE_CONFIG,
|
||||
config
|
||||
});
|
||||
dispatch(updateConfig(config));
|
||||
|
||||
// FIXME On Web we rely on the global 'config' variable which gets altered
|
||||
// multiple times, before it makes it to the reducer. At some point it may
|
||||
|
||||
@@ -4,7 +4,7 @@ import _ from 'lodash';
|
||||
|
||||
import { equals, ReducerRegistry, set } from '../redux';
|
||||
|
||||
import { _UPDATE_CONFIG, CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes';
|
||||
import { UPDATE_CONFIG, CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes';
|
||||
import { _cleanupConfig } from './functions';
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ const INITIAL_RN_STATE = {
|
||||
|
||||
ReducerRegistry.register('features/base/config', (state = _getInitialState(), action) => {
|
||||
switch (action.type) {
|
||||
case _UPDATE_CONFIG:
|
||||
case UPDATE_CONFIG:
|
||||
return _updateConfig(state, action);
|
||||
|
||||
case CONFIG_WILL_LOAD:
|
||||
|
||||
@@ -80,12 +80,8 @@ export function connect(id: ?string, password: ?string) {
|
||||
const state = getState();
|
||||
const options = _constructOptions(state);
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const { issuer, jwt } = state['features/base/jwt'];
|
||||
const connection
|
||||
= new JitsiMeetJS.JitsiConnection(
|
||||
options.appId,
|
||||
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
|
||||
options);
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
const connection = new JitsiMeetJS.JitsiConnection(options.appId, jwt, options);
|
||||
|
||||
connection[JITSI_CONNECTION_URL_KEY] = locationURL;
|
||||
|
||||
|
||||
@@ -54,7 +54,17 @@ export function getInviteURL(stateOrGetState: Function | Object): string {
|
||||
throw new Error('Can not get invite URL - the app is not ready');
|
||||
}
|
||||
|
||||
return getURLWithoutParams(locationURL).href;
|
||||
const { inviteDomain } = state['features/dynamic-branding'];
|
||||
const urlWithoutParams = getURLWithoutParams(locationURL);
|
||||
|
||||
if (inviteDomain) {
|
||||
const meetingId
|
||||
= state['features/base/config'].brandingRoomAlias || urlWithoutParams.pathname;
|
||||
|
||||
return `${inviteDomain}/${meetingId}`;
|
||||
}
|
||||
|
||||
return urlWithoutParams.href;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,6 @@ import { processExternalDeviceRequest } from '../../device-selection';
|
||||
import { showNotification, showWarningNotification } from '../../notifications';
|
||||
import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { CONFERENCE_JOINED } from '../conference';
|
||||
import { JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { updateSettings } from '../settings';
|
||||
@@ -24,6 +23,7 @@ import {
|
||||
setVideoInputDevice
|
||||
} from './actions';
|
||||
import {
|
||||
areDeviceLabelsInitialized,
|
||||
formatDeviceLabel,
|
||||
groupDevicesByKind,
|
||||
setAudioOutputDeviceId
|
||||
@@ -73,8 +73,6 @@ function logDeviceList(deviceList) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED:
|
||||
return _conferenceJoined(store, next, action);
|
||||
case NOTIFY_CAMERA_ERROR: {
|
||||
if (typeof APP !== 'object' || !action.error) {
|
||||
break;
|
||||
@@ -148,6 +146,9 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
case UPDATE_DEVICE_LIST:
|
||||
logDeviceList(groupDevicesByKind(action.devices));
|
||||
if (areDeviceLabelsInitialized(store.getState())) {
|
||||
return _processPendingRequests(store, next, action);
|
||||
}
|
||||
break;
|
||||
case CHECK_AND_NOTIFY_FOR_NEW_DEVICE:
|
||||
_checkAndNotifyForNewDevice(store, action.newDevices, action.oldDevices);
|
||||
@@ -170,11 +171,15 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _conferenceJoined({ dispatch, getState }, next, action) {
|
||||
function _processPendingRequests({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
const state = getState();
|
||||
const { pendingRequests } = state['features/base/devices'];
|
||||
|
||||
if (!pendingRequests || pendingRequests.length === 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
pendingRequests.forEach(request => {
|
||||
processExternalDeviceRequest(
|
||||
dispatch,
|
||||
|
||||
@@ -98,4 +98,7 @@ export { default as IconVolume } from './volume.svg';
|
||||
export { default as IconVolumeEmpty } from './volume-empty.svg';
|
||||
export { default as IconVolumeOff } from './volume-off.svg';
|
||||
export { default as IconWarning } from './warning.svg';
|
||||
export { default as IconWifi1Bar } from './wifi-1.svg';
|
||||
export { default as IconWifi2Bars } from './wifi-2.svg';
|
||||
export { default as IconWifi3Bars } from './wifi-3.svg';
|
||||
export { default as IconYahoo } from './yahoo.svg';
|
||||
|
||||
5
react/features/base/icons/svg/wifi-1.svg
Normal file
5
react/features/base/icons/svg/wifi-1.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.4" d="M13.0913 6.59847C12.4227 5.88894 11.629 5.32611 10.7554 4.94212C9.88182 4.55812 8.94553 4.36048 7.99997 4.36048C7.05442 4.36048 6.11813 4.55812 5.24456 4.94212C4.371 5.32611 3.57726 5.88894 2.90869 6.59847L4.36305 8.14176C4.84061 7.63486 5.4076 7.23276 6.03163 6.95842C6.65566 6.68408 7.32451 6.54288 7.99997 6.54288C8.67544 6.54288 9.34429 6.68408 9.96832 6.95842C10.5923 7.23276 11.1593 7.63486 11.6369 8.14176L13.0913 6.59847Z" fill="white"/>
|
||||
<path opacity="0.4" d="M16 3.51081C13.8766 1.26261 10.9996 0 8 0C5.00044 0 2.12337 1.26261 0 3.51081L1.45436 5.0541C3.19156 3.21432 5.54565 2.18105 8 2.18105C10.4543 2.18105 12.8084 3.21432 14.5456 5.0541L16 3.51081Z" fill="white"/>
|
||||
<path d="M5.94287 9.81713L7.99996 12L10.057 9.81713C9.78693 9.53041 9.46623 9.30297 9.11328 9.1478C8.76032 8.99263 8.38201 8.91276 7.99996 8.91276C7.6179 8.91276 7.23959 8.99263 6.88663 9.1478C6.53368 9.30297 6.21298 9.53041 5.94287 9.81713Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
5
react/features/base/icons/svg/wifi-2.svg
Normal file
5
react/features/base/icons/svg/wifi-2.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.0913 6.59847C12.4227 5.88894 11.629 5.32611 10.7554 4.94211C9.88182 4.55811 8.94553 4.36047 7.99997 4.36047C7.05442 4.36047 6.11813 4.55811 5.24456 4.94211C4.371 5.32611 3.57726 5.88894 2.90869 6.59847L4.36305 8.14176C4.84061 7.63486 5.4076 7.23276 6.03163 6.95842C6.65566 6.68408 7.32451 6.54288 7.99997 6.54288C8.67544 6.54288 9.34429 6.68408 9.96832 6.95842C10.5923 7.23276 11.1593 7.63486 11.6369 8.14176L13.0913 6.59847Z" fill="white"/>
|
||||
<path opacity="0.4" d="M16 3.51081C13.8766 1.26261 10.9996 0 8 0C5.00044 0 2.12337 1.26261 0 3.51081L1.45436 5.0541C3.19156 3.21432 5.54565 2.18105 8 2.18105C10.4543 2.18105 12.8084 3.21432 14.5456 5.0541L16 3.51081Z" fill="white"/>
|
||||
<path d="M5.94287 9.81713L7.99996 12L10.057 9.81713C9.78693 9.53042 9.46623 9.30298 9.11328 9.14781C8.76032 8.99263 8.38201 8.91277 7.99996 8.91277C7.6179 8.91277 7.23959 8.99263 6.88663 9.14781C6.53368 9.30298 6.21298 9.53042 5.94287 9.81713Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
5
react/features/base/icons/svg/wifi-3.svg
Normal file
5
react/features/base/icons/svg/wifi-3.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.0913 6.59847C12.4227 5.88894 11.629 5.32611 10.7554 4.94211C9.88182 4.55811 8.94553 4.36047 7.99997 4.36047C7.05442 4.36047 6.11813 4.55811 5.24456 4.94211C4.371 5.32611 3.57726 5.88894 2.90869 6.59847L4.36305 8.14176C4.84061 7.63486 5.4076 7.23276 6.03163 6.95842C6.65566 6.68408 7.32451 6.54288 7.99997 6.54288C8.67544 6.54288 9.34429 6.68408 9.96832 6.95842C10.5923 7.23276 11.1593 7.63486 11.6369 8.14176L13.0913 6.59847Z" fill="white"/>
|
||||
<path d="M16 3.51081C13.8766 1.26261 10.9996 0 8 0C5.00044 0 2.12337 1.26261 0 3.51081L1.45436 5.0541C3.19156 3.21432 5.54565 2.18105 8 2.18105C10.4543 2.18105 12.8084 3.21432 14.5456 5.0541L16 3.51081Z" fill="white"/>
|
||||
<path d="M5.94287 9.81713L7.99996 12L10.057 9.81713C9.78693 9.53042 9.46623 9.30298 9.11328 9.14781C8.76032 8.99263 8.38201 8.91277 7.99996 8.91277C7.6179 8.91277 7.23959 8.99263 6.88663 9.14781C6.53368 9.30298 6.21298 9.53042 5.94287 9.81713Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
5
react/features/base/jwt/logger.js
Normal file
5
react/features/base/jwt/logger.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from '../logging/functions';
|
||||
|
||||
export default getLogger('features/base/jwt');
|
||||
@@ -13,6 +13,7 @@ import { MiddlewareRegistry } from '../redux';
|
||||
import { SET_JWT } from './actionTypes';
|
||||
import { setJWT } from './actions';
|
||||
import { parseJWTFromURLParams } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
@@ -133,7 +134,13 @@ function _setJWT(store, next, action) {
|
||||
|
||||
action.isGuest = !enableUserRolesBasedOnToken;
|
||||
|
||||
const jwtPayload = jwtDecode(jwt);
|
||||
let jwtPayload;
|
||||
|
||||
try {
|
||||
jwtPayload = jwtDecode(jwt);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
|
||||
if (jwtPayload) {
|
||||
const { context, iss } = jwtPayload;
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import { limitLastN, validateLastNLimits } from './functions';
|
||||
|
||||
describe('limitLastN', () => {
|
||||
it('handles undefined mapping', () => {
|
||||
expect(limitLastN(0, undefined)).toBe(undefined);
|
||||
});
|
||||
describe('when a correct limit mapping is given', () => {
|
||||
const limits = new Map();
|
||||
|
||||
limits.set(5, -1);
|
||||
limits.set(10, 8);
|
||||
limits.set(20, 5);
|
||||
|
||||
it('returns undefined when less participants that the first limit', () => {
|
||||
expect(limitLastN(2, limits)).toBe(undefined);
|
||||
});
|
||||
it('picks the first limit correctly', () => {
|
||||
expect(limitLastN(5, limits)).toBe(-1);
|
||||
expect(limitLastN(9, limits)).toBe(-1);
|
||||
});
|
||||
it('picks the middle limit correctly', () => {
|
||||
expect(limitLastN(10, limits)).toBe(8);
|
||||
expect(limitLastN(13, limits)).toBe(8);
|
||||
expect(limitLastN(19, limits)).toBe(8);
|
||||
});
|
||||
it('picks the top limit correctly', () => {
|
||||
expect(limitLastN(20, limits)).toBe(5);
|
||||
expect(limitLastN(23, limits)).toBe(5);
|
||||
expect(limitLastN(100, limits)).toBe(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateLastNLimits', () => {
|
||||
describe('validates the input by returning undefined', () => {
|
||||
it('if lastNLimits param is not an Object', () => {
|
||||
expect(validateLastNLimits(5)).toBe(undefined);
|
||||
});
|
||||
it('if any key is not a number', () => {
|
||||
const limits = {
|
||||
'abc': 8,
|
||||
5: -1,
|
||||
20: 5
|
||||
};
|
||||
|
||||
expect(validateLastNLimits(limits)).toBe(undefined);
|
||||
});
|
||||
it('if any value is not a number', () => {
|
||||
const limits = {
|
||||
8: 'something',
|
||||
5: -1,
|
||||
20: 5
|
||||
};
|
||||
|
||||
expect(validateLastNLimits(limits)).toBe(undefined);
|
||||
});
|
||||
it('if any value is null', () => {
|
||||
const limits = {
|
||||
1: 1,
|
||||
5: null,
|
||||
20: 5
|
||||
};
|
||||
|
||||
expect(validateLastNLimits(limits)).toBe(undefined);
|
||||
});
|
||||
it('if any value is undefined', () => {
|
||||
const limits = {
|
||||
1: 1,
|
||||
5: undefined,
|
||||
20: 5
|
||||
};
|
||||
|
||||
expect(validateLastNLimits(limits)).toBe(undefined);
|
||||
});
|
||||
it('if the map is empty', () => {
|
||||
expect(validateLastNLimits({})).toBe(undefined);
|
||||
});
|
||||
});
|
||||
it('sorts by the keys', () => {
|
||||
const mappingKeys = validateLastNLimits({
|
||||
10: 5,
|
||||
3: 3,
|
||||
5: 4
|
||||
}).keys();
|
||||
|
||||
expect(mappingKeys.next().value).toBe(3);
|
||||
expect(mappingKeys.next().value).toBe(5);
|
||||
expect(mappingKeys.next().value).toBe(10);
|
||||
expect(mappingKeys.next().done).toBe(true);
|
||||
});
|
||||
it('converts keys and values to numbers', () => {
|
||||
const mapping = validateLastNLimits({
|
||||
3: 3,
|
||||
5: 4,
|
||||
10: 5
|
||||
});
|
||||
|
||||
for (const key of mapping.keys()) {
|
||||
expect(typeof key).toBe('number');
|
||||
expect(typeof mapping.get(key)).toBe('number');
|
||||
}
|
||||
});
|
||||
});
|
||||
22
react/features/base/logging/ExternalApiLogTransport.js
Normal file
22
react/features/base/logging/ExternalApiLogTransport.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// @flow
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Constructs a log transport object for use with external API.
|
||||
*
|
||||
* @param {Array} levels - The log levels forwarded to the external API.
|
||||
|
||||
* @returns {Object} - The transport object.
|
||||
*/
|
||||
function buildTransport(levels: Array<string>) {
|
||||
return levels.reduce((logger, level) => {
|
||||
logger[level] = (...args) => {
|
||||
APP.API.notifyLog(level, args);
|
||||
};
|
||||
|
||||
return logger;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export default buildTransport;
|
||||
@@ -11,6 +11,7 @@ import JitsiMeetJS, {
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { isTestModeEnabled } from '../testing';
|
||||
|
||||
import buildExternalApiLogTransport from './ExternalApiLogTransport';
|
||||
import JitsiMeetInMemoryLogStorage from './JitsiMeetInMemoryLogStorage';
|
||||
import JitsiMeetLogStorage from './JitsiMeetLogStorage';
|
||||
import { SET_LOGGING_CONFIG } from './actionTypes';
|
||||
@@ -141,6 +142,15 @@ function _initLogging({ dispatch, getState }, loggingConfig, isTestingEnabled) {
|
||||
const _logCollector
|
||||
= new Logger.LogCollector(new JitsiMeetLogStorage(getState));
|
||||
|
||||
const { apiLogLevels } = getState()['features/base/config'];
|
||||
|
||||
if (apiLogLevels && Array.isArray(apiLogLevels) && typeof APP === 'object') {
|
||||
const transport = buildExternalApiLogTransport(apiLogLevels);
|
||||
|
||||
Logger.addGlobalTransport(transport);
|
||||
JitsiMeetJS.addGlobalLogTransport(transport);
|
||||
}
|
||||
|
||||
Logger.addGlobalTransport(_logCollector);
|
||||
JitsiMeetJS.addGlobalLogTransport(_logCollector);
|
||||
dispatch(setLogCollector(_logCollector));
|
||||
|
||||
@@ -70,6 +70,7 @@ function ActionButton({
|
||||
{children}
|
||||
{hasOptions && <div
|
||||
className = 'options'
|
||||
data-testid = 'prejoin.joinOptions'
|
||||
onClick = { disabled ? undefined : onOptionsClick }>
|
||||
<Icon
|
||||
className = 'icon'
|
||||
|
||||
61
react/features/base/premeeting/components/web/Avatar.js
Normal file
61
react/features/base/premeeting/components/web/Avatar.js
Normal file
@@ -0,0 +1,61 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Avatar } from '../../../avatar';
|
||||
import { connect } from '../../../redux';
|
||||
import { calculateAvatarDimensions } from '../../functions';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The height of the window.
|
||||
*/
|
||||
height: number,
|
||||
|
||||
/**
|
||||
* The name of the participant (if any).
|
||||
*/
|
||||
name: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Component displaying the avatar for the premeeting screen.
|
||||
*
|
||||
* @param {Props} props - The props of the component.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function PremeetingAvatar({ height, name }: Props) {
|
||||
const { marginTop, size } = calculateAvatarDimensions(height);
|
||||
|
||||
if (size <= 5) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div style = {{ marginTop }}>
|
||||
<Avatar
|
||||
className = 'preview-avatar'
|
||||
displayName = { name }
|
||||
participantId = 'local'
|
||||
size = { size } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the React {@code Component} props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {{
|
||||
* height: number
|
||||
* }}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
height: state['features/base/responsive-ui'].clientHeight
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(PremeetingAvatar);
|
||||
@@ -0,0 +1,104 @@
|
||||
// @flow
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
import { Icon, IconArrowDownSmall, IconWifi1Bar, IconWifi2Bars, IconWifi3Bars } from '../../../icons';
|
||||
import { connect } from '../../../redux';
|
||||
import { CONNECTION_TYPE } from '../../constants';
|
||||
import { getConnectionData } from '../../functions';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* List of strings with details about the connection.
|
||||
*/
|
||||
connectionDetails: string[],
|
||||
|
||||
/**
|
||||
* The type of the connection. Can be: 'none', 'poor', 'nonOptimal' or 'good'.
|
||||
*/
|
||||
connectionType: string,
|
||||
|
||||
/**
|
||||
* Used for translation.
|
||||
*/
|
||||
t: Function
|
||||
}
|
||||
|
||||
const CONNECTION_TYPE_MAP = {
|
||||
[CONNECTION_TYPE.POOR]: {
|
||||
connectionClass: 'con-status--poor',
|
||||
icon: IconWifi1Bar,
|
||||
connectionText: 'prejoin.connection.poor'
|
||||
},
|
||||
[CONNECTION_TYPE.NON_OPTIMAL]: {
|
||||
connectionClass: 'con-status--non-optimal',
|
||||
icon: IconWifi2Bars,
|
||||
connectionText: 'prejoin.connection.nonOptimal'
|
||||
},
|
||||
[CONNECTION_TYPE.GOOD]: {
|
||||
connectionClass: 'con-status--good',
|
||||
icon: IconWifi3Bars,
|
||||
connectionText: 'prejoin.connection.good'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Component displaying information related to the connection & audio/video quality.
|
||||
*
|
||||
* @param {Props} props - The props of the component.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function ConnectionStatus({ connectionDetails, t, connectionType }: Props) {
|
||||
if (connectionType === CONNECTION_TYPE.NONE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { connectionClass, icon, connectionText } = CONNECTION_TYPE_MAP[connectionType];
|
||||
const [ showDetails, toggleDetails ] = useState(false);
|
||||
const arrowClassName = showDetails
|
||||
? 'con-status-arrow con-status-arrow--up'
|
||||
: 'con-status-arrow';
|
||||
const detailsText = connectionDetails.map(t).join(' ');
|
||||
|
||||
return (
|
||||
<div className = 'con-status'>
|
||||
<div className = 'con-status-container'>
|
||||
<div className = 'con-status-header'>
|
||||
<div className = { `con-status-circle ${connectionClass}` }>
|
||||
<Icon
|
||||
size = { 16 }
|
||||
src = { icon } />
|
||||
</div>
|
||||
<span className = 'con-status-text'>{t(connectionText)}</span>
|
||||
<Icon
|
||||
className = { arrowClassName }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick = { () => toggleDetails(!showDetails) }
|
||||
size = { 24 }
|
||||
src = { IconArrowDownSmall } />
|
||||
</div>
|
||||
{ showDetails
|
||||
&& <div className = 'con-status-details'>{detailsText}</div> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the React {@code Component} props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state): Object {
|
||||
const { connectionDetails, connectionType } = getConnectionData(state);
|
||||
|
||||
return {
|
||||
connectionDetails,
|
||||
connectionType
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(ConnectionStatus));
|
||||
@@ -18,7 +18,13 @@ type Props = {
|
||||
/**
|
||||
* Used for translation.
|
||||
*/
|
||||
t: Function
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* Used to determine if invitation link should be automatically copied
|
||||
* after creating a meeting.
|
||||
*/
|
||||
_enableAutomaticUrlCopy: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -58,6 +64,7 @@ class CopyMeetingUrl extends Component<Props, State> {
|
||||
this._hideLinkCopied = this._hideLinkCopied.bind(this);
|
||||
this._showCopyLink = this._showCopyLink.bind(this);
|
||||
this._showLinkCopied = this._showLinkCopied.bind(this);
|
||||
this._copyUrlAutomatically = this._copyUrlAutomatically.bind(this);
|
||||
}
|
||||
|
||||
_copyUrl: () => void;
|
||||
@@ -135,6 +142,37 @@ class CopyMeetingUrl extends Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
_copyUrlAutomatically: () => void;
|
||||
|
||||
/**
|
||||
* Attempts to automatically copy invitation URL.
|
||||
* Document has to be focused in order for this to work.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_copyUrlAutomatically() {
|
||||
navigator.clipboard.writeText(this.props.url)
|
||||
.then(() => {
|
||||
this._showLinkCopied();
|
||||
window.setTimeout(this._hideLinkCopied, COPY_TIMEOUT);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount()}. Invoked
|
||||
* immediately before mounting occurs.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
const { _enableAutomaticUrlCopy } = this.props;
|
||||
|
||||
if (_enableAutomaticUrlCopy) {
|
||||
setTimeout(this._copyUrlAutomatically, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -177,8 +215,11 @@ class CopyMeetingUrl extends Component<Props, State> {
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
const { enableAutomaticUrlCopy } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
url: getCurrentConferenceUrl(state)
|
||||
url: getCurrentConferenceUrl(state),
|
||||
_enableAutomaticUrlCopy: enableAutomaticUrlCopy || false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,11 @@ import { getFieldValue } from '../../../react';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* If the input should be focused on display.
|
||||
*/
|
||||
autoFocus?: boolean,
|
||||
|
||||
/**
|
||||
* Class name to be appended to the default class list.
|
||||
*/
|
||||
@@ -109,6 +114,7 @@ export default class InputField extends PureComponent<Props, State> {
|
||||
render() {
|
||||
return (
|
||||
<input
|
||||
autoFocus = { this.props.autoFocus }
|
||||
className = { `field ${this.state.focused ? 'focused' : ''} ${this.props.className || ''}` }
|
||||
data-testid = { this.props.testId ? this.props.testId : undefined }
|
||||
onBlur = { this._onBlur }
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, { PureComponent } from 'react';
|
||||
|
||||
import { AudioSettingsButton, VideoSettingsButton } from '../../../../toolbox/components/web';
|
||||
|
||||
import ConnectionStatus from './ConnectionStatus';
|
||||
import CopyMeetingUrl from './CopyMeetingUrl';
|
||||
import Preview from './Preview';
|
||||
|
||||
@@ -82,6 +83,7 @@ export default class PreMeetingScreen extends PureComponent<Props> {
|
||||
<div
|
||||
className = 'premeeting-screen'
|
||||
id = 'lobby-screen'>
|
||||
<ConnectionStatus />
|
||||
<Preview
|
||||
name = { name }
|
||||
showAvatar = { showAvatar }
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Avatar } from '../../../avatar';
|
||||
import { Video } from '../../../media';
|
||||
import { connect } from '../../../redux';
|
||||
import { getLocalVideoTrack } from '../../../tracks';
|
||||
|
||||
import PreviewAvatar from './Avatar';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
@@ -54,13 +55,7 @@ function Preview(props: Props) {
|
||||
<div
|
||||
className = 'no-video'
|
||||
id = 'preview'>
|
||||
<div className = 'preview-avatar-container'>
|
||||
<Avatar
|
||||
className = 'preview-avatar'
|
||||
displayName = { name }
|
||||
participantId = 'local'
|
||||
size = { 200 } />
|
||||
</div>
|
||||
<PreviewAvatar name = { name } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
8
react/features/base/premeeting/constants.js
Normal file
8
react/features/base/premeeting/constants.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// @flow
|
||||
|
||||
export const CONNECTION_TYPE = {
|
||||
GOOD: 'good',
|
||||
NON_OPTIMAL: 'nonOptimal',
|
||||
NONE: 'none',
|
||||
POOR: 'poor'
|
||||
};
|
||||
213
react/features/base/premeeting/functions.js
Normal file
213
react/features/base/premeeting/functions.js
Normal file
@@ -0,0 +1,213 @@
|
||||
// @flow
|
||||
|
||||
import { findIndex } from 'lodash';
|
||||
|
||||
import { CONNECTION_TYPE } from './constants';
|
||||
|
||||
const LOSS_AUDIO_THRESHOLDS = [ 0.33, 0.05 ];
|
||||
const LOSS_VIDEO_THRESHOLDS = [ 0.33, 0.1, 0.05 ];
|
||||
|
||||
const THROUGHPUT_AUDIO_THRESHOLDS = [ 8, 20 ];
|
||||
const THROUGHPUT_VIDEO_THRESHOLDS = [ 60, 750 ];
|
||||
|
||||
/**
|
||||
* The avatar size to container size ration.
|
||||
*/
|
||||
const ratio = 1 / 3;
|
||||
|
||||
/**
|
||||
* The max avatar size.
|
||||
*/
|
||||
const maxSize = 190;
|
||||
|
||||
/**
|
||||
* The window limit hight over which the avatar should have the default dimension.
|
||||
*/
|
||||
const upperHeightLimit = 760;
|
||||
|
||||
/**
|
||||
* The window limit hight under which the avatar should not be resized anymore.
|
||||
*/
|
||||
const lowerHeightLimit = 460;
|
||||
|
||||
/**
|
||||
* The default top margin of the avatar.
|
||||
*/
|
||||
const defaultMarginTop = '10%';
|
||||
|
||||
/**
|
||||
* The top margin of the avatar when its dimension is small.
|
||||
*/
|
||||
const smallMarginTop = '5%';
|
||||
|
||||
/**
|
||||
* Calculates avatar dimensions based on window height and position.
|
||||
*
|
||||
* @param {number} height - The window height.
|
||||
* @returns {{
|
||||
* marginTop: string,
|
||||
* size: number
|
||||
* }}
|
||||
*/
|
||||
export function calculateAvatarDimensions(height: number) {
|
||||
if (height > upperHeightLimit) {
|
||||
return {
|
||||
size: maxSize,
|
||||
marginTop: defaultMarginTop
|
||||
};
|
||||
}
|
||||
|
||||
if (height > lowerHeightLimit) {
|
||||
const diff = height - lowerHeightLimit;
|
||||
const percent = diff * ratio;
|
||||
const size = Math.floor(maxSize * percent / 100);
|
||||
let marginTop = defaultMarginTop;
|
||||
|
||||
if (height < 600) {
|
||||
marginTop = smallMarginTop;
|
||||
}
|
||||
|
||||
return {
|
||||
size,
|
||||
marginTop
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
size: 0,
|
||||
marginTop: '0'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the level based on a list of thresholds.
|
||||
*
|
||||
* @param {number[]} thresholds - The thresholds array.
|
||||
* @param {number} value - The value against which the level is calculated.
|
||||
* @param {boolean} descending - The order based on which the level is calculated.
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
function _getLevel(thresholds, value, descending = true) {
|
||||
let predicate;
|
||||
|
||||
if (descending) {
|
||||
predicate = function(threshold) {
|
||||
return value > threshold;
|
||||
};
|
||||
} else {
|
||||
predicate = function(threshold) {
|
||||
return value < threshold;
|
||||
};
|
||||
}
|
||||
|
||||
const i = findIndex(thresholds, predicate);
|
||||
|
||||
if (i === -1) {
|
||||
return thresholds.length;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection details from the test results.
|
||||
*
|
||||
* @param {{
|
||||
* fractionalLoss: number,
|
||||
* throughput: number
|
||||
* }} testResults - The state of the app.
|
||||
*
|
||||
* @returns {{
|
||||
* connectionType: string,
|
||||
* connectionDetails: string[]
|
||||
* }}
|
||||
*/
|
||||
function _getConnectionDataFromTestResults({ fractionalLoss: l, throughput: t }) {
|
||||
const loss = {
|
||||
audioQuality: _getLevel(LOSS_AUDIO_THRESHOLDS, l),
|
||||
videoQuality: _getLevel(LOSS_VIDEO_THRESHOLDS, l)
|
||||
};
|
||||
const throughput = {
|
||||
audioQuality: _getLevel(THROUGHPUT_AUDIO_THRESHOLDS, t, false),
|
||||
videoQuality: _getLevel(THROUGHPUT_VIDEO_THRESHOLDS, t, false)
|
||||
};
|
||||
let connectionType = CONNECTION_TYPE.NONE;
|
||||
const connectionDetails = [];
|
||||
|
||||
if (throughput.audioQuality === 0 || loss.audioQuality === 0) {
|
||||
// Calls are impossible.
|
||||
connectionType = CONNECTION_TYPE.POOR;
|
||||
connectionDetails.push('prejoin.connectionDetails.veryPoorConnection');
|
||||
} else if (
|
||||
throughput.audioQuality === 2
|
||||
&& throughput.videoQuality === 2
|
||||
&& loss.audioQuality === 2
|
||||
&& loss.videoQuality === 3
|
||||
) {
|
||||
// Ideal conditions for both audio and video. Show only one message.
|
||||
connectionType = CONNECTION_TYPE.GOOD;
|
||||
connectionDetails.push('prejoin.connectionDetails.goodQuality');
|
||||
} else {
|
||||
connectionType = CONNECTION_TYPE.NON_OPTIMAL;
|
||||
|
||||
if (throughput.audioQuality === 1) {
|
||||
// Minimum requirements for a call are met.
|
||||
connectionDetails.push('prejoin.connectionDetails.audioLowNoVideo');
|
||||
} else {
|
||||
// There are two paragraphs: one saying something about audio and the other about video.
|
||||
if (loss.audioQuality === 1) {
|
||||
connectionDetails.push('prejoin.connectionDetails.audioClipping');
|
||||
} else {
|
||||
connectionDetails.push('prejoin.connectionDetails.audioHighQuality');
|
||||
}
|
||||
|
||||
if (throughput.videoQuality === 0 || loss.videoQuality === 0) {
|
||||
connectionDetails.push('prejoin.connectionDetails.noVideo');
|
||||
} else if (throughput.videoQuality === 1) {
|
||||
connectionDetails.push('prejoin.connectionDetails.videoLowQuality');
|
||||
} else if (loss.videoQuality === 1) {
|
||||
connectionDetails.push('prejoin.connectionDetails.videoFreezing');
|
||||
} else if (loss.videoQuality === 2) {
|
||||
connectionDetails.push('prejoin.connectionDetails.videoTearing');
|
||||
} else {
|
||||
connectionDetails.push('prejoin.connectionDetails.videoHighQuality');
|
||||
}
|
||||
}
|
||||
connectionDetails.push('prejoin.connectionDetails.undetectable');
|
||||
}
|
||||
|
||||
return {
|
||||
connectionType,
|
||||
connectionDetails
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining the connection type & details.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {{
|
||||
* connectionType: string,
|
||||
* connectionDetails: string[]
|
||||
* }}
|
||||
*/
|
||||
export function getConnectionData(state: Object) {
|
||||
const { precallTestResults } = state['features/prejoin'];
|
||||
|
||||
if (precallTestResults) {
|
||||
if (precallTestResults.mediaConnectivity) {
|
||||
return _getConnectionDataFromTestResults(precallTestResults);
|
||||
}
|
||||
|
||||
return {
|
||||
connectionType: CONNECTION_TYPE.POOR,
|
||||
connectionDetails: [ 'prejoin.connectionDetails.noMediaConnectivity' ]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
connectionType: CONNECTION_TYPE.NONE,
|
||||
connectionDetails: []
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import punycode from 'punycode';
|
||||
import React, { Component } from 'react';
|
||||
import ReactLinkify from 'react-linkify';
|
||||
import { Text } from 'react-native';
|
||||
@@ -68,7 +69,7 @@ export default class Linkify extends Component<Props> {
|
||||
key = { key }
|
||||
style = { this.props.linkStyle }
|
||||
url = { decoratedHref }>
|
||||
{decoratedText}
|
||||
{ punycode.toASCII(decoratedText) }
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import punycode from 'punycode';
|
||||
import React, { Component } from 'react';
|
||||
import ReactLinkify from 'react-linkify';
|
||||
|
||||
@@ -44,7 +45,7 @@ export default class Linkify extends Component<Props> {
|
||||
key = { key }
|
||||
rel = 'noopener noreferrer'
|
||||
target = '_blank'>
|
||||
{decoratedText}
|
||||
{ punycode.toASCII(decoratedText) }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user