Compare commits

..

1 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
d490b29b7a deps: replace node-sass with sass
The former is no longer actively maintained.

Fixes: https://github.com/jitsi/jitsi-meet/issues/6427
2020-09-17 16:24:57 +02:00
82 changed files with 1712 additions and 2734 deletions

View File

@@ -16,10 +16,6 @@ android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
packagingOptions {
exclude 'lib/*/libhermes*.so'
}
defaultConfig {
applicationId 'org.jitsi.meet'
versionCode vcode
@@ -78,7 +74,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.2.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-5'
if (!rootProject.ext.libreBuild) {
implementation 'com.google.android.gms:play-services-auth:16.0.1'
@@ -86,7 +82,6 @@ dependencies {
// Firebase
// - Crashlytics
// - Dynamic Links
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'
}

View File

@@ -85,4 +85,8 @@
# ^^^ We added the above when we switched minifyEnabled on.
# Rule to avoid build errors related to SVGs.
-keep public class com.horcrux.svg.** {*;}
-keep public class com.horcrux.svg.** {*;}
# Hermes
-keep class com.facebook.hermes.unicode.** { *; }

View File

@@ -9,44 +9,18 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'com.google.gms:google-services:4.3.3'
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
@@ -161,6 +135,30 @@ 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.

View File

@@ -10,8 +10,7 @@
# 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
@@ -21,5 +20,5 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
android.useAndroidX=true
android.enableJetifier=true
appVersion=20.5.0
sdkVersion=2.11.0
appVersion=20.4.0
sdkVersion=2.10.0

View File

@@ -1,6 +1,6 @@
#Wed Sep 23 11:48:00 EEST 2020
#Fri Mar 08 13:36:51 CET 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip

View File

@@ -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)
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1 | cut -c 2-)
HERMES_VERSION=$(jq -r '.dependencies."hermes-engine"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -c 2-)
DO_GIT_TAG=${GIT_TAG:-0}
if [[ $THE_MVN_REPO == http* ]]; then
@@ -38,17 +38,19 @@ if [[ $MVN_HTTP == 1 ]]; then
-DgeneratePom=false \
-DpomFile=react-native-${RN_VERSION}.pom || true
popd
# 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}
# Push Hermes
echo "Pushing Hermes ${HERMES_VERSION} to the Maven repo"
pushd ${THIS_DIR}/../../node_modules/hermes-engine/android/
mvn \
deploy:deploy-file \
-Durl=${MVN_REPO} \
-DrepositoryId=${MVN_REPO_ID} \
-Dfile=android-jsc-${JSC_VERSION}.aar \
-Dfile=hermes-release.aar \
-Dpackaging=aar \
-DgeneratePom=false \
-DpomFile=android-jsc-${JSC_VERSION}.pom || true
-DgroupId=com.facebook \
-DartifactId=hermes \
-Dversion=${HERMES_VERSION} \
-DgeneratePom=true || true
popd
else
# Push React Native, if necessary
@@ -65,17 +67,19 @@ else
popd
fi
# 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}
# 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/
mvn \
deploy:deploy-file \
-Durl=${MVN_REPO} \
-Dfile=android-jsc-${JSC_VERSION}.aar \
-Dfile=hermes-release.aar \
-Dpackaging=aar \
-DgeneratePom=false \
-DpomFile=android-jsc-${JSC_VERSION}.pom
-DgroupId=com.facebook \
-DartifactId=hermes \
-Dversion=${HERMES_VERSION} \
-DgeneratePom=true
popd
fi

View File

@@ -1,3 +1,5 @@
import groovy.json.JsonSlurper
apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
@@ -33,6 +35,10 @@ android {
}
}
}
packagingOptions {
pickFirst '**/libc++_shared.so'
}
}
dependencies {
@@ -44,8 +50,11 @@ dependencies {
//noinspection GradleDynamicVersion
api 'com.facebook.react:react-native:+'
//noinspection GradleDynamicVersion
implementation 'org.webkit:android-jsc:+'
// Hermes JS engine
def hermesPath = "../../node_modules/hermes-engine/android/"
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
implementation 'com.jakewharton.timber:timber:4.7.1'
@@ -144,7 +153,7 @@ android.libraryVariants.all { def variant ->
mergeResourcesTask.dependsOn(currentBundleTask)
mergeAssetsTask.doLast {
def assetsDir = mergeAssetsTask.outputDir.get()
def assetsDir = mergeAssetsTask.outputDir
// Bundle sounds
//
@@ -178,7 +187,7 @@ android.libraryVariants.all { def variant ->
if (currentBundleTask.enabled) {
copy {
from(resourcesDir)
into(mergeResourcesTask.outputDir.get())
into(mergeResourcesTask.outputDir)
}
}
}
@@ -218,6 +227,14 @@ 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)
}
}

View File

@@ -20,6 +20,7 @@ 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;
@@ -27,7 +28,6 @@ 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,7 +35,6 @@ 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;
@@ -217,9 +216,8 @@ class ReactInstanceManagerHolder {
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
}
// Keep on using JSC, the jury is out on Hermes.
JSCExecutorFactory jsFactory
= new JSCExecutorFactory("", "");
// Use the Hermes JavaScript engine.
HermesExecutorFactory jsFactory = new HermesExecutorFactory();
reactInstanceManager
= ReactInstanceManager.builder()

10
app.js
View File

@@ -6,11 +6,6 @@ import 'jQuery-Impromptu';
import 'olm';
// We need to setup the jitsi-local-storage as early as possible so that we can start using it.
// NOTE: If jitsi-local-storage is used before the initial setup is performed this will break the use case when we use
// the local storage from the parent page when the localStorage is disabled. Also the setup is relying that
// window.location is not changed and still has all URL parameters.
import './react/features/base/jitsi-local-storage/setup';
import conference from './conference';
import API from './modules/API';
import UI from './modules/UI/UI';
@@ -20,10 +15,7 @@ import translation from './modules/translation/translation';
// Initialize Olm as early as possible.
if (window.Olm) {
window.Olm.init().catch(e => {
console.error('Failed to initialize Olm, E2EE will be disabled', e);
delete window.Olm;
});
window.Olm.init();
}
window.APP = {

View File

@@ -108,7 +108,6 @@ import {
getBackendSafePath,
getJitsiMeetGlobalNS
} from './react/features/base/util';
import { downloadJSON } from './react/features/base/util/downloadJSON';
import { showDesktopPicker } from './react/features/desktop-picker';
import { appendSuffix } from './react/features/display-name';
import {
@@ -1222,8 +1221,19 @@ export default {
// this can be called from console and will not have reference to this
// that's why we reference the global var
const logs = APP.connection.getLogs();
const data = encodeURIComponent(JSON.stringify(logs, null, ' '));
downloadJSON(logs, filename);
const elem = document.createElement('a');
elem.download = filename;
elem.href = `data:application/json;charset=utf-8,\n${data}`;
elem.dataset.downloadurl
= [ 'text/json', elem.download, elem.href ].join(':');
elem.dispatchEvent(new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: false
}));
},
/**

View File

@@ -14,6 +14,9 @@ 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',
@@ -64,11 +67,6 @@ 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
@@ -337,7 +335,6 @@ var config = {
// 'datachannel'), undefined (treat it as 'datachannel') and false (don't
// open any channel).
// openBridgeChannel: true,
openBridgeChannel: 'websocket',
// UI
@@ -638,6 +635,8 @@ var config = {
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
autoRecord
autoRecordToken
debug
debugAudioLevels
deploymentInfo

View File

@@ -45,8 +45,10 @@
@extend .connection-info__icon;
}
.connection-actions {
.showmore {
display: block;
margin: 10px auto;
text-align: center;
width: 90px;
}
}

View File

@@ -45,12 +45,8 @@ case "$1" in
rm -rf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.*
rm -rf /var/lib/prosody/$JVB_HOSTNAME.*
# 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
# clean created users
rm -rf /var/lib/prosody/`echo $JICOFO_AUTH_DOMAIN | sed -e "s/\./%2e/g"`
fi
# Clear the debconf variable

View File

@@ -36,6 +36,26 @@ 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
@@ -113,9 +133,19 @@ 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
@@ -140,14 +170,18 @@ 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 [ -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 "------------------------------------------------"
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
fi
# Enable turn server in config.js

View File

@@ -24,6 +24,7 @@ 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
@@ -32,6 +33,7 @@ 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

View File

@@ -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 = "5349", transport = "tcp" }
{ type = "turns", host = "jitmeet.example.com", port = "443", transport = "tcp" }
};
cross_domain_bosh = false;

View File

@@ -12,6 +12,7 @@ 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

View File

@@ -1,11 +1,7 @@
# 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;

View File

@@ -87,15 +87,6 @@ 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;
}

View File

@@ -5,9 +5,8 @@ require_relative '../node_modules/@react-native-community/cli-platform-ios/nativ
target 'jitsi-meet' do
project 'app/app.xcodeproj'
pod 'Firebase/Analytics', '~> 6.33.0'
pod 'Firebase/Crashlytics', '~> 6.33.0'
pod 'Firebase/DynamicLinks', '~> 6.33.0'
pod 'Firebase/Crashlytics', '~> 6.24.0'
pod 'Firebase/DynamicLinks', '~> 6.24.0'
end
target 'JitsiMeet' do

View File

@@ -20,49 +20,42 @@ PODS:
- React-Core (= 0.61.5-jitsi.1)
- React-jsi (= 0.61.5-jitsi.1)
- ReactCommon/turbomodule/core (= 0.61.5-jitsi.1)
- Firebase/Analytics (6.33.0):
- Firebase/Core
- Firebase/Core (6.33.0):
- Firebase/CoreOnly (6.24.0):
- FirebaseCore (= 6.7.0)
- Firebase/Crashlytics (6.24.0):
- Firebase/CoreOnly
- FirebaseAnalytics (= 6.8.3)
- Firebase/CoreOnly (6.33.0):
- FirebaseCore (= 6.10.3)
- Firebase/Crashlytics (6.33.0):
- FirebaseCrashlytics (~> 4.1.0)
- Firebase/DynamicLinks (6.24.0):
- Firebase/CoreOnly
- 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)
- FirebaseDynamicLinks (~> 4.0.8)
- FirebaseAnalyticsInterop (1.5.0)
- FirebaseCore (6.7.0):
- FirebaseCoreDiagnostics (~> 1.3)
- FirebaseCoreDiagnosticsInterop (~> 1.2)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/Logger (~> 6.5)
- FirebaseCoreDiagnostics (1.3.0):
- FirebaseCoreDiagnosticsInterop (~> 1.2)
- GoogleDataTransportCCTSupport (~> 3.1)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/Logger (~> 6.5)
- nanopb (~> 1.30905.0)
- FirebaseCoreDiagnosticsInterop (1.2.0)
- FirebaseCrashlytics (4.1.1):
- FirebaseAnalyticsInterop (~> 1.2)
- FirebaseCore (~> 6.6)
- FirebaseInstallations (~> 1.1)
- GoogleDataTransport (~> 6.1)
- GoogleDataTransportCCTSupport (~> 3.1)
- nanopb (~> 1.30905.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)
- FirebaseDynamicLinks (4.0.8):
- FirebaseAnalyticsInterop (~> 1.3)
- FirebaseCore (~> 6.2)
- FirebaseInstallations (1.2.0):
- FirebaseCore (~> 6.6)
- GoogleUtilities/Environment (~> 6.6)
- GoogleUtilities/UserDefaults (~> 6.6)
- PromisesObjC (~> 1.2)
- Folly (2018.10.22.00):
- boost-for-react-native
@@ -74,36 +67,19 @@ PODS:
- DoubleConversion
- glog
- glog (0.3.5)
- 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)
- GoogleDataTransport (6.1.0)
- GoogleDataTransportCCTSupport (3.1.0):
- GoogleDataTransport (~> 6.1)
- nanopb (~> 1.30905.0)
- GoogleSignIn (5.0.1):
- AppAuth (~> 1.2)
- GTMAppAuth (~> 1.0)
- GTMSessionFetcher/Core (~> 1.1)
- GoogleUtilities/AppDelegateSwizzler (6.7.2):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (6.7.2):
- GoogleUtilities/Environment (6.6.0):
- PromisesObjC (~> 1.2)
- GoogleUtilities/Logger (6.7.2):
- GoogleUtilities/Logger (6.6.0):
- GoogleUtilities/Environment
- GoogleUtilities/MethodSwizzler (6.7.2):
- GoogleUtilities/Logger
- GoogleUtilities/Network (6.7.2):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (6.7.2)"
- GoogleUtilities/Reachability (6.7.2):
- GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (6.7.2):
- GoogleUtilities/UserDefaults (6.6.0):
- GoogleUtilities/Logger
- GTMAppAuth (1.0.0):
- AppAuth/Core (~> 1.0)
@@ -113,13 +89,13 @@ PODS:
- GTMSessionFetcher/Core (1.2.2)
- GTMSessionFetcher/Full (1.2.2):
- GTMSessionFetcher/Core (= 1.2.2)
- nanopb (1.30906.0):
- nanopb/decode (= 1.30906.0)
- nanopb/encode (= 1.30906.0)
- nanopb/decode (1.30906.0)
- nanopb/encode (1.30906.0)
- nanopb (1.30905.0):
- nanopb/decode (= 1.30905.0)
- nanopb/encode (= 1.30905.0)
- nanopb/decode (1.30905.0)
- nanopb/encode (1.30905.0)
- ObjectiveDropboxOfficial (3.9.4)
- PromisesObjC (1.2.10)
- PromisesObjC (1.2.8)
- RCTRequired (0.61.5-jitsi.1)
- RCTTypeSafety (0.61.5-jitsi.1):
- FBLazyVector (= 0.61.5-jitsi.1)
@@ -293,7 +269,7 @@ PODS:
- React
- react-native-webrtc (1.84.0):
- React
- react-native-webview (10.9.0):
- react-native-webview (7.4.1):
- React
- React-RCTActionSheet (0.61.5-jitsi.1):
- React-Core/RCTActionSheetHeaders (= 0.61.5-jitsi.1)
@@ -374,9 +350,8 @@ DEPENDENCIES:
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector/`)
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec/`)
- Firebase/Analytics (~> 6.33.0)
- Firebase/Crashlytics (~> 6.33.0)
- Firebase/DynamicLinks (~> 6.33.0)
- Firebase/Crashlytics (~> 6.24.0)
- Firebase/DynamicLinks (~> 6.24.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)
@@ -422,14 +397,15 @@ SPEC REPOS:
- boost-for-react-native
- CocoaLumberjack
- Firebase
- FirebaseAnalytics
- FirebaseAnalyticsInterop
- FirebaseCore
- FirebaseCoreDiagnostics
- FirebaseCoreDiagnosticsInterop
- FirebaseCrashlytics
- FirebaseDynamicLinks
- FirebaseInstallations
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleDataTransportCCTSupport
- GoogleSignIn
- GoogleUtilities
- GTMAppAuth
@@ -525,24 +501,25 @@ SPEC CHECKSUMS:
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
FBLazyVector: 4a5251159a3ed05dc11cc8b74cf937869935814b
FBReactNativeSpec: 6fa602a20993212cc9877a81838578ffb0008bc9
Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5
FirebaseAnalytics: 5dd088bd2e67bb9d13dbf792d1164ceaf3052193
FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd
FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1
FirebaseCrashlytics: 5777d3462fb8c3ab9e80a2473bd7d667a2e8411c
FirebaseDynamicLinks: 6eac37d86910382eafb6315d952cc44c9e176094
FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2
Firebase: b28e55c60efd98963cd9011fe2fac5a10c2ba124
FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae
FirebaseCore: e610482f64097b0e9f056cd97bc6b33dfabcbb6a
FirebaseCoreDiagnostics: 4a773a47bd83bbd5a9b1ccf1ce7caa8b2d535e67
FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850
FirebaseCrashlytics: a87cce5746d3335995bd18b1b60d073cd05a6920
FirebaseDynamicLinks: 417dc6dbb6013233c77558290d73296f429656a6
FirebaseInstallations: 2119fb3e46b0a88bfdbf12562f855ee3252462fa
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
GoogleAppMeasurement: 966e88df9d19c15715137bb2ddaf52373f111436
GoogleDataTransport: b7f406340a291370045a270c599e53c6fa6ec20f
GoogleDataTransport: f6f8eba931df03ebd2232ff4645aa85f8f47b5ab
GoogleDataTransportCCTSupport: d70a561f7d236af529fee598835caad5e25f6d3d
GoogleSignIn: 3a51b9bb8e48b635fd7f4272cee06ca260345b86
GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3
GoogleUtilities: 39530bc0ad980530298e9c4af8549e991fd033b1
GTMAppAuth: 4deac854479704f348309e7b66189e604cf5e01e
GTMSessionFetcher: 61bb0f61a4cb560030f1222021178008a5727a23
nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc
nanopb: c43f40fadfe79e8b8db116583945847910cbabc9
ObjectiveDropboxOfficial: a5afefc83f6467c42c45f2253f583f2ad1ffc701
PromisesObjC: b14b1c6b68e306650688599de8a45e49fae81151
PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6
RCTRequired: f63dd90a89a60602acdd44c42e5d2645ca60ab79
RCTTypeSafety: 24a3c6d55684046ed550b1d0ef083a9bf71c8bd4
React: 71c5a51135f291c3b32c0b558e167b858ae50e84
@@ -557,7 +534,7 @@ SPEC CHECKSUMS:
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
react-native-webrtc: 9268ae9a2bc9730796b0968d012327e92c392adf
react-native-webview: 6ee7868ca8eba635dbf7963986d1ab7959da0391
react-native-webview: 4dbc1d2a4a6b9c5e9e723c62651917aa2b5e579e
React-RCTActionSheet: b72ddbfbe15b44ce691d128e4b582f4bb9abb540
React-RCTAnimation: cfaefba5024499d336b76ab850e6bd33b232b5e3
React-RCTBlob: c427e643bef82999deeab97489ba43298ecfbe24
@@ -576,6 +553,6 @@ SPEC CHECKSUMS:
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
Yoga: 7b4209fda2441f99d54dd6cf4c82b094409bb68f
PODFILE CHECKSUM: 224e84629bf45ae487c4ebc66faf33ec8304fb67
PODFILE CHECKSUM: 7255ec38ea51a8bc10a7a582248b4eb4bbbff80c
COCOAPODS: 1.9.3

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>20.5.0</string>
<string>20.4.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>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>20.5.0</string>
<string>20.4.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UISupportedInterfaceOrientations</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>20.5.0</string>
<string>20.4.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CLKComplicationPrincipalClass</key>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.11.0</string>
<string>2.10.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -20,7 +20,6 @@
"ru": "Russisch",
"sk": "Slowakisch",
"sl": "Slowenisch",
"sr": "Serbish",
"sv": "Schwedisch",
"tr": "Türkisch",
"vi": "Vietnamesisch",

View File

@@ -20,7 +20,6 @@
"ru": "Russian",
"sk": "",
"sl": "Slovenian",
"sr": "Serbian",
"sv": "Swedish",
"tr": "Turkish",
"vi": "Vietnamese",

View File

@@ -20,7 +20,6 @@
"ru": "Ruski",
"sk": "Slovački",
"sl": "Slovenski",
"sr": "Srpski",
"sv": "Švedski",
"tr": "Turski",
"vi": "Vijetnamski",

View File

@@ -24,7 +24,6 @@
"pl": "Польский",
"ptBR": "Португальский (Бразилия)",
"ru": "Русский",
"sr": "Сербский",
"sv": "Шведский",
"tr": "Турецкий",
"vi": "Вьетнамский",

View File

@@ -1,39 +1,34 @@
{
"en": "Енглески",
"af": "Африкански",
"az": "Азербејџански",
"bg": "Бугарски",
"cs": "Чешки",
"de": "Њемачки",
"el": "Грчки",
"eo": "Есперанто",
"es": "Шпански",
"fr": "Француски",
"hy": "Јерменски",
"it": "Италијански",
"ja": "Јапански",
"ko": "Корејски",
"nb": "Норвешки Бокал",
"oc": "Окцитански",
"pl": "Пољски",
"ptBR": "Португалски (Бразил)",
"ru": "Руски",
"sk": "Словачки",
"sl": "Словенски",
"sr": "Српски",
"sv": "Шведски",
"tr": "Турски",
"vi": "Вијетнамски",
"zhCN": "Кинески (Кина)",
"zhTW": "Кинески (Тајван)",
"nl": "Холандски",
"hu": "Мађарски",
"hr": "Хрватски",
"frCA": "Француски (Канада)",
"fi": "Фински",
"et": "Естонски",
"esUS": "Шпански (Латинска Америка)",
"enGB": "Енглески (Велика Британија)",
"da": "Дански",
"ca": "Каталонски"
"en": "",
"af": "",
"bg": "",
"ca": "",
"cs": "",
"da": "",
"de": "",
"el": "",
"enGB": "",
"eo": "",
"es": "",
"esUS": "",
"et": "",
"fi": "",
"fr": "",
"frCA": "",
"hr": "",
"hu": "",
"hy": "",
"it": "",
"ja": "",
"ko": "",
"nl": "",
"oc": "",
"pl": "",
"ptBR": "",
"ru": "",
"sv": "",
"tr": "",
"vi": "",
"zhCN": "",
"zhTW": ""
}

View File

@@ -37,7 +37,6 @@
"sc": "Sardinian",
"sk": "Slovak",
"sl": "Slovenian",
"sr": "Serbian",
"sv": "Swedish",
"th": "Thailand",
"tr": "Turkish",

View File

@@ -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}} stummschalten?",
"muteEveryoneElseTitle": "Alle außer {{whom}} stummschaten?",
"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",

View File

@@ -139,9 +139,6 @@
"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...",

View File

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

View File

@@ -1,77 +1,62 @@
{
"addPeople": {
"add": "Pozvať",
"addContacts": "Pozvať kontakty",
"copyInvite": "Skopírovať pozvánku",
"copyLink": "Skopírovať odkaz na konferenciu",
"copyStream": "Skopírovať odkaz na živé vysielanie",
"countryNotSupported": "Zatiaľ nepodporujeme túto krajinu.",
"countryReminder": "Medzinárodný hovor? Prosím skontrolujte, či telefónne číslo začína smerovým číslo krajiny.",
"defaultEmail": "Predvolený email",
"disabled": "Nemôžete pozvať ďalších ľudí.",
"disabled": "Nemôžete pozvať ďalších účastníkov.",
"failedToAdd": "Nepodarilo sa pridať účastníka.",
"footerText": "Odchádzajúce hovory sú zablokované.",
"googleEmail": "Google email",
"inviteMoreHeader": "Ste sám v tejto konferencii",
"inviteMoreMailSubject": "Pozvánka do konferencie {{appName}}",
"inviteMorePrompt": "Pozvať ľudí",
"linkCopied": "Odkaz skopírovaný do schránky",
"loading": "Hľadanie ľudí a telefónnych čísiel",
"loading": "Hľadanie účastníkov a telefónnych čísiel",
"loadingNumber": "Kontrola telefónneho čísla",
"loadingPeople": "Hľadanie ľudí na pozvanie",
"loadingPeople": "Hľadanie účastníkov na pozvanie",
"noResults": "Žiadne výsledky hľadania",
"noValidNumbers": "Prosím zadajte telefónne číslo",
"outlookEmail": "Outlook email",
"searchNumbers": "Zadajte telefónne čísla",
"searchPeople": "Hľadanie ľudí",
"searchPeopleAndNumbers": "Hľadať ľudí alebo pridať ich telefónne čísla",
"shareInvite": "Zdieľať pozvánku do konferencie",
"shareLink": "Zdieľať odkaz na pozvanie",
"shareStream": "Zdieľať odkaz na živé vysielanie",
"searchPeople": "Hľadanie účastníkov",
"searchPeopleAndNumbers": "Hľadanie účastníkov alebo pridávanie telefónny čísel",
"telephone": "Telefón: {{number}}",
"title": "Pozvať ľudí do tejto konferencie",
"yahooEmail": "Yahoo email"
"title": "Pozvať účastníkov do tejto konferencie"
},
"audioDevices": {
"bluetooth": "Bluetooth",
"headphones": "Slúchadlá",
"headphones": "Sluchátka",
"phone": "Telefón",
"speaker": "Reproduktor",
"speaker": "Rečník",
"none": "Žiadne zvukové zariadenia"
},
"audioOnly": {
"audioOnly": "Iba zvuk"
},
"calendarSync": {
"addMeetingURL": "Pridať odkaz na konferenciu",
"addMeetingURL": "Pridať odkaz na stretnutie",
"confirmAddLink": "Chcete pridal Jitsi odkaz do tejto udalosti?",
"error": {
"appConfiguration": "Integrácia s kalendárom nie je správne nastavená.",
"generic": "Stala sa chyba. Skontrolujte nastavenia kalendára a skúste obnoviť kalendár.",
"notSignedIn": "Stala sa chyba počas autentifikácie pre zobrazenie kaledárových udalosti. Skontrolujte nastavenia kalendára a skúste sa znovu prihlásiť."
"generic": "Stala sa chyba. Skontrolujte si nastavenia kalendáru a skúste aktualizovať kalendár. ",
"notSignedIn": "Stala sa chyba počas autentifikácie pre zobrazovanie kaledárových udalosti. Skontrolujte si nastavenia kalendáru a skúste sa znovu prihlásiť."
},
"join": "Zúčastniť sa",
"joinTooltip": "Zúčastniť sa konferencie",
"nextMeeting": "nasledujúca konferencia",
"noEvents": "Nie sú naplánované žiadne ďalšie udalosti.",
"ongoingMeeting": "prebiehajúca konferencia",
"joinTooltip": "Zúčastniť sa stretnutia",
"nextMeeting": "nasledujúce stretnutie",
"noEvents": "Niesú naplánované žiadne ďalšie udalosti.",
"ongoingMeeting": "prebiehajúce stretnutie",
"permissionButton": "Otvoriť nastavenia",
"permissionMessage": "Aplikácia potrebuje kalendárové oprávnenie pre zobranie termínov a stretnutí.",
"refresh": "Obnoviť kalendár",
"permissionMessage": "Aplikácia potrebuje kalendárové oprávnenie pre zobranie termínov a stretnutí ",
"refresh": "Aktualizovať kalendár",
"today": "Dnes"
},
"chat": {
"error": "Chyba: vaša správa \"{{originalText}}\" nebola poslaná. Dôvod: {{error}}",
"fieldPlaceHolder": "Zadajte sem vašu správu",
"messagebox": "Napíšte správu",
"messageTo": "Súkromná správa pre {{recipient}}",
"noMessagesMessage": "V tejto konferencii ešte nie je žiadna správa. Začnite tu vašu diskusiu!",
"messageTo": "Správa pre {{recipient}}",
"noMessagesMessage": "V tejto konferencií ešte nie je žiadna správa. Začnite tu vašu diskusiu!",
"nickname": {
"popover": "Zvoľte meno",
"title": "Zadajte vašu prezývku"
"title": "Zadajte sem vašu prezývku"
},
"privateNotice": "Súkromná správa pre {{recipient}}",
"title": "Chat",
"title": "Písanie",
"you": "Vy"
},
"chromeExtensionBanner": {
@@ -80,54 +65,54 @@
"dontShowAgain": "Upozornenie viac nezobrazovať"
},
"connectingOverlay": {
"joiningRoom": "Pripájanie do konferencie..."
"joiningRoom": "Vytvára sa spojenie do vašej konferencie"
},
"connection": {
"ATTACHED": "Priložený",
"AUTHENTICATING": "Overovanie",
"AUTHENTICATING": "Overujem",
"AUTHFAIL": "Overenie zlyhalo",
"CONNECTED": "Pripojený",
"CONNECTING": "Pripájanie",
"CONNECTING": "Pripájam",
"CONNFAIL": "Spojenie zlyhalo",
"DISCONNECTED": "Odpojený",
"DISCONNECTING": "Odpájanie",
"DISCONNECTING": "Odpájam",
"ERROR": "Chyba",
"FETCH_SESSION_ID": "Získavanie session-id...",
"GET_SESSION_ID_ERROR": "Chyba pri získavaní session-id: {{code}}",
"GOT_SESSION_ID": "Získavanie session-id... Hotovo",
"RECONNECTING": "Chyba siete. Skúšam sa znova pripojiť ...",
"LOW_BANDWIDTH": "Video pre {{displayName}} bolo vypnuté, aby sa ušetrila prenosová kapacita"
},
"connectionindicator": {
"address": "Adresa:",
"bandwidth": "Dátový tok:",
"bitrate": "Prenos. rýchlosť:",
"bridgeCount": "Počet serverov: ",
"bandwidth": "Predpokladaný dat. tok:",
"bitrate": "Prenos. rýchlosť",
"bridgeCount": "Počet serverov:",
"connectedTo": "Spojenie s:",
"e2e_rtt": "E2E RTT:",
"framerate": "Rýchlosť snímkovania:",
"less": "Zobraz menej",
"localaddress": "Lokálna adresa:",
"localaddress_plural": "Lokálne adresy:",
"localport": "Lokálny port:",
"localport_plural": "Lokálne porty:",
"maxEnabledResolution": "send max",
"more": "Zobraziť viac",
"packetloss": "Strata paketov:",
"less": "Zobraz menej",
"localaddress_0": "Lokálna adresa:",
"localaddress_1": "Lokálne adresy:",
"localaddress_2": "",
"localport_0": "Lokálny port:",
"localport_1": "Lokálne porty:",
"localport_2": "",
"more": "Zobraz viac",
"packetloss": "Strata packetov:",
"quality": {
"good": "Dobré",
"inactive": "Neaktívne",
"lost": "Stratené",
"nonoptimal": "Neoptimálne",
"nonoptimal": "Nie je optimálne",
"poor": "Slabé"
},
"remoteaddress": "Vzdialená adresa:",
"remoteaddress_plural": "Vzdialené adresy:",
"remoteport": "Vzdialený port:",
"remoteport_plural": "Vzdialené porty:",
"remoteaddress_0": "Vzdialená adresa:",
"remoteaddress_1": "Vzdialené adresy:",
"remoteaddress_2": "",
"remoteport_0": "Vzdialený port:",
"remoteport_1": "Vzdialené porty:",
"remoteport_2": "",
"resolution": "Rozlíšenie:",
"status": "Spojenie:",
"transport": "Prenos:",
"transport_plural": "Prenosy:"
"transport": "Prenos:"
},
"dateUtils": {
"earlier": "Skôr",
@@ -136,18 +121,16 @@
},
"deepLinking": {
"appNotInstalled": "Potrebujete aplikáciu {{app}}, aby ste sa mohli pripojiť do tejto konferencie na vašom telefóne.",
"description": "Nič sa nestalo? Snažili sme sa otvoriť konferenciu v {{app}}. Skúste to znovu, alebo sa pripojte na konferenciu v {{app}} cez web.",
"descriptionWithoutWeb": "Nič sa nestalo? Snažili sme sa spustiť konferenciu v desktopovej aplikácií {{app}}.",
"description": "Nič sa nestalo? Snažili sme sa otvoriť konferenciu v {{app}}. Skúste to znovu, alebo sa pripojte na konferenciu v {{app}} cez Web.",
"descriptionWithoutWeb": "Nič sa nestalo? Snažili sme sa spustiť váš rozhovor v desktopovej aplikácií {{app}}.",
"downloadApp": "Stiahnutie aplikácie",
"ifDoNotHaveApp": "Ak nemáte aplikáciu:",
"ifHaveApp": "Ak máte aplikáciu:",
"joinInApp": "Vstúpiť do konferencie cez aplikáciu",
"launchWebButton": "Otvoriť na webe",
"openApp": "Pokračovať na aplikáciu",
"title": "Konferencia sa otvára v {{app}}...",
"tryAgainButton": "Skúsiť znova s natívnou aplikáciou"
},
"defaultLink": "napr. {{url}}",
"defaultNickname": "napr. Ján Kováč",
"defaultNickname": "napr. Jane Pink",
"deviceError": {
"cameraError": "Chyba pri prístupe ku kamere",
"cameraPermission": "Aplikácia nemá oprávnenie pristupovať ku kamere",
@@ -157,84 +140,79 @@
"deviceSelection": {
"noPermission": "Oprávnenie nie je poskytnuté",
"previewUnavailable": "Náhľad nie je dostupný",
"selectADevice": "Vybrať zariadenie",
"selectADevice": "Vyberte zvukové zariadenie",
"testAudio": "Vyskúšať zvuk"
},
"dialog": {
"accessibilityLabel": {
"liveStreaming": "Živé vysielanie"
},
"add": "Pridať",
"allow": "Povoliť",
"alreadySharedVideoMsg": "Iný účastník už zdieľa video. Pri tejto konferencií môže zdieľať video iba jeden účastník.",
"alreadySharedVideoTitle": "Je možné zdieľať iba jedno video",
"alreadySharedVideoMsg": "Iný účastník už poskytuje video. Pri tejto konferencií môže poskytovať súčasne iba jeden účastník.",
"alreadySharedVideoTitle": "Naraz sa dá poskytovať iba jedno video",
"applicationWindow": "Okno aplikácie",
"Back": "Späť",
"cameraConstraintFailedError": "Vaša kamera nespĺňa potrebné požiadavky.",
"cameraNotFoundError": "Kamera nebola nájdená.",
"cameraNotSendingData": "Kamera nie je dostupná. Skontrolujte či iná aplikácia používa kameru, vyberte inú kameru v nastaveniach alebo znovu spustite aplikáciu.",
"cameraNotSendingData": "Kamera nie je dostupná. Skontrolujte či iná aplikácia používa kameru, vyberte inú kameru v nastaveniach ale znovu spustite aplikáciu.",
"cameraNotSendingDataTitle": "Prístup na kameru nie je možný.",
"cameraPermissionDeniedError": "Nebolo udelené oprávnenie používať kameru. Napriek tomu sa môže zúčastniť na konferencií, ale ostatní účastníci vás nebudu vidieť. Pre pridelenie oprávnenia môžete použiť ikonu kamery na adresnej lište.",
"cameraPermissionDeniedError": "Nebolo udelené oprávnenie používať kameru. Napriek tomu sa môže zúčastniť na konferencií, ale ostatný účastníci vás nebudu vidieť. Pre pridelenie oprávnenia môžete použiť ikonu kamery na adresnej lište.",
"cameraUnknownError": "Z neznámeho dôvodu sa kamera nedá použiť.",
"cameraUnsupportedResolutionError": "Kamera nepodporuje požadované rozlíšenie.",
"cameraUnsupportedResolutionError": "Táto kamera nepodporuje požadované rozlíšenie.",
"Cancel": "Zrušiť",
"close": "Zatvoriť",
"conferenceDisconnectMsg": "Skontrolujte prípadne vaše sieťové pripojenie. Pripájam znovu o {{seconds}} sekúnd...",
"conferenceDisconnectTitle": "Vaše spojenie bolo prerušené.",
"conferenceReloadMsg": "Snažíme sa to napraviť. Pripájam znovu o {{seconds}} sekund...",
"conferenceReloadTitle": "Spojenie sa prerušilo.",
"conferenceReloadTitle": "Žiaľ niečo sa nepodarilo.",
"confirm": "Potvrdiť",
"confirmNo": "Nie",
"confirmYes": "Áno",
"connectError": "Niečo je zle a nemôžem sa pripojiť do konferencie.",
"connectErrorWithMsg": "Niečo je zle a nemôžem sa pripojiť do konferencie. Správa: {{msg}}",
"connectError": "Oops! Niečo je zle a nemôžem sa pripojiť do konferencie.",
"connectErrorWithMsg": "Oops! Niečo je zle a nemôžem sa pripojiť do konferencie. Správa: {{msg}}",
"connecting": "Pripájam",
"contactSupport": "Spojiť sa s podporou",
"copy": "Kopírovať",
"dismiss": "Zavrieť",
"displayNameRequired": "Ahoj! Ako sa voláš?",
"done": "Hotovo",
"e2eeDescription": "Koncové šifrovanie (End-to-End Encryption, E2EE) je momentálne EXPERIMENTÁLNE. Zapnutie koncového šifrovania znemožní použitie serverových služieb ako: nahrávanie, živé vysielanie a účasť cez telefón. Do konferencie je možné vstúpiť len s prehliadačom, ktorý podporuje vložiteľné prúdy (insertable streams).",
"e2eeLabel": "E2EE kľúč",
"e2eeNoKey": "žiadny",
"e2eeToggleSet": "Nastaviť kľúč",
"e2eeSet": "Nastaviť",
"e2eeWarning": "VAROVANIE: NIektorí účastníci nemajú podporu pre koncové šifrovanie. Ak ho zapnete, nebudú Vás vidieť ani počuť.",
"enterDisplayName": "Prosím zadajte sem vaše meno",
"error": "Chyba",
"gracefulShutdown": "Služba je momentálne vypnutá pre údržbu. Skúste to neskor.",
"grantModeratorDialog": "Chcete naozaj tohoto účastníka urobiť moderatorom?",
"grantModeratorTitle": "Urobiť moderatorom",
"externalInstallationMsg": "Zlyhanie pri inštalácií rozšírenia pre zdieľanie prac. plochy",
"externalInstallationTitle": "Potrebné rozšírenie:",
"goToStore": "",
"gracefulShutdown": "Naša služba je momentálne vypnutá pre údržbu. Skúste to neskor.",
"IamHost": "Ja som hostiteľ",
"incorrectRoomLockPassword": "Nesprávne heslo",
"incorrectPassword": "Používateľské meno alebo heslo je nesprávne",
"inlineInstallationMsg": "Musí byť nainštalované rozšírenie pre zdieľanie pracovnej prochy.",
"inlineInstallExtension": "Teraz inštalovať",
"internalError": "Ups! Niečo nefunguje. Vyskytla sa nasledujúca chyba: {{error}}",
"internalErrorTitle": "Interná chyba",
"kickMessage": "Pre podrobnosti sa môžete spojiť s {{participantDisplayName}}.",
"kickParticipantButton": "Odstrániť",
"kickParticipantDialog": "Skutočne chcete odstrániť tohto účastnika?",
"kickParticipantTitle": "Odstrániť účastníka?",
"kickTitle": "{{participantDisplayName}} vás odstránil z konferencie.",
"kickTitle": "Ouch! {{participantDisplayName}} vás odstránil zo stretnutia.",
"liveStreaming": "Živé vysielanie",
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Nie je možné keď je aktívne nahrávanie",
"liveStreamingDisabledForGuestTooltip": "Hostia nemôžu začať živé vysielanie.",
"liveStreamingDisabledTooltip": "Spustenie živého vysielania je zakázané.",
"liveStreamingDisabledTooltip": "Štartovanie živého vysielania je vypnuté.",
"lockMessage": "Zlyhanie pri pokuse o zabezpečenie konferencie.",
"lockRoom": "Pridať $t(lockRoomPassword)",
"lockRoom": "Pridať stretnutie $t(lockRoomPasswordUppercase)",
"lockTitle": "Zabezpečenie zlyhalo",
"logoutQuestion": "Ste si istý, že sa chcete odhlásiť a skončiť konferenciu?",
"logoutTitle": "Odhlásiť",
"maxUsersLimitReached": "Bol dosiahnutý maximálny počet účastníkov. Konferencia je plná. Spojte sa prosím s organizátorom konferencie, alebo to skúste neskôr.",
"maxUsersLimitReached": "Bol dosiahnutý maximálny počet účastníkov. Konferencia je plná. Spojte sa prosím s organizátorom stretnutia, alebo to skúste neskôr.",
"maxUsersLimitReachedTitle": "Dosiahnutý maximálny počet účastníkov",
"micConstraintFailedError": "Váš mikrofón nespĺňa potrebné požiadavky.",
"micNotFoundError": "Mikrofón nebol nájdený.",
"micNotSendingData": "Choďte do nastavení vašeho počítača, aby ste odblokovali stlmenie vášho mikrofónu a upravte jeho úroveň.",
"micNotSendingDataTitle": "Mikrofón je stlmený vašimi systémovými nastaveniami.",
"micPermissionDeniedError": "Nebolo udelené oprávnenie používať mikrofón. Napriek tomu sa môže zúčastniť na konferencií, ale ostatní účastníci vás nebudú počuť. Pre pridelenie oprávnenia môžete použiť ikonu kamery na adresnej lište.",
"micPermissionDeniedError": "Nebolo udelené oprávnenie používať mikrofón. Napriek tomu sa môže zúčastniť na konferencií, ale ostatný účastníci vás nebudú počuť. Pre pridelenie oprávnenia môžete použiť ikonu kamery na adresnej lište.",
"micUnknownError": "Mikrofón sa nedá použiť z neznámeho dôvodu.",
"muteEveryoneElseDialog": "Keď všetkým vypnete mikrofóny, nedokážete ich späť zapnúť. Účastníci si ale môžu zapnúť mikrofóny sami.",
"muteEveryoneElseDialog": "Keď všetkým vypnete mikrofón, nedokážete spať zapnuť mikrofóny. Účastníci si ale môžu zapnúť mikrofóny sami.",
"muteEveryoneElseTitle": "Vypnúť mikrofón všetkým okrem {{whom}}?",
"muteEveryoneDialog": "Chcete naozaj všetkým vypnúť mikrofón. Keď všetkým vypnete mikrofóny, nedokážete ich späť zapnúť. Účastníci si ale môžu zapnúť mikrofóny sami.",
"muteEveryoneDialog": "Chcete naozaj všetkým vypnúť mikrofón. Keď všetkým vypnete mikrofón, nedokážete spať zapnúť mikrofóny. Účastníci si ale môžu zapnúť mikrofóny sami.",
"muteEveryoneTitle": "Všetkým vypnúť mikrofón?",
"muteEveryoneSelf": "seba samého",
"muteEveryoneStartMuted": "Všetci odteraz začínajú s vypnutým mikrofónom",
@@ -244,34 +222,33 @@
"muteParticipantTitle": "Vypnúť účastníkovi mikrofón?",
"Ok": "Ok",
"passwordLabel": "$t(lockRoomPasswordUppercase)",
"passwordNotSupported": "$t(lockRoomPasswordUppercase) nie je podporované.",
"passwordNotSupported": "Nastavovanie $t(lockRoomPassword) nie je podporované.",
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) nie je podporované",
"passwordRequired": "Prihlásenie",
"popupError": "Váš prehliadač blokuje vyskakovacie okná tejto stránky. Prosím aktivujte vyskakovacie okná v bezpečnostných nastaveniach vašeho prehliadača a skúste znovu.",
"passwordRequired": "$t(lockRoomPasswordUppercase) je potrebné",
"popupError": "Váš prehliadať blokuje vyskakovacie okná tejto stránky. Prosím aktivujte vyskakovacie okná v bezpečnostných nastaveniach vašeho prehliadača a skúste znovu.",
"popupErrorTitle": "Vyskakovacie okná sú zablokované",
"readMore": "viac",
"recording": "Nahrávanie",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Nie je možné keď je aktívny živý prenos",
"recordingDisabledForGuestTooltip": "Hostia nemôžu začať nahrávanie.",
"recordingDisabledTooltip": "Spustenie nahrávania je zakázané.",
"rejoinNow": "Pripojiť hneď",
"recordingDisabledTooltip": "Štartovanie nahrávania je vypnuté.",
"rejoinNow": "Teraz sa znovu pridať.",
"remoteControlAllowedMessage": "{{user}} prijal požiadavku o vzdialené ovládanie.",
"remoteControlDeniedMessage": "{{user}} odmietol požiadavku o vzdialené ovládanie.",
"remoteControlDeniedMessage": "{{user}} odmietol prijal požiadavku o vzdialené ovládanie.",
"remoteControlErrorMessage": "Stala sa chyba počas žiadania o vzdialené ovládanie od {{user}}",
"remoteControlRequestMessage": "Povolíte {{user}} ovládať vášu pracovnú plochu?",
"remoteControlShareScreenWarning": "Pozor, keď povolíte požiadavku budete zdielať vašu obrazovku!",
"remoteControlStopMessage": "Vzdialené ovládanie bolo ukončené.",
"remoteControlTitle": "Vzdialené ovládanie",
"Remove": "Odstrániť",
"removePassword": "$t(lockRoomPasswordUppercase) odstránené",
"removeSharedVideoMsg": "Naozaj chcete odstrániť zdieľané video?",
"removeSharedVideoTitle": "Odstrániť zdieľané video",
"removePassword": "$t(lockRoomPassword) odstránené",
"removeSharedVideoMsg": "Ste si istý že chcete odstrániť zdielané video?",
"removeSharedVideoTitle": "Odstrániť zdielané video",
"reservationError": "Systémová chyba rezervácie",
"reservationErrorMsg": "Chyba: {{code}}, správa: {{msg}}",
"retry": "Skúsiť znovu",
"screenSharingAudio": "Zdieľať zvuk",
"screenSharingFailed": "Nie je možné spustiť zdieľanie obrazovky!",
"screenSharingFailedTitle": "Zdieľanie obrazovky zlyhalo!",
"screenSharingFailedToInstall": "Ups! Nepodarilo sa nainštalovať rozšírenie pre zdieľanie obrazovky.",
"screenSharingFailedToInstallTitle": "Chyba v inštalácii rozšírenie pre zdieľanie obrazovky",
"screenSharingFirefoxPermissionDeniedError": "Niečo sa nepodarilo pri pokuse o zdielanie obrazovky. Skontrolujte prosím či ste dali oprávnenie v prehliadači.",
"screenSharingFirefoxPermissionDeniedTitle": "Nepodarilo sa zdielať obrazovku",
"screenSharingPermissionDeniedError": "Ups! Niečo sa nepodarilo pri žiadaní o oprávnenie zdielať obrazovku. Prosím aktualizovať a skúsiť znovu.",
"sendPrivateMessage": "Dostali ste súkromnú správu. Chceli ste na ňu odpovedať súkromne, alebo chcete poslať správu skupine?",
"sendPrivateMessageCancel": "Poslať skupine",
@@ -281,26 +258,26 @@
"sessTerminated": "Volanie ukončené",
"Share": "Zdieľať",
"shareVideoLinkError": "Prosím, zadajte správny Youtube odkaz.",
"shareVideoTitle": "Zdieľať video",
"shareVideoTitle": "Zdielať video",
"shareYourScreen": "Zdielať obrazovku",
"shareYourScreenDisabled": "Zdieľanie obrazovky vypnuté.",
"shareYourScreenDisabledForGuest": "Hostia nemôžu zdielať obrazovku.",
"startLiveStreaming": "Spustiť živý prenos",
"startLiveStreaming": "Spustiť priamy prenos",
"startRecording": "Začať záznam",
"startRemoteControlErrorMessage": "Chyba pri pokuse o začatie vzdialeného ovládania!",
"stopLiveStreaming": "Zastaviť živý prenos",
"startRemoteControlErrorMessage": "Chyba pri pokuse o začatie vzdialeného riadenia!",
"stopLiveStreaming": "Prerušiť priamy prenos",
"stopRecording": "Zastaviť záznam",
"stopRecordingWarning": "Chcete zastaviť záznam?",
"stopStreamingWarning": "Chcete zastaviť priamy prenos?",
"streamKey": "Kľúč živého vysielania",
"stopStreamingWarning": "Chcete prerušiť priamy prenos",
"streamKey": "Klúč živého vysielania",
"Submit": "OK",
"thankYou": "Ďakujeme za používanie {{appName}}!",
"thankYou": "Ďakujeme vám za používanie {{appName}}!",
"token": "token",
"tokenAuthFailed": "Prepáčte, nie ste oprávnený zúčastniť tejto sa konferencie.",
"tokenAuthFailedTitle": "Overenie zlyhalo",
"transcribing": "",
"unlockRoom": "Odstrániť $t(lockRoomPassword)",
"userPassword": "heslo",
"unlockRoom": "Odstrániť stretnutie $t(lockRoomPassword)",
"userPassword": "užívateľské heslo",
"WaitForHostMsg": "Konferencia <b>{{room}}</b> sa ešte nezačala. Autorizujte sa prosím ak ste hostiteľ. V opačnom prípade čakajte na hostiteľa.",
"WaitForHostMsgWOk": "Konferencia <b>{{room}}</b> sa ešte nezačala. Ak ste hostiteľ autorizujte sa stlačením Ok. V opačnom prípade čakajte na hostiteľa.",
"WaitingForHost": "Čakám na hostiteľa ...",
@@ -311,10 +288,7 @@
"statusMessage": "je teraz {{status}}"
},
"documentSharing": {
"title": "Zdieľaný dokument"
},
"e2ee": {
"labelToolTip": "Zvuková a obrazová komunikácia je koncovo šifrovaná"
"title": "Zdielaný dokument"
},
"feedback": {
"average": "Priemerný",
@@ -334,8 +308,8 @@
},
"info": {
"accessibilityLabel": "Zobraziť informácie",
"addPassword": "Nastaviť $t(lockRoomPassword)",
"cancelPassword": "Zrušiť $t(lockRoomPassword)",
"addPassword": "$t(lockRoomPassword) pridať",
"cancelPassword": "$t(lockRoomPassword) zmazať",
"conferenceURL": "Odkaz:",
"country": "Krajina",
"dialANumber": "Aby ste sa zúčastnili stretnutia, zavolajte jedno z týchto čísel a zadajte pin.",
@@ -348,9 +322,9 @@
"inviteLiveStream": "Kliknite túto linku {{url}}, pre zobrazenie živého vysielania z tohto stretnutia.",
"invitePhone": "Keď sa chcete pripojiť cez telefón, klikni na: {{number}},,{{conferenceID}}#\n",
"invitePhoneAlternatives": "Hľadáte iné pripojovacie číslo? Pripojovacie čísla pre konferenciu: {{{url}}\n\n\n\nTaktiež pokiaľ sa telefonicky pripájate cez konferenčný celomiestnostný telefón pripojte sa bez prenosu zvuku {{silentUrl}}",
"inviteURLFirstPartGeneral": "Ste pozvaný do konferencie.",
"inviteURLFirstPartPersonal": "{{name}} vás pozýva do konferencie.\n",
"inviteURLSecondPart": "\nVstúpiť do konferencie:\n{{url}}\n",
"inviteURLFirstPartGeneral": "Ste pozývaný pripojiť sa na stretnutie.",
"inviteURLFirstPartPersonal": "{{name}} vás pozýva na stretnutie.",
"inviteURLSecondPart": "\n Zúčastniť sa stretnutia:\n{{url}}\n",
"liveStreamURL": "Živý prenos:",
"moreNumbers": "Ďalšie telefónne čísla",
"noNumbers": "Žiadne pripojovacie telefónne čísla.",
@@ -394,8 +368,6 @@
"videoQuality": "Nastavenie kvality volania"
},
"liveStreaming": {
"limitNotificationDescriptionWeb": "Živé vysielanie je obmedzené na {{limit}} minút. Pre neobmedzené vysielanie skúste <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"limitNotificationDescriptionNative": "Živé vysielanie je obmedzené na {{limit}} minút. Pre neobmedzené vysielanie skúste {{app}}.",
"busy": "Chystajú sa zdroje pre vysielanie. Skúste znova za pár minút.",
"busyTitle": "Všetky vysielacie inštancie sú obsadené",
"changeSignIn": "Prepnúť konto",
@@ -403,19 +375,19 @@
"chooseCTA": "Vyberte vysielaciu možnosť. Ste prihlásený ako {{email}}",
"enterStreamKey": "Zadajte meno/heslo pre YouTube vysielanie.",
"error": "Živé vysielanie zlyhalo. Prosím skúste to znovu.",
"errorAPI": "Došlo k chybe pri prístupe k vašemu YouTube vysielaniu. Prosím skúste sa znovu prihlásiť.",
"errorAPI": "Došlo chybe pri prístupe k vašemu YouTube vysielaniu. Prosím skúste sa znovu prihlásiť.",
"errorLiveStreamNotEnabled": "Živé vysielanie pre {{email}} nie je aktivované. Aktivujte živé vysielanie, alebo sa prihláste pomocou konta s aktivovaným živým vysielaním.",
"expandedOff": "Živé vysielanie bolo zastavené",
"expandedOn": "Stretnutie je momentálne vysielané na YouTube.",
"expandedPending": "Spúšťa živé vysielanie...",
"failedToStart": "Nepodarilo sa spustiť živé vysielanie",
"failedToStart": "Nepodarilo sa naštartovať živé vysielanie",
"getStreamKeyManually": "Nepodarilo sa získať žiadne živé vysielania. Skúste získať kľúč pre živé vysielanie z YouTube.",
"invalidStreamKey": "Kľúč pre živé vysielanie je nesprávny.",
"off": "Živé vysielanie ukončené",
"offBy": "{{name}} ukončil živé vysielanie",
"on": "Živé vysielanie",
"onBy": "{{name}} začal živé vysielanie",
"pending": "Spúšťa sa živé vysielanie...",
"pending": "Štartuje sa živé vysielanie...",
"serviceName": "Služba pre živé vysielanie",
"signedInAs": "Ste prihlásený ako:",
"signIn": "Prihlásiť sa pomocou Google",
@@ -423,9 +395,7 @@
"signOut": "Odhlásiť",
"start": "Začať živé vysielanie",
"streamIdHelp": "Čo je to?",
"unavailableTitle": "Živé vysielanie nie je k dispozícií",
"youtubeTerms": "Podmienky poskytovania služby YouTube",
"googlePrivacyPolicy": "Pravidlá ochrany súkromia Google"
"unavailableTitle": "Živé vysielanie nie je k dispozícií"
},
"localRecording": {
"clientState": {
@@ -433,19 +403,19 @@
"on": "Zapnutý",
"unknown": "Neznámy"
},
"dialogTitle": "Ovládacie prvky lokálneho nahrávania",
"dialogTitle": "Lokálne ovládacie prvky nahrávania",
"duration": "Dĺžka",
"durationNA": "neznáma",
"encoding": "Kódovanie",
"label": "",
"labelToolTip": "Lokálne nahrávanie je aktivovaný",
"labelToolTip": "Lokálny nahrávanie je aktivovaný",
"localRecording": "Lokálne nahrávanie",
"me": "Ja",
"messages": {
"engaged": "Lokálne nahrávanie je spustené",
"finished": "Nahrávanie sedenia {{token}} je ukončené. Prosím pošlite nahratý súbor moderátorovi.",
"finishedModerator": "Nahrávanie sedenia {{token}} je ukončené. Bola uložená nahrávka lokálnej stopy. Poproste ostatných účastníkov, aby vám poslali ich nahrávky.",
"notModerator": "Nie ste moderátor. Nemôže začať, alebo skončiť lokálne nahrávanie."
"notModerator": "Nieste moderátor. Nemôže začať, alebo skončiť lokálne nahrávanie."
},
"moderator": "Moderátor",
"no": "Nie",
@@ -468,17 +438,17 @@
"focusFail": "{{component}} je nedostupný - skúste znova za {{ms}} sek",
"grantedTo": "Práva moderátora boli udelené {{to}}!",
"invitedOneMember": "{{displayName}} bol pozvaný",
"invitedThreePlusMembers": "{{name}} a {{count}} ďalší boli pozvaní",
"invitedTwoMembers": "{{first}} a {{second}} boli pozvaní",
"kickParticipant": "{{kicked}} bol odstránený účastníkom {{kicker}}",
"invitedThreePlusMembers": "{{name}} a {{count}} ďalší boli pozvaný",
"invitedTwoMembers": "{{first}} a {{second}} boli pozvaný",
"kickParticipant": "Pre ďalšie podrobnosti sa môže obrátiť na {{participantDisplayName}}",
"me": "Ja",
"moderator": "Boli vám udelené práva moderátora!",
"muted": "Začali ste rozhovor s vypnutým mikrofónom.",
"mutedTitle": "Boli ste stíšený!",
"mutedRemotelyTitle": "{{participantDisplayName}} vám vypol mikrofón",
"mutedRemotelyTitle": "{{participantDisplayName}} vás stíšil",
"mutedRemotelyDescription": "Kedykoľvek môžete stíšenie zrušiť, keď ste prichystaný rozprávať. Keď skončite môžete sa znova stíšiť, aby ste znížili hluk na stretnutí.",
"passwordRemovedRemotely": "Iný účastník odstránil $t(lockRoomPassword)",
"passwordSetRemotely": "Iný účastník nastavil $t(lockRoomPassword)",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) bolo odstránené iným účastníkom",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) bolo nastavené iným účastníkom",
"raisedHand": "{{name}} chce hovoriť",
"somebody": "Niekto",
"startSilentTitle": "Pripojili ste sa bez zvukového výstupu!",
@@ -488,49 +458,11 @@
"unmute": "Zapnúť mikrofón",
"newDeviceCameraTitle": "Bola zistená nová kamera",
"newDeviceAudioTitle": "Bolo zistené nové audio zariadenie",
"newDeviceAction": "Použiť",
"OldElectronAPPTitle": "Bezpečnostná hrozba!",
"oldElectronClientDescription1": "Používate starú verziu klienta Jitsi Meet, ktorá má známe zraniteľnosti. Aktualizujte na ",
"oldElectronClientDescription2": "najnovšiu verziu",
"oldElectronClientDescription3": " teraz!"
"newDeviceAction": "Použiť"
},
"passwordSetRemotely": "nastavené iným účastníkom",
"passwordDigitsOnly": "až {{number}} číslic",
"poweredby": "založené na",
"prejoin": {
"audioAndVideoError": "Chyba zvuku a videa:",
"audioOnlyError": "Chyba zvuku:",
"audioTrackError": "Nemôžem vytvoriť zvukovú stopu.",
"callMe": "Zavolať mi",
"callMeAtNumber": "Zavolajte mi na toto číslo:",
"configuringDevices": "Konfigurácia zariedení...",
"connectedWithAudioQ": "Ste pripojení so zvukom?",
"copyAndShare": "Kopírovať a zdieľať odkaz",
"dialInMeeting": "Volanie dnu do konferencie",
"dialInPin": "Volajte dnu do konferencie a zadajte PIN kód:",
"dialing": "Vytáčanie",
"doNotShow": "Viac nezobrazovať",
"errorDialOut": "Nemôžem volať von",
"errorDialOutDisconnected": "Nemôžem volať von. Odpojené",
"errorDialOutFailed": "Nemôžem volať von. Volanie zlyhalo",
"errorDialOutStatus": "Chyba pri získavaní stavu volania",
"errorStatusCode": "Chyba volania von, kód: {{status}}",
"errorValidation": "Overenie čísla zlyhalo",
"iWantToDialIn": "Chcem volať dnu",
"joinAudioByPhone": "Vstúpiť so zvukom cez telefón",
"joinMeeting": "Vstúpiť do konferencie",
"joinWithoutAudio": "Vstúpiť bez zvuku",
"initiated": "Hovor začatý",
"linkCopied": "Odkaz skopírovaný do schránky",
"lookGood": "Váš mikrofón funguje správne",
"or": "alebo",
"calling": "Volanie",
"startWithPhone": "Začať so zvukom cez telefón",
"screenSharingError": "Chyba pri zdieľaní obrazovky:",
"videoOnlyError": "Chyba videa:",
"videoTrackError": "Nemôžem vytvoriť video stopu.",
"viewAllNumbers": "zobraziť všetky čísla"
},
"presenceStatus": {
"busy": "Obsadený",
"calling": "Je volaný",
@@ -553,8 +485,6 @@
},
"raisedHand": "Chcel by som hovoriť",
"recording": {
"limitNotificationDescriptionWeb": "Nahrávanie je obmedzené na {{limit}} minút. Pre neobmedzené nahrávanie skúste <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"limitNotificationDescriptionNative": "Nahrávanie je obmedzené na {{limit}} minút. Pre neobmedzené nahrávanie skúste <3>{{app}}</3>.",
"authDropboxText": "Nahrať na Dropbox",
"availableSpace": "Dostupná kapacita {{spaceLeft}} MB (ca. {{duration}} minút nahrávania)",
"beta": "BETA",
@@ -584,12 +514,6 @@
"sectionList": {
"pullToRefresh": "Potiahnuť pre aktualizáciu"
},
"security": {
"about": "Môžete nastaviť $t(lockRoomPassword) pre konferenciu. Účastníci budú musieť zadať $t(lockRoomPassword), aby mohli vstúpiť.",
"aboutReadOnly": "Moderátor može nastaviť $t(lockRoomPassword) pre konferenciu. Účastníci budú musieť zadať $t(lockRoomPassword), aby mohli vstúpiť.",
"insecureRoomNameWarning": "Názov konferencie nie je bezpečný. Môžu do nej vstúpiť neželaní účastníci. Zvážte zabezpečenie konferencie tlačidlom.",
"securityOptions": "Nastavenie zabezpečenia"
},
"settings": {
"calendar": {
"about": "Používa sa kalendárová integrácia {{appName}} pre zabezpečený prístup ku vašemu kalendáru.",
@@ -602,7 +526,6 @@
"followMe": "Všetci sledujú mňa",
"language": "Jazyk",
"loggedIn": "Prihlásený ako {{name}}",
"microphones": "Mikrofóny",
"moderator": "Moderátor",
"more": "Viac",
"name": "Meno",
@@ -610,7 +533,6 @@
"selectAudioOutput": "Zvukový výstup",
"selectCamera": "Kamera",
"selectMic": "Mikrofón",
"speakers": "Reproduktory",
"startAudioMuted": "Pri pripojení všetkým stlmiť zvuk",
"startVideoMuted": "Pri pripojení všetkým vypnúť video",
"title": "Nastavenia"
@@ -618,15 +540,12 @@
"settingsView": {
"advanced": "Rozšírené",
"alertOk": "OK",
"alertCancel": "Zrušiť",
"alertTitle": "Upozornenie",
"alertURLText": "Zadaná serverová URL je neplatná",
"alertURLText": "Zadaná serverový URL je neplatná",
"buildInfoSection": "informácie o kompilácií",
"conferenceSection": "Konferencia",
"disableCallIntegration": "Deaktivovať integráciu s natívnymi volaniami",
"disableP2P": "Deaktivovať mód s koncovými zariadeniami",
"disableCrashReporting": "Vypnúť oznamovanie pádov",
"disableCrashReportingWarning": "Naozak chcete vypnúť oznamovanie pádov? Nastavenie bude aktívne po reštartovaní aplikácie.",
"displayName": "Ukázať",
"email": "E-mail",
"header": "Nastavenia",
@@ -643,10 +562,10 @@
},
"speaker": "Rečník",
"speakerStats": {
"hours": "{{count}}h",
"minutes": "{{count}}m",
"hours": "",
"minutes": "",
"name": "Meno",
"seconds": "{{count}}s",
"seconds": "",
"speakerStats": "Štatistiky rečníka",
"speakerTime": "Čas rečníka"
},
@@ -657,7 +576,7 @@
"suspendedoverlay": {
"rejoinKeyTitle": "Znovu pripojiť",
"text": "Stlačte tlačidlo <i>Znovu pripojiť</i> na opätovné spojenie.",
"title": "Konferencia sa prerušila lebo váš počítač bol uspaný."
"title": "Konferencia sa prerušila lebo váš počítač bol uspaní."
},
"toolbar": {
"accessibilityLabel": {
@@ -668,17 +587,14 @@
"chat": "Zapnúť/vypnúť textovú diskusiu",
"document": "Zatvoriť zdielaný dokument",
"download": "Stiahnuť našu aplikáciu",
"e2ee": "Koncové šifrovanie",
"feedback": "Zanechať spätnú väzbu",
"fullScreen": "Zapnúť/vypnúť zobrazenie na celú obrazovku",
"grantModerator": "Urobiť moderátorom",
"hangup": "Ukončiť volanie",
"help": "Pomoc",
"invite": "Pozvať účastníka",
"kick": "Odstrániť účastníka",
"lobbyButton": "Zapnúť/vypnúť čakáreň",
"localRecording": "Zapnúť/vypnúť ovládanie lokálneho nahrávania",
"lockRoom": "Zapnúť/vypnúť heslo",
"lockRoom": "Zapnúť/vypnúť heslo pre stretnutie",
"moreActions": "Menu „Ďalšie akcie“ zapnúť/vypnúť",
"moreActionsMenu": "Menu „Ďalšie akcie“",
"moreOptions": "Zobraz viac možností",
@@ -690,7 +606,6 @@
"raiseHand": "„Ohlásiť sa“ zapnúť/vypnúť",
"recording": "Nahrávanie zapnúť/vypnúť",
"remoteMute": "Účastníka stlmiť",
"security": "Nastavenie zabezpečenia",
"Settings": "Nastavenia zapnúť/vypnúť",
"sharedvideo": "Zdieľanie YouTube videa zapnúť/vypnúť",
"shareRoom": "Pozvať osobu",
@@ -700,7 +615,6 @@
"speakerStats": "Štatistiky rečníka zobraziť/skryť",
"tileView": "Prepnúť dlaždicové zobrazenie",
"toggleCamera": "Zmeniť kameru",
"toggleFilmstrip": "Zapnúť/vypnúť video náhľady",
"videomute": "„Video odpojiť“ zapnúť/vypnúť",
"videoblur": "Rozmazanie pozadia zapnúť/vypnúť"
},
@@ -708,24 +622,21 @@
"audioOnlyOff": "Mód „Iba zvuk“ deaktivovať",
"audioOnlyOn": "Mód „Iba zvuk“ aktivovať",
"audioRoute": "Vybrať zvukové zariadenie",
"authenticate": "Autentifikácia",
"authenticate": "Overiť",
"callQuality": "Spravovať kvalitu videa",
"chat": "Otvoriť / Zatvoriť chat",
"closeChat": "Chat zatvoriť",
"documentClose": "Zatvoriť zdieľaný dokument",
"documentOpen": "Otvoriť zdieľaný dokument",
"documentClose": "Zdielaný dokument zatvoriť",
"documentOpen": "Zdielaný dokument otvoriť",
"download": "Stiahnuť našu aplikáciu",
"e2ee": "Koncové šifrovanie",
"enterFullScreen": "Zobraziť na celú obrazovku",
"enterTileView": "Dlaždicové zobrazenie",
"enterTileView": "Kachličkové zobrazenie",
"exitFullScreen": "Opustiť celú obrazovku",
"exitTileView": "Zrušiť dlaždicové zobrazenie",
"exitTileView": "Kachličkové zobrazenie vypnúť",
"feedback": "Nechať spätnú väzbu",
"hangup": "Odísť",
"help": "Pomoc",
"invite": "Pozvať ľudí",
"lobbyButtonDisable": "Vypnúť čakáreň",
"lobbyButtonEnable": "Zapnúť čakáreň",
"invite": "Pozvať účastníkov",
"login": "Prihlásiť",
"logout": "Odhlásiť",
"lowerYourHand": "Dať dole ruku",
@@ -734,10 +645,10 @@
"mute": "Vypnúť / Zapnúť mikrofón",
"muteEveryone": "Všetkých stlmiť",
"noAudioSignalTitle": "Neprichádza žiaden vstup z vašeho mikrofónu!",
"noAudioSignalDesc": "Pokiaľ ste zámerne nestlmili váš mikrofón v systémových nastaveniach alebo hardvéri, pouvažujte nad prepnutím zariadenia.",
"noAudioSignalDescSuggestion": "Pokiaľ ste zámerne nestlmili váš mikrofón v systémových nastaveniach alebo hardvéri, pouvažujte nad prepnutím na odporúčané zariadenie.",
"noAudioSignalDesc": "Pokiaľ ste zámerne nestlmili váš mikrofón v systémových nastavenia alebo hardvery, pouvažujte nad prepnutím zariadenia.",
"noAudioSignalDescSuggestion": "Pokiaľ ste zámerne nestlmili váš mikrofón v systémových nastavenia alebo hardvery, pouvažujte nad prepnutím na odporúčané zariadenie.",
"noAudioSignalDialInDesc": "Môže zavolať pomocou:",
"noAudioSignalDialInLinkDesc": "Pripojovacie telefónne čísla",
"noAudioSignalDialInLinkDesc" : "Pripojovacie telefónne čísla",
"noisyAudioInputTitle": "Váš mikrofón vyzerá byť zašumený!",
"noisyAudioInputDesc": "Vyzerá, že váš mikrofón je zašumený, skúste ho vypnuť, alebo zmeňte zariadenie.",
"openChat": "Otvoriť chat",
@@ -746,7 +657,6 @@
"profile": "Úprava profilu",
"raiseHand": "Prihlásiť / Odhlásiť sa o slovo",
"raiseYourHand": "Prihlásiť sa o slovo",
"security": "Nastavenie zabezpečenia",
"Settings": "Nastavenia",
"sharedvideo": "Zdielať YouTube video",
"shareRoom": "Pozvať niekoho",
@@ -774,7 +684,7 @@
"pending": "Pripravuje sa prepisovanie stretnutia...",
"start": "Začni zobrazovať titulky",
"stop": "Skonči zobrazovať titulky",
"tr": "TR"
"tr": ""
},
"userMedia": {
"androidGrantPermissions": "Vyberte <b><i>Povoliť</i></b> keď sa prehliadač bude pýtať na povolenie.",
@@ -789,14 +699,14 @@
"safariGrantPermissions": "Vyberte <b><i>OK</i></b> keď sa prehliadač bude pýtať na povolenie."
},
"videoSIPGW": {
"busy": "Všetky zdroje sú obsadené, skúste znovu o pár minút.",
"busyTitle": "Služba je obsadená",
"errorAlreadyInvited": "{{displayName}} už bol pozvaný",
"errorInvite": "Konferencia sa ešte nezačala, skúste neskôr.",
"errorInviteFailed": "Skúste znovu neskôr.",
"errorInviteFailedTitle": "Pozývanie {{displayName}} zlyhalo",
"errorInviteTitle": "Chyba pozývania",
"pending": "{{displayName}} bol pozvaný"
"busy": "",
"busyTitle": "",
"errorAlreadyInvited": "",
"errorInvite": "",
"errorInviteFailed": "",
"errorInviteFailedTitle": "",
"errorInviteTitle": "",
"pending": ""
},
"videoStatus": {
"audioOnly": "AUD",
@@ -818,10 +728,8 @@
},
"videothumbnail": {
"domute": "Vypnúť mikrofón",
"domuteOthers": "Vypnúť mikrofóny ostatným",
"flip": "Prevrátiť",
"grantModerator": "Urobiť moderátorom",
"kick": "Odstrániť",
"kick": "Vyhodiť",
"moderator": "Moderátor",
"mute": "Účastník s vypnutým mikrofónom",
"muted": "Vypnutý mikrofón",
@@ -843,62 +751,20 @@
"connectCalendarButton": "Pripojte váš kalendár",
"connectCalendarText": "",
"enterRoomTitle": "Začať nové stretnutie",
"getHelp": "Získať pomoc",
"roomNameAllowedChars": "Meno stretnutia by nemalo obsahovať žiaden z týchto znakov: ?, &, :, ', \", %, #.",
"go": "Začať",
"goSmall": "Začať",
"info": "Info",
"join": "Pripojiť",
"moderatedMessage": "Alebo si <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">rezervujte vopred URL</a> pre konferenciu, kde budete jediný moderátor.",
"info": "Info",
"privacy": "Súkromie",
"recentList": "Posledné",
"recentListDelete": "Vymazať",
"recentListEmpty": "Váš zoznam posledných hovorov je prázdny. Spojte sa s kolegami z Vášho tímu a potom tu nájdete všetky vaše stretnutia.",
"reducedUIText": "Vitajte v {{app}}!",
"roomNameAllowedChars": "Názov miestnosti by nemal obsahovať žiaden z týchto znakov: ?, &, :, ', \", %, #.",
"reducedUIText": "Vítajte v {{app}}!",
"roomname": "Zadajte názov miestnosti",
"roomnameHint": "Zadajte názov alebo URL odkaz miestnosti ku ktorej sa chcete pripojiť. Názov si môžete vymyslieť - dajte ho vedieť ostatným účastníkom konferencie, ktorí ho sem zadajú.",
"roomnameHint": "Zadajte názov alebo URL odkaz miestnosti ku ktorej sa chcete pripojiť. Pokial ste miestnosť vytvorili, uistite sa, že ostatný účastníci schôdzky zadajú rovnaké meno ako vy.",
"sendFeedback": "Odoslať spätnú väzbu",
"terms": "Podmienky používania",
"title": "Zabezpečené, plnohodnotné a úplne bezplatné videokonferencie"
},
"lonelyMeetingExperience": {
"button": "Pozvať ďalších",
"youAreAlone": "Ste sám v tejto konferencii"
},
"helpView": {
"header": "Centrum pomoci"
},
"lobby": {
"knockingParticipantList": "Zoznam čakajúcich účastníkov",
"allow": "Povoliť",
"backToKnockModeButton": "Žiadne heslo, požiadať o vstup",
"dialogTitle": "Čakáreň",
"disableDialogContent": "Čakáreň je zapnutá. Táto funkcia zabezpečuje, že do konferencie nemôžu vstúpiť neželaní účastníci. Chcete ju vypnúť?",
"disableDialogSubmit": "Vypnúť",
"emailField": "Zadajte vašu e-mailovú adresu",
"enableDialogPasswordField": "Nastaviť heslo (voliteľné)",
"enableDialogSubmit": "Zapnúť",
"enableDialogText": "Čakáreň umožňuje zabezpečiť konferenciu tým, že účastníci môžu do konferencie vstúpiť len po schválení moderátorom.",
"enterPasswordButton": "Zadať heslo do konferencie",
"enterPasswordTitle": "Zadajte heslo pre vstup do konferencie",
"invalidPassword": "Nesprávne heslo",
"joiningMessage": "Vstúpite do konferencie, keď niekto schváli vašu žiadosť",
"joinWithPasswordMessage": "Vstupujem s heslom...",
"joinRejectedMessage": "Vaša žiadosť bola zamietnutá moderátorom.",
"joinTitle": "Vstup do konferencie",
"joiningTitle": "Žiadam o vstup do konferencie...",
"joiningWithPasswordTitle": "Vstupujem s heslom...",
"knockButton": "Požiadať o vstup",
"knockTitle": "Niekto žiada o vstup do konferencie",
"nameField": "Zadajte vaše meno",
"notificationLobbyAccessDenied": "Žiadosť {{targetParticipantName}} o vstup bola zamietnutá účastníkom {{originParticipantName}}",
"notificationLobbyAccessGranted": "Žiadosť {{targetParticipantName}} o vstup bola povolená účastníkom {{originParticipantName}}",
"notificationLobbyDisabled": "Účastník {{originParticipantName}} vypol čakáreň",
"notificationLobbyEnabled": "Účastník {{originParticipantName}} zapol čakáreň",
"notificationTitle": "Čakáreň",
"passwordField": "Zadajte heslo do konferencie",
"passwordJoinButton": "Vstúpiť",
"reject": "Odmietnuť",
"toggleLabel": "Zapnúť čakáreň"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -579,7 +579,7 @@
},
"security": {
"about": "Toplantınıza bir parola ekleyebilirsiniz. Katılımcıların toplantıya katılmasına izin verilmeden önce parolayı girmeleri gerekecektir.",
"about": "Toplantınıza bir şifre ekleyebilirsiniz. Katılımcıların toplantıya katılmasına izin verilmeden önce şifreyi girmeleri gerekecektir.",
"insecureRoomNameWarning": "Toplantı odası güvenli değil. Konferansınıza istenmeyen katılımcılar katılabilir.",
"securityOptions": "Güvenlik Seçenekleri"
},
@@ -819,7 +819,7 @@
"join": "Katılmak için dokunun",
"roomname": "Oda adı girin"
},
"appDescription": "Durma ve tüm ekiple görüntülü sohbet et. Hatta tanıdığın herkesi davet et. {{app}} tüm gün, her gün ücretsiz olarak ve hesap gerektirmeden kullanabileceğiniz tamamen şifrelenmiş, % 100 özgür bir video konferans çözümüdür.",
"appDescription": "Durma ve tüm ekiple görüntülü sohbet et. Hatta tanıdığın herkesi davet et. {{app}} tüm gün, her gün ücretsiz olarak kullanabileceğiniz, hesap gerektirmeden kullanbilieceğiniz tamamen şifrelenmiş, % 100 özgür bir video konferans çözümüdür.",
"audioVideoSwitch": {
"audio": "Ses",
"video": "Görüntü"
@@ -865,27 +865,27 @@
"lobby": {
"allow": "İzin ver",
"backToKnockModeButton": "Parola yok, bunun yerine katılmayı isteyin",
"backToKnockModeButton": "Şifre yok, bunun yerine katılmayı isteyin",
"dialogTitle": "Lobi modu",
"disableDialogContent": "Lobi modu şu anda etkin. Bu özellik, istenmeyen katılımcıların toplantınıza katılamamasını sağlar. Devre dışı bırakmak istiyor musunuz?",
"disableDialogSubmit": "Devre Dışı",
"emailField": "E-posta adresinizi giriniz",
"enableDialogPasswordField": "Parola belirleyin (isteğe bağlı)",
"enableDialogPasswordField": "Şifre belirleyin (isteğe bağlı)",
"enableDialogSubmit": "Etkin",
"enableDialogText": "Lobi modu, toplantınızı yalnızca kişilerin bir moderatör tarafından resmi olarak onaylandıktan sonra girmelerine izin vererek korumanıza izin verir.",
"enterPasswordButton": "Toplantı parolasını girin",
"enterPasswordTitle": "Toplantıya katılmak için parola girin",
"invalidPassword": "Geçersiz parola",
"enterPasswordButton": "Toplantı şifresini girin",
"enterPasswordTitle": "Toplantıya katılmak için şifre girin",
"invalidPassword": "Geçersiz şifre",
"joiningMessage": "Birisi isteğinizi kabul eder etmez toplantıya katılacaksınız",
"joinWithPasswordMessage": "Parola ile katılmaya çalışıyorsunuz lütfen bekleyin...",
"joinWithPasswordMessage": "Şifre ile katılmaya çalışıyorsunuz lütfen bekleyin...",
"joinRejectedMessage": "Katılma isteğiniz bir moderatör tarafından reddedildi.",
"joinTitle": "Toplantıya katıl",
"joiningTitle": "Toplantıya katılma isteniyor...",
"joiningWithPasswordTitle": "Parola ile katılıyor...",
"joiningWithPasswordTitle": "Şifre ile katılıyor...",
"knockButton": "Katılmak için sor",
"knockTitle": "Birisi toplantıya katılmak istiyor",
"nameField": "Adınızı giriniz",
"passwordField": "Toplantı parolasını giriniz",
"passwordField": "Toplantı şifresini giriniz",
"passwordJoinButton": "Katıl",
"reject": "Reddet",
"toggleLabel": "Lobiyi etkinleştir"

View File

@@ -99,7 +99,6 @@
},
"connectionindicator": {
"address": "Address:",
"audio_ssrc": "Audio SSRC:",
"bandwidth": "Estimated bandwidth:",
"bitrate": "Bitrate:",
"bridgeCount": "Server count: ",
@@ -127,12 +126,9 @@
"remoteport": "Remote port:",
"remoteport_plural": "Remote ports:",
"resolution": "Resolution:",
"savelogs": "Save logs",
"participant_id": "Participant id:",
"status": "Connection:",
"transport": "Transport:",
"transport_plural": "Transports:",
"video_ssrc": "Video SSRC:"
"transport_plural": "Transports:"
},
"dateUtils": {
"earlier": "Earlier",

View File

@@ -15,18 +15,13 @@ 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 { toggleE2EE } from '../../react/features/e2ee/actions';
import { invite } from '../../react/features/invite';
import {
captureLargeVideoScreenshot,
resizeLargeVideo,
selectParticipantInLargeVideo
} from '../../react/features/large-video/actions';
import { 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';
@@ -121,19 +116,9 @@ 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;
@@ -349,21 +334,6 @@ 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;
@@ -1026,19 +996,6 @@ class API {
});
}
/**
* Notify external application (if API is enabled) that the localStorage has changed.
*
* @param {string} localStorageContent - The new localStorageContent.
* @returns {void}
*/
notifyLocalStorageChanged(localStorageContent: string) {
this._sendEvent({
name: 'local-storage-changed',
localStorageContent
});
}
/**
* Disposes the allocated resources.
*

View File

@@ -1,4 +1,3 @@
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
import EventEmitter from 'events';
import { urlObjectToString } from '../../../react/features/base/util/uri';
@@ -36,8 +35,6 @@ 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',
@@ -269,7 +266,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
userInfo,
e2eeKey
} = parseArguments(args);
const localStorageContent = jitsiLocalStorage.getItem('jitsiLocalStorage');
this._parentNode = parentNode;
this._url = generateURL(domain, {
@@ -279,10 +275,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
noSSL,
roomName,
devices,
userInfo,
appData: {
localStorageContent
}
userInfo
});
this._createIFrame(height, width, onload);
this._transport = new Transport({
@@ -444,12 +437,10 @@ 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;
}
}
@@ -531,11 +522,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
case 'video-quality-changed':
this._videoQuality = data.videoQuality;
break;
case 'local-storage-changed':
jitsiLocalStorage.setItem('jitsiLocalStorage', data.localStorageContent);
// Since this is internal event we don't need to emit it to the consumer of the API.
return true;
}
const eventName = events[name];
@@ -647,18 +633,6 @@ 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.
*
@@ -930,17 +904,6 @@ 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.
*
@@ -967,19 +930,6 @@ 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

View File

@@ -5,6 +5,13 @@
*/
const UIUtil = {
/**
* Returns the available video width.
*/
getAvailableVideoWidth() {
return window.innerWidth;
},
/**
* Escapes the given text.
*/

View File

@@ -15,12 +15,13 @@ import { VIDEO_TYPE } from '../../../react/features/base/media';
import { CHAT_SIZE } from '../../../react/features/chat';
import {
updateKnownLargeVideoResolution
} from '../../../react/features/large-video/actions';
} from '../../../react/features/large-video';
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';
@@ -67,30 +68,7 @@ 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;
/**
@@ -345,32 +323,20 @@ export default class LargeVideoManager {
/**
* Update container size.
*/
updateContainerSize(width, height) {
if (typeof width === 'number') {
this.preferredWidth = width;
}
if (typeof height === 'number') {
this.preferredHeight = height;
}
let widthToUse = this.preferredWidth || window.innerWidth;
updateContainerSize() {
let widthToUse = UIUtil.getAvailableVideoWidth();
const { isOpen } = APP.store.getState()['features/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) {
if (isOpen) {
/**
* If chat state is open, we re-compute the container width
* by subtracting the default width of the chat.
*/
widthToUse -= CHAT_SIZE;
this.resizedForChat = true;
} else if (this.resizedForChat) {
this.resizedForChat = false;
widthToUse += CHAT_SIZE;
}
this.width = widthToUse;
this.height = this.preferredHeight || window.innerHeight;
this.height = window.innerHeight;
}
/**

View File

@@ -373,6 +373,7 @@ export default class RemoteVideo extends SmallVideo {
if (stream === this.videoStream) {
this.videoStream = null;
this.videoType = undefined;
}
this.updateView();
@@ -483,6 +484,7 @@ export default class RemoteVideo extends SmallVideo {
if (isVideo) {
this.videoStream = stream;
this.videoType = stream.videoType;
} else {
this.audioStream = stream;
}

View File

@@ -243,10 +243,6 @@ export default class SmallVideo {
* or hidden
*/
setScreenSharing(isScreenSharing) {
if (isScreenSharing === this.isScreenSharing) {
return;
}
this.isScreenSharing = isScreenSharing;
this.updateView();
this.updateStatusBar();

View File

@@ -489,7 +489,7 @@ const VideoLayout = {
onVideoTypeChanged(id, newVideoType) {
const remoteVideo = remoteVideos[id];
if (!remoteVideo) {
if (!remoteVideo || remoteVideo.videoType === newVideoType) {
return;
}

View File

@@ -209,7 +209,7 @@ export default {
})
.catch(err => {
audioTrackError = err;
showError && APP.store.dispatch(notifyMicError(err));
showError && APP.store.disptach(notifyMicError(err));
return [];
}));

787
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,7 @@
"@atlaskit/theme": "7.0.2",
"@atlaskit/toggle": "5.0.14",
"@atlaskit/tooltip": "12.1.13",
"@jitsi/js-utils": "1.0.2",
"@jitsi/js-utils": "1.0.1",
"@microsoft/microsoft-graph-client": "1.1.0",
"@react-native-community/async-storage": "1.3.4",
"@react-native-community/google-signin": "3.0.1",
@@ -56,14 +56,13 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0cffc064e644ad87ff381cc6c4df1c1a9f2c73ff",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#43e7c853b834dc7ced0f81ee5f4b130444d85e95",
"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",
@@ -71,7 +70,7 @@
"react-linkify": "1.0.0-alpha",
"react-native": "github:jitsi/react-native#efd2aff5661d75a230e36406b698cfe0ee545be2",
"react-native-background-timer": "2.4.0",
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#df48ecdc4e1e90c5352f803ddbab1fa7269b74a7",
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#928a80e2ffef0d7e84936d7e7e0acc4f53ee8470",
"react-native-callstats": "3.61.0",
"react-native-collapsible": "1.5.1",
"react-native-default-preference": "1.4.2",
@@ -84,7 +83,7 @@
"react-native-swipeout": "2.3.6",
"react-native-watch-connectivity": "0.4.3",
"react-native-webrtc": "1.84.0",
"react-native-webview": "10.9.0",
"react-native-webview": "7.4.1",
"react-native-youtube-iframe": "1.2.3",
"react-redux": "7.1.0",
"react-textarea-autosize": "7.1.0",
@@ -93,7 +92,6 @@
"redux-thunk": "2.2.0",
"rnnoise-wasm": "github:jitsi/rnnoise-wasm.git#566a16885897704d6e6d67a1d5ac5d39781db2af",
"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",

View File

@@ -57,15 +57,12 @@ 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 async function createHandlers({ getState }: { getState: Function }) {
export function createHandlers({ getState }: { getState: Function }) {
getJitsiMeetGlobalNS().analyticsHandlers = [];
window.analyticsHandlers = []; // Legacy support.
if (!isAnalyticsEnabled(getState)) {
// Avoid all analytics processing if there are no handlers, since no event would be sent.
analytics.dispose();
return [];
return Promise.resolve([]);
}
const state = getState();
@@ -103,47 +100,43 @@ export async function createHandlers({ getState }: { getState: Function }) {
};
const handlers = [];
if (amplitudeAPPKey) {
try {
const amplitude = new AmplitudeHandler(handlerConstructorOptions);
try {
const amplitude = new AmplitudeHandler(handlerConstructorOptions);
analytics.amplitudeIdentityProps = amplitude.getIdentityProps();
analytics.amplitudeIdentityProps = amplitude.getIdentityProps();
handlers.push(amplitude);
} catch (e) {
logger.error('Failed to initialize Amplitude handler', e);
}
}
handlers.push(amplitude);
// eslint-disable-next-line no-empty
} catch (e) {}
if (matomoEndpoint && matomoSiteID) {
try {
const matomo = new MatomoHandler(handlerConstructorOptions);
try {
const matomo = new MatomoHandler(handlerConstructorOptions);
handlers.push(matomo);
} catch (e) {
logger.error('Failed to initialize Matomo handler', e);
}
}
handlers.push(matomo);
// eslint-disable-next-line no-empty
} catch (e) {}
if (Array.isArray(scriptURLs) && scriptURLs.length > 0) {
let externalHandlers;
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!');
}
try {
externalHandlers = await _loadHandlers(scriptURLs, handlerConstructorOptions);
handlers.push(...externalHandlers);
} catch (e) {
logger.error('Failed to initialize external analytics handlers', e);
}
}
return handlers;
})
.catch(e => {
analytics.dispose();
if (handlers.length !== 0) {
logger.error(e);
}
// Avoid all analytics processing if there are no handlers, since no event would be sent.
if (handlers.length === 0) {
analytics.dispose();
}
return [];
}));
logger.info(`Initialized ${handlers.length} analytics handlers`);
return handlers;
}
/**
@@ -235,7 +228,7 @@ function _inIframe() {
}
/**
* Tries to load the scripts for the external analytics handlers and creates them.
* Tries to load the scripts for the 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.
@@ -286,7 +279,7 @@ function _loadHandlers(scriptURLs = [], handlerConstructorOptions) {
logger.warn(`Error creating analytics handler: ${error}`);
}
}
logger.debug(`Loaded ${handlers.length} external analytics handlers`);
logger.debug(`Loaded ${handlers.length} analytics handlers`);
return handlers;
});

View File

@@ -72,11 +72,6 @@ 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,

View File

@@ -294,8 +294,6 @@ export function maybeRedirectToWelcomePage(options: Object = {}) {
if (enableClosePage) {
if (isVpaasMeeting(getState())) {
redirectToStaticPage('/');
return;
}
const { isGuest, jwt } = getState()['features/base/jwt'];

View File

@@ -15,6 +15,8 @@ export default [
'abTesting',
'analytics.disabled',
'audioLevelsInterval',
'autoRecord',
'autoRecordToken',
'apiLogLevels',
'avgRtpStatsN',

View File

@@ -1,5 +0,0 @@
// @flow
import { getLogger } from '../logging/functions';
export default getLogger('features/base/jitsi-local-storage');

View File

@@ -1,45 +0,0 @@
// @flow
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
import { parseURLParams } from '../util/parseURLParams';
import logger from './logger';
declare var APP: Object;
/**
* Handles changes of the fake local storage.
*
* @returns {void}
*/
function onFakeLocalStorageChanged() {
APP.API.notifyLocalStorageChanged(jitsiLocalStorage.serialize());
}
/**
* Performs initial setup of the jitsiLocalStorage.
*
* @returns {void}
*/
function setupJitsiLocalStorage() {
if (jitsiLocalStorage.isLocalStorageDisabled()) {
const urlParams = parseURLParams(window.location);
try {
const localStorageContent = JSON.parse(urlParams['appData.localStorageContent']);
if (typeof localStorageContent === 'object') {
Object.keys(localStorageContent).forEach(key => {
jitsiLocalStorage.setItem(key, localStorageContent[key]);
});
}
} catch (error) {
logger.error('Can\'t parse localStorageContent.', error);
}
jitsiLocalStorage.on('changed', onFakeLocalStorageChanged);
}
}
setupJitsiLocalStorage();

View File

@@ -1,6 +1,5 @@
/* @flow */
import { jitsiLocalStorage } from '@jitsi/js-utils';
import type { Dispatch } from 'redux';
import { isOnline } from '../net-info/selectors';
@@ -52,8 +51,7 @@ export function initLib() {
try {
JitsiMeetJS.init({
enableAnalyticsLogging: isAnalyticsEnabled(getState),
...config,
externalStorage: jitsiLocalStorage.isLocalStorageDisabled() ? jitsiLocalStorage : undefined
...config
});
JitsiMeetJS.setNetworkInfo({
isOnline: isOnline(state)

View File

@@ -18,7 +18,7 @@ const { JavaScriptSandbox } = NativeModules;
*/
export async function loadConfig(url: string): Promise<Object> {
try {
const configTxt = await loadScript(url, 10 * 1000 /* Timeout in ms */, true /* skipeval */);
const configTxt = await loadScript(url, 2.5 * 1000 /* Timeout in ms */, true /* skipeval */);
const configJson = await JavaScriptSandbox.evaluate(`${configTxt}\nJSON.stringify(config);`);
const config = JSON.parse(configJson);

View File

@@ -1,6 +1,5 @@
// @flow
import punycode from 'punycode';
import React, { Component } from 'react';
import ReactLinkify from 'react-linkify';
import { Text } from 'react-native';
@@ -69,7 +68,7 @@ export default class Linkify extends Component<Props> {
key = { key }
style = { this.props.linkStyle }
url = { decoratedHref }>
{ punycode.toASCII(decoratedText) }
{decoratedText}
</Link>
);
}

View File

@@ -1,6 +1,5 @@
// @flow
import punycode from 'punycode';
import React, { Component } from 'react';
import ReactLinkify from 'react-linkify';
@@ -45,7 +44,7 @@ export default class Linkify extends Component<Props> {
key = { key }
rel = 'noopener noreferrer'
target = '_blank'>
{ punycode.toASCII(decoratedText) }
{decoratedText}
</a>
);
}

View File

@@ -24,19 +24,35 @@ const _RIGHT_WATERMARK_STYLE = {
type Props = {
/**
* The link used to navigate to on logo click.
* The user selected url used to navigate to on logo click.
*/
_logoLink: string,
_customLogoLink: string,
/**
* The url for the logo.
* The url of the user selected logo.
*/
_logoUrl: string,
_customLogoUrl: string,
/**
* If the Jitsi watermark should be displayed or not.
* Whether or not the current user is logged in through a JWT.
*/
_showJitsiWatermark: boolean,
_isGuest: boolean,
/**
* Whether or not the current meeting is a vpaas one.
*/
_isVpaas: boolean,
/**
* Flag used to signal that the logo can be displayed.
* It becomes true after the user customization options are fetched.
*/
_readyToDisplayJitsiWatermark: boolean,
/**
* Returns true if welcome page is visible at the moment.
*/
_welcomePageIsVisible: boolean,
/**
* The default value for the Jitsi logo URL.
@@ -59,11 +75,27 @@ type State = {
*/
brandWatermarkLink: string,
/**
* The url to open when clicking the Jitsi watermark.
*/
jitsiWatermarkLink: string,
/**
* Whether or not the brand watermark should be displayed.
*/
showBrandWatermark: boolean,
/**
* Whether or not the Jitsi watermark should be displayed.
*/
showJitsiWatermark: boolean,
/**
* Whether or not the Jitsi watermark should be displayed for users not
* logged in through a JWT.
*/
showJitsiWatermarkForGuests: boolean,
/**
* Whether or not the show the "powered by Jitsi.org" link.
*/
@@ -85,17 +117,29 @@ class Watermarks extends Component<Props, State> {
super(props);
let showBrandWatermark;
let showJitsiWatermark;
let showJitsiWatermarkForGuests;
if (interfaceConfig.filmStripOnly) {
showBrandWatermark = false;
showJitsiWatermark = false;
showJitsiWatermarkForGuests = false;
} else {
showBrandWatermark = interfaceConfig.SHOW_BRAND_WATERMARK;
showJitsiWatermark = interfaceConfig.SHOW_JITSI_WATERMARK;
showJitsiWatermarkForGuests
= interfaceConfig.SHOW_WATERMARK_FOR_GUESTS;
}
this.state = {
brandWatermarkLink:
showBrandWatermark ? interfaceConfig.BRAND_WATERMARK_LINK : '',
jitsiWatermarkLink:
showJitsiWatermark || showJitsiWatermarkForGuests
? interfaceConfig.JITSI_WATERMARK_LINK : '',
showBrandWatermark,
showJitsiWatermark,
showJitsiWatermarkForGuests,
showPoweredBy: interfaceConfig.SHOW_POWERED_BY
};
}
@@ -122,6 +166,55 @@ class Watermarks extends Component<Props, State> {
);
}
/**
* Returns true if the watermark is ready to be displayed.
*
* @private
* @returns {boolean}
*/
_canDisplayJitsiWatermark() {
const {
showJitsiWatermark,
showJitsiWatermarkForGuests
} = this.state;
const {
_isGuest,
_readyToDisplayJitsiWatermark,
_welcomePageIsVisible
} = this.props;
return (_readyToDisplayJitsiWatermark
&& (showJitsiWatermark || (_isGuest && showJitsiWatermarkForGuests)))
|| _welcomePageIsVisible;
}
/**
* Returns the background image style.
*
* @private
* @returns {string}
*/
_getBackgroundImageStyle() {
const {
_customLogoUrl,
_isVpaas,
defaultJitsiLogoURL
} = this.props;
let style = 'none';
if (_isVpaas) {
if (_customLogoUrl) {
style = `url(${_customLogoUrl})`;
}
} else {
style = `url(${_customLogoUrl
|| defaultJitsiLogoURL
|| interfaceConfig.DEFAULT_LOGO_URL})`;
}
return style;
}
/**
* Renders a brand watermark if it is enabled.
*
@@ -161,28 +254,33 @@ class Watermarks extends Component<Props, State> {
* @returns {ReactElement|null}
*/
_renderJitsiWatermark() {
const {
_logoLink,
_logoUrl,
_showJitsiWatermark
} = this.props;
let reactElement = null;
if (_showJitsiWatermark) {
if (this._canDisplayJitsiWatermark()) {
const backgroundImage = this._getBackgroundImageStyle();
const link = this.props._customLogoLink || this.state.jitsiWatermarkLink;
const additionalStyles = {};
if (backgroundImage === 'none') {
additionalStyles.height = 0;
additionalStyles.width = 0;
}
const style = {
backgroundImage: `url(${_logoUrl})`,
backgroundImage,
maxWidth: 140,
maxHeight: 70
maxHeight: 70,
...additionalStyles
};
reactElement = (<div
className = 'watermark leftwatermark'
style = { style } />);
if (_logoLink) {
if (link) {
reactElement = (
<a
href = { _logoLink }
href = { link }
target = '_new'>
{ reactElement }
</a>
@@ -221,52 +319,27 @@ class Watermarks extends Component<Props, State> {
* Maps parts of Redux store to component prop types.
*
* @param {Object} state - Snapshot of Redux store.
* @param {Object} ownProps - Component's own props.
* @returns {Props}
*/
function _mapStateToProps(state, ownProps) {
function _mapStateToProps(state) {
const { isGuest } = state['features/base/jwt'];
const {
customizationReady,
customizationFailed,
defaultBranding,
useDynamicBrandingData,
logoClickUrl,
logoImageUrl
} = state['features/dynamic-branding'];
const isValidRoom = state['features/base/conference'].room;
const {
DEFAULT_LOGO_URL,
JITSI_WATERMARK_LINK,
SHOW_JITSI_WATERMARK,
SHOW_JITSI_WATERMARK_FOR_GUESTS,
filmStripOnly
} = interfaceConfig;
let _showJitsiWatermark = (!filmStripOnly
&& (customizationReady && !customizationFailed)
&& (SHOW_JITSI_WATERMARK || (isGuest && SHOW_JITSI_WATERMARK_FOR_GUESTS)))
|| !isValidRoom;
let _logoUrl = logoImageUrl;
let _logoLink = logoClickUrl;
if (useDynamicBrandingData) {
if (isVpaasMeeting(state)) {
// don't show logo if request fails or no logo set for vpaas meetings
_showJitsiWatermark = !customizationFailed && Boolean(logoImageUrl);
} else if (defaultBranding) {
_logoUrl = DEFAULT_LOGO_URL;
_logoLink = JITSI_WATERMARK_LINK;
}
} else {
// When there is no custom branding data use defaults
_logoUrl = ownProps.defaultJitsiLogoURL || DEFAULT_LOGO_URL;
_logoLink = JITSI_WATERMARK_LINK;
}
const { customizationReady, logoClickUrl, logoImageUrl } = state['features/dynamic-branding'];
const { room } = state['features/base/conference'];
return {
_logoLink,
_logoUrl,
_showJitsiWatermark
/**
* The indicator which determines whether the local participant is a
* guest in the conference.
*
* @private
* @type {boolean}
*/
_customLogoLink: logoClickUrl,
_customLogoUrl: logoImageUrl,
_isGuest: isGuest,
_isVpaas: isVpaasMeeting(state),
_readyToDisplayJitsiWatermark: customizationReady,
_welcomePageIsVisible: !room
};
}

View File

@@ -153,10 +153,12 @@ MiddlewareRegistry.register(store => next => action => {
const isVideoTrack = jitsiTrack.type !== MEDIA_TYPE.AUDIO;
if (isVideoTrack) {
// Do not change the video mute state for local presenter tracks.
if (jitsiTrack.type === MEDIA_TYPE.PRESENTER) {
APP.conference.mutePresenter(muted);
} else if (jitsiTrack.isLocal()) {
}
// Make sure we change the video mute state only for camera tracks.
if (jitsiTrack.isLocal() && jitsiTrack.videoType !== 'desktop') {
APP.conference.setVideoMuteStatus(muted);
} else {
APP.UI.setVideoMuted(participantID, muted);

View File

@@ -1,23 +0,0 @@
// @flow
/**
* Downloads a JSON object.
*
* @param {Object} json - The JSON object to download.
* @param {string} filename - The filename to give to the downloaded file.
* @returns {void}
*/
export function downloadJSON(json: Object, filename: string) {
const data = encodeURIComponent(JSON.stringify(json, null, ' '));
const elem = document.createElement('a');
elem.download = filename;
elem.href = `data:application/json;charset=utf-8,\n${data}`;
elem.dataset.downloadurl = [ 'text/json', elem.download, elem.href ].join(':');
elem.dispatchEvent(new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: false
}));
}

View File

@@ -546,7 +546,7 @@ export function urlObjectToString(o: Object): ?string {
let { hash } = url;
for (const urlPrefix of [ 'config', 'interfaceConfig', 'devices', 'userInfo', 'appData' ]) {
for (const urlPrefix of [ 'config', 'interfaceConfig', 'devices', 'userInfo' ]) {
const urlParamsArray
= _objectToURLParamsArray(
o[`${urlPrefix}Overwrite`]

View File

@@ -1,18 +0,0 @@
import getRoomName from '../base/config/getRoomName';
import { downloadJSON } from '../base/util/downloadJSON';
/**
* Create an action for saving the conference logs.
*
* @returns {Function}
*/
export function saveLogs() {
return (dispatch, getState) => {
const logs = getState()['features/base/connection'].connection.getLogs();
const roomName = getRoomName() || '';
downloadJSON(logs, `meetlog-${roomName}.json`);
};
}

View File

@@ -1,17 +1,12 @@
// @flow
import React from 'react';
import type { Dispatch } from 'redux';
import { translate } from '../../../base/i18n';
import { Icon, IconConnectionActive, IconConnectionInactive } from '../../../base/icons';
import { JitsiParticipantConnectionStatus } from '../../../base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../../../base/media';
import { Popover } from '../../../base/popover';
import { connect } from '../../../base/redux';
import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
import { ConnectionStatsTable } from '../../../connection-stats';
import { saveLogs } from '../../actions';
import AbstractConnectionIndicator, {
INDICATOR_DISPLAY_THRESHOLD,
type Props as AbstractProps,
@@ -67,22 +62,12 @@ type Props = AbstractProps & {
*/
alwaysVisible: boolean,
/**
* The audio SSRC of this client.
*/
audioSsrc: number,
/**
* The current condition of the user's connection, matching one of the
* enumerated values in the library.
*/
connectionStatus: string,
/**
* The Redux dispatch function.
*/
dispatch: Dispatch<any>,
/**
* Whether or not clicking the indicator should display a popover for more
* details.
@@ -108,17 +93,7 @@ type Props = AbstractProps & {
/**
* Invoked to obtain translated strings.
*/
t: Function,
/**
* The video SSRC of this client.
*/
videoSsrc: number,
/**
* Invoked to save the conference logs.
*/
_onSaveLogs: Function
t: Function
};
/**
@@ -378,7 +353,6 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
return (
<ConnectionStatsTable
audioSsrc = { this.props.audioSsrc }
bandwidth = { bandwidth }
bitrate = { bitrate }
bridgeCount = { bridgeCount }
@@ -388,67 +362,15 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
framerate = { framerate }
isLocalVideo = { this.props.isLocalVideo }
maxEnabledResolution = { maxEnabledResolution }
onSaveLogs = { this.props._onSaveLogs }
onShowMore = { this._onToggleShowMore }
packetLoss = { packetLoss }
participantId = { this.props.participantId }
region = { region }
resolution = { resolution }
serverRegion = { serverRegion }
shouldShowMore = { this.state.showMoreStats }
transport = { transport }
videoSsrc = { this.props.videoSsrc } />
transport = { transport } />
);
}
}
/**
* Maps redux actions to the props of the component.
*
* @param {Function} dispatch - The redux action {@code dispatch} function.
* @returns {{
* _onSaveLogs: Function,
* }}
* @private
*/
export function _mapDispatchToProps(dispatch: Dispatch<any>) {
return {
/**
* Saves the conference logs.
*
* @returns {Function}
*/
_onSaveLogs() {
dispatch(saveLogs());
}
};
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the component.
* @returns {Props}
*/
export function _mapStateToProps(state: Object, ownProps: Props) {
const conference = state['features/base/conference'].conference;
if (conference) {
const firstVideoTrack = getTrackByMediaTypeAndParticipant(
state['features/base/tracks'], MEDIA_TYPE.VIDEO, ownProps.participantId);
const firstAudioTrack = getTrackByMediaTypeAndParticipant(
state['features/base/tracks'], MEDIA_TYPE.AUDIO, ownProps.participantId);
return {
audioSsrc: firstAudioTrack ? conference.getSsrcByTrack(firstAudioTrack.jitsiTrack) : undefined,
videoSsrc: firstVideoTrack ? conference.getSsrcByTrack(firstVideoTrack.jitsiTrack) : undefined
};
}
return {};
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ConnectionIndicator));
export default translate(ConnectionIndicator);

View File

@@ -10,11 +10,6 @@ import { translate } from '../../base/i18n';
*/
type Props = {
/**
* The audio SSRC of this client.
*/
audioSsrc: number,
/**
* Statistics related to bandwidth.
* {{
@@ -54,11 +49,6 @@ type Props = {
*/
e2eRtt: number,
/**
* The endpoint id of this client.
*/
participantId: string,
/**
* Statistics related to frame rates for each ssrc.
* {{
@@ -78,11 +68,6 @@ type Props = {
*/
maxEnabledResolution: number,
/**
* Callback to invoke when the user clicks on the download logs link.
*/
onSaveLogs: Function,
/**
* Callback to invoke when the show additional stats link is clicked.
*/
@@ -129,11 +114,6 @@ type Props = {
*/
t: Function,
/**
* The video SSRC of this client.
*/
videoSsrc: number,
/**
* Statistics related to transports.
*/
@@ -158,11 +138,9 @@ class ConnectionStatsTable extends Component<Props> {
return (
<div className = 'connection-info'>
{ this._renderStatistics() }
<div className = 'connection-actions'>
{ isLocalVideo ? this._renderSaveLogs() : null}
{ this._renderShowMoreLink() }
</div>
{ this.props.shouldShowMore ? this._renderAdditionalStats() : null }
{ isLocalVideo ? this._renderShowMoreLink() : null }
{ isLocalVideo && this.props.shouldShowMore
? this._renderAdditionalStats() : null }
</div>
);
}
@@ -175,17 +153,12 @@ class ConnectionStatsTable extends Component<Props> {
* @returns {ReactElement}
*/
_renderAdditionalStats() {
const { isLocalVideo } = this.props;
return (
<table className = 'connection-info__container'>
<tbody>
{ isLocalVideo ? this._renderBandwidth() : null }
{ isLocalVideo ? this._renderTransport() : null }
{ isLocalVideo ? this._renderRegion() : null }
{ this._renderAudioSsrc() }
{ this._renderVideoSsrc() }
{ this._renderParticipantId() }
{ this._renderBandwidth() }
{ this._renderTransport() }
{ this._renderRegion() }
</tbody>
</table>
);
@@ -251,66 +224,6 @@ class ConnectionStatsTable extends Component<Props> {
);
}
/**
* Creates a table row as a ReactElement for displaying the audio ssrc.
* This will typically be something like "Audio SSRC: 12345".
*
* @returns {JSX.Element}
* @private
*/
_renderAudioSsrc() {
const { audioSsrc, t } = this.props;
return (
<tr>
<td>
<span>{ t('connectionindicator.audio_ssrc') }</span>
</td>
<td>{ audioSsrc || 'N/A' }</td>
</tr>
);
}
/**
* Creates a table row as a ReactElement for displaying the video ssrc.
* This will typically be something like "Video SSRC: 12345".
*
* @returns {JSX.Element}
* @private
*/
_renderVideoSsrc() {
const { videoSsrc, t } = this.props;
return (
<tr>
<td>
<span>{ t('connectionindicator.video_ssrc') }</span>
</td>
<td>{ videoSsrc || 'N/A' }</td>
</tr>
);
}
/**
* Creates a table row as a ReactElement for displaying the endpoint id.
* This will typically be something like "Endpoint id: 1e8fbg".
*
* @returns {JSX.Element}
* @private
*/
_renderParticipantId() {
const { participantId, t } = this.props;
return (
<tr>
<td>
<span>{ t('connectionindicator.participant_id') }</span>
</td>
<td>{ participantId || 'N/A' }</td>
</tr>
);
}
/**
* Creates a a table row as a ReactElement for displaying codec, if present.
* This will typically be something like "Codecs (A/V): Opus, vp8".
@@ -542,26 +455,6 @@ class ConnectionStatsTable extends Component<Props> {
);
}
/**
* Creates a ReactElement for display a link to save the logs.
*
* @private
* @returns {ReactElement}
*/
_renderSaveLogs() {
return (
<span>
<a
className = 'savelogs link'
onClick = { this.props.onSaveLogs } >
{ this.props.t('connectionindicator.savelogs') }
</a>
<span> | </span>
</span>
);
}
/**
* Creates a ReactElement for display a link to toggle showing additional
* statistics.

View File

@@ -3,11 +3,6 @@
*/
export const SET_DYNAMIC_BRANDING_DATA = 'SET_DYNAMIC_BRANDING_DATA';
/**
* Action used to signal the customization failed.
*/
export const SET_DYNAMIC_BRANDING_FAILED = 'SET_DYNAMIC_BRANDING_FAILED';
/**
* Action used to signal the branding elements are ready to be displayed
*/

View File

@@ -4,11 +4,7 @@ import { getLogger } from 'jitsi-meet-logger';
import { doGetJSON } from '../base/util';
import {
SET_DYNAMIC_BRANDING_DATA,
SET_DYNAMIC_BRANDING_FAILED,
SET_DYNAMIC_BRANDING_READY
} from './actionTypes';
import { SET_DYNAMIC_BRANDING_DATA, SET_DYNAMIC_BRANDING_READY } from './actionTypes';
import { extractFqnFromPath } from './functions';
const logger = getLogger(__filename);
@@ -36,8 +32,6 @@ export function fetchCustomBrandingData() {
return dispatch(setDynamicBrandingData(res));
} catch (err) {
logger.error('Error fetching branding data', err);
return dispatch(setDynamicBrandingFailed());
}
}
@@ -69,14 +63,3 @@ function setDynamicBrandingReady() {
type: SET_DYNAMIC_BRANDING_READY
};
}
/**
* Action used to signal the branding request failed.
*
* @returns {Object}
*/
function setDynamicBrandingFailed() {
return {
type: SET_DYNAMIC_BRANDING_FAILED
};
}

View File

@@ -2,11 +2,7 @@
import { ReducerRegistry } from '../base/redux';
import {
SET_DYNAMIC_BRANDING_DATA,
SET_DYNAMIC_BRANDING_FAILED,
SET_DYNAMIC_BRANDING_READY
} from './actionTypes';
import { SET_DYNAMIC_BRANDING_DATA, SET_DYNAMIC_BRANDING_READY } from './actionTypes';
/**
* The name of the redux store/state property which is the root of the redux
@@ -15,80 +11,12 @@ import {
const STORE_NAME = 'features/dynamic-branding';
const DEFAULT_STATE = {
/**
* The custom background color for the LargeVideo.
*
* @public
* @type {string}
*/
backgroundColor: '',
/**
* The custom background image used on the LargeVideo.
*
* @public
* @type {string}
*/
backgroundImageUrl: '',
/**
* Flag indicating that the logo (JitsiWatermark) can be displayed.
* This is used in order to avoid image flickering.
*
* @public
* @type {boolean}
*/
customizationReady: false,
/**
* Flag indicating that the dynamic branding data request has failed.
* When the request fails there is no logo (JitsiWatermark) displayed.
*
* @public
* @type {boolean}
*/
customizationFailed: false,
/**
* Flag indicating that the dynamic branding has not been modified and should use
* the default options.
*
* @public
* @type {boolean}
*/
defaultBranding: true,
/**
* The custom invite domain.
*
* @public
* @type {string}
*/
inviteDomain: '',
/**
* The custom url used when the user clicks the logo.
*
* @public
* @type {string}
*/
logoClickUrl: '',
/**
* The custom logo (JitisWatermark).
*
* @public
* @type {string}
*/
logoImageUrl: '',
/**
* Flag used to signal if the app should use a custom logo or not
*
* @public
* @type {boolean}
*/
useDynamicBrandingData: false
logoImageUrl: ''
};
/**
@@ -97,33 +25,15 @@ const DEFAULT_STATE = {
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
switch (action.type) {
case SET_DYNAMIC_BRANDING_DATA: {
const {
backgroundColor,
backgroundImageUrl,
defaultBranding,
inviteDomain,
logoClickUrl,
logoImageUrl
} = action.value;
const { backgroundColor, backgroundImageUrl, inviteDomain, logoClickUrl, logoImageUrl } = action.value;
return {
backgroundColor,
backgroundImageUrl,
defaultBranding,
inviteDomain,
logoClickUrl,
logoImageUrl,
customizationFailed: false,
customizationReady: true,
useDynamicBrandingData: true
};
}
case SET_DYNAMIC_BRANDING_FAILED: {
return {
...state,
customizationReady: true,
customizationFailed: true,
useDynamicBrandingData: true
customizationReady: true
};
}
case SET_DYNAMIC_BRANDING_READY:
@@ -131,6 +41,7 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
...state,
customizationReady: true
};
}
return state;

View File

@@ -10,7 +10,6 @@ import { translate } from '../../../../base/i18n';
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
import { getLocalParticipant } from '../../../../base/participants';
import { connect } from '../../../../base/redux';
import { isVpaasMeeting } from '../../../../billing-counter/functions';
import EmbedMeetingTrigger from '../../../../embed-meeting/components/EmbedMeetingTrigger';
import { getActiveSession } from '../../../../recording';
import { updateDialInNumbers } from '../../../actions';
@@ -37,11 +36,6 @@ type Props = {
*/
_dialIn: Object,
/**
* Whether or not embed meeting should be visible.
*/
_embedMeetingVisible: boolean,
/**
* Whether or not invite contacts should be visible.
*/
@@ -86,7 +80,6 @@ type Props = {
function AddPeopleDialog({
_conferenceName,
_dialIn,
_embedMeetingVisible,
_inviteContactsVisible,
_inviteUrl,
_liveStreamViewURL,
@@ -159,7 +152,7 @@ function AddPeopleDialog({
<InviteByEmailSection
inviteSubject = { inviteSubject }
inviteText = { invite } />
{ _embedMeetingVisible && <EmbedMeetingTrigger /> }
<EmbedMeetingTrigger />
<div className = 'invite-more-dialog separator' />
{
_liveStreamViewURL
@@ -198,7 +191,6 @@ function mapStateToProps(state) {
return {
_conferenceName: getRoomName(state),
_dialIn: state['features/invite'],
_embedMeetingVisible: !isVpaasMeeting(state),
_inviteContactsVisible: interfaceConfig.ENABLE_DIAL_OUT && !hideInviteContacts,
_inviteUrl: getInviteURL(state),
_liveStreamViewURL:

View File

@@ -17,6 +17,8 @@ import {
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
} from './actionTypes';
declare var APP: Object;
/**
* Signals conference to select a participant.
*
@@ -48,7 +50,7 @@ export function selectParticipant() {
/**
* Action to select the participant to be displayed in LargeVideo based on the
* participant id provided. If a participant id is not provided, the LargeVideo
* participant id provided. If a partcipant id is not provided, the LargeVideo
* participant will be selected based on a variety of factors: If there is a
* dominant or pinned speaker, or if there are remote tracks, etc.
*

View File

@@ -1,3 +0,0 @@
// @flow
export * from './actions.any';

View File

@@ -1,75 +0,0 @@
// @flow
import type { Dispatch } from 'redux';
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
import { MEDIA_TYPE } from '../base/media';
import { getTrackByMediaTypeAndParticipant } from '../base/tracks';
export * from './actions.any';
/**
* Captures a screenshot of the video displayed on the large video.
*
* @returns {Function}
*/
export function captureLargeVideoScreenshot() {
return (dispatch: Dispatch<any>, getState: Function): Promise<string> => {
const state = getState();
const largeVideo = state['features/large-video'];
if (!largeVideo) {
return Promise.resolve();
}
const tracks = state['features/base/tracks'];
const { jitsiTrack } = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, largeVideo.participantId);
const videoStream = jitsiTrack.getOriginalStream();
// Get the video element for the large video, cast HTMLElement to HTMLVideoElement to make flow happy.
/* eslint-disable-next-line no-extra-parens*/
const videoElement = ((document.getElementById('largeVideo'): any): HTMLVideoElement);
if (!videoElement) {
return Promise.resolve();
}
// Create a HTML canvas and draw video on to the canvas.
const [ track ] = videoStream.getVideoTracks();
const { height, width } = track.getSettings() ?? track.getConstraints();
const canvasElement = document.createElement('canvas');
const ctx = canvasElement.getContext('2d');
canvasElement.style.display = 'none';
canvasElement.height = parseInt(height, 10);
canvasElement.width = parseInt(width, 10);
ctx.drawImage(videoElement, 0, 0);
const dataURL = canvasElement.toDataURL('image/png', 1.0);
// Cleanup.
ctx.clearRect(0, 0, canvasElement.width, canvasElement.height);
canvasElement.remove();
return Promise.resolve(dataURL);
};
}
/**
* Resizes the large video container based on 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 {Function}
*/
export function resizeLargeVideo(width: number, height: number) {
return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState();
const largeVideo = state['features/large-video'];
if (largeVideo) {
const largeVideoContainer = VideoLayout.getLargeVideo();
largeVideoContainer.updateContainerSize(width, height);
largeVideoContainer.resize();
}
};
}

View File

@@ -4,7 +4,6 @@ import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../base/conference';
import { JitsiConferenceErrors, JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { getFirstLoadableAvatarUrl, getParticipantDisplayName } from '../base/participants';
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { isTestModeEnabled } from '../base/testing';
import { NOTIFICATION_TYPE, showNotification } from '../notifications';
import { isPrejoinPageEnabled } from '../prejoin/functions';
@@ -194,5 +193,5 @@ function _maybeSendLobbyNotification(origin, message, { dispatch, getState }) {
break;
}
dispatch(showNotification(notificationProps, isTestModeEnabled(getState()) ? undefined : 5000));
dispatch(showNotification(notificationProps, 5000));
}

View File

@@ -1,21 +0,0 @@
// @flow
import { toState } from '../base/redux';
/**
* Checks whether rtcstats is enabled or not.
*
* @param {Function|Object} stateful - The redux store or {@code getState} function.
* @returns {boolean}
*/
export function isRtcstatsEnabled(stateful: Function | Object) {
// TODO: Remove when rtcstats is fully cimpatible with mobile.
if (navigator.product === 'ReactNative') {
return false;
}
const state = toState(stateful);
const config = state['features/base/config'];
return config?.analytics?.rtcstatsEnabled ?? false;
}

View File

@@ -9,7 +9,6 @@ import { getLocalParticipant } from '../base/participants';
import { MiddlewareRegistry } from '../base/redux';
import RTCStats from './RTCStats';
import { isRtcstatsEnabled } from './functions';
import logger from './logger';
/**
@@ -26,7 +25,7 @@ MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case LIB_WILL_INIT: {
if (isRtcstatsEnabled(state)) {
if (analytics.rtcstatsEnabled) {
// RTCStats "proxies" WebRTC functions such as GUM and RTCPeerConnection by rewriting the global
// window functions. Because lib-jitsi-meet uses references to those functions that are taken on
// init, we need to add these proxies before it initializes, otherwise lib-jitsi-meet will use the
@@ -48,7 +47,7 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case CONFERENCE_JOINED: {
if (isRtcstatsEnabled(state) && RTCStats.isInitialized()) {
if (analytics.rtcstatsEnabled && RTCStats.isInitialized()) {
// Once the conference started connect to the rtcstats server and send data.
try {
RTCStats.connect();

View File

@@ -1,11 +1,11 @@
// @flow
import * as StackBlur from 'stackblur-canvas';
import * as bodyPix from '@tensorflow-models/body-pix';
import {
CLEAR_TIMEOUT,
TIMEOUT_TICK,
SET_TIMEOUT,
CLEAR_INTERVAL,
INTERVAL_TIMEOUT,
SET_INTERVAL,
timerWorkerScript
} from './TimerWorker';
@@ -17,7 +17,6 @@ import {
export default class JitsiStreamBlurEffect {
_bpModel: Object;
_inputVideoElement: HTMLVideoElement;
_inputVideoCanvasElement: HTMLCanvasElement;
_onMaskFrameTimer: Function;
_maskFrameTimerWorker: Worker;
_maskInProgress: boolean;
@@ -44,7 +43,6 @@ export default class JitsiStreamBlurEffect {
this._outputCanvasElement = document.createElement('canvas');
this._outputCanvasElement.getContext('2d');
this._inputVideoElement = document.createElement('video');
this._inputVideoCanvasElement = document.createElement('canvas');
}
/**
@@ -55,8 +53,10 @@ export default class JitsiStreamBlurEffect {
* @returns {void}
*/
async _onMaskFrameTimer(response: Object) {
if (response.data.id === TIMEOUT_TICK) {
await this._renderMask();
if (response.data.id === INTERVAL_TIMEOUT) {
if (!this._maskInProgress) {
await this._renderMask();
}
}
}
@@ -67,53 +67,20 @@ export default class JitsiStreamBlurEffect {
* @returns {void}
*/
async _renderMask() {
if (!this._maskInProgress) {
this._maskInProgress = true;
this._bpModel.segmentPerson(this._inputVideoElement, {
internalResolution: 'low', // resized to 0.5 times of the original resolution before inference
maxDetections: 1, // max. number of person poses to detect per image
segmentationThreshold: 0.7, // represents probability that a pixel belongs to a person
flipHorizontal: false,
scoreThreshold: 0.2
}).then(data => {
this._segmentationData = data;
this._maskInProgress = false;
});
}
const inputCanvasCtx = this._inputVideoCanvasElement.getContext('2d');
inputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
const currentFrame = inputCanvasCtx.getImageData(
0,
0,
this._inputVideoCanvasElement.width,
this._inputVideoCanvasElement.height
);
if (this._segmentationData) {
const blurData = new ImageData(currentFrame.data.slice(), currentFrame.width, currentFrame.height);
StackBlur.imageDataRGB(blurData, 0, 0, currentFrame.width, currentFrame.height, 12);
for (let x = 0; x < this._outputCanvasElement.width; x++) {
for (let y = 0; y < this._outputCanvasElement.height; y++) {
const n = (y * this._outputCanvasElement.width) + x;
if (this._segmentationData.data[n] === 0) {
currentFrame.data[n * 4] = blurData.data[n * 4];
currentFrame.data[(n * 4) + 1] = blurData.data[(n * 4) + 1];
currentFrame.data[(n * 4) + 2] = blurData.data[(n * 4) + 2];
currentFrame.data[(n * 4) + 3] = blurData.data[(n * 4) + 3];
}
}
}
}
this._outputCanvasElement.getContext('2d').putImageData(currentFrame, 0, 0);
this._maskFrameTimerWorker.postMessage({
id: SET_TIMEOUT,
timeMs: 1000 / 30
this._maskInProgress = true;
this._segmentationData = await this._bpModel.segmentPerson(this._inputVideoElement, {
internalResolution: 'medium', // resized to 0.5 times of the original resolution before inference
maxDetections: 1, // max. number of person poses to detect per image
segmentationThreshold: 0.7 // represents probability that a pixel belongs to a person
});
this._maskInProgress = false;
bodyPix.drawBokehEffect(
this._outputCanvasElement,
this._inputVideoElement,
this._segmentationData,
12, // Constant for background blur, integer values between 0-20
7 // Constant for edge blur, integer values between 0-20
);
}
/**
@@ -143,16 +110,14 @@ export default class JitsiStreamBlurEffect {
this._outputCanvasElement.width = parseInt(width, 10);
this._outputCanvasElement.height = parseInt(height, 10);
this._inputVideoCanvasElement.width = parseInt(width, 10);
this._inputVideoCanvasElement.height = parseInt(height, 10);
this._inputVideoElement.width = parseInt(width, 10);
this._inputVideoElement.height = parseInt(height, 10);
this._inputVideoElement.autoplay = true;
this._inputVideoElement.srcObject = stream;
this._inputVideoElement.onloadeddata = () => {
this._maskFrameTimerWorker.postMessage({
id: SET_TIMEOUT,
timeMs: 1000 / 30
id: SET_INTERVAL,
timeMs: 1000 / parseInt(frameRate, 10)
});
};
@@ -166,7 +131,7 @@ export default class JitsiStreamBlurEffect {
*/
stopEffect() {
this._maskFrameTimerWorker.postMessage({
id: CLEAR_TIMEOUT
id: CLEAR_INTERVAL
});
this._maskFrameTimerWorker.terminate();

View File

@@ -1,34 +1,34 @@
/**
* SET_TIMEOUT constant is used to set interval and it is set in
* SET_INTERVAL constant is used to set interval and it is set in
* the id property of the request.data property. timeMs property must
* also be set. request.data example:
*
* {
* id: SET_TIMEOUT,
* id: SET_INTERVAL,
* timeMs: 33
* }
*/
export const SET_TIMEOUT = 1;
export const SET_INTERVAL = 1;
/**
* CLEAR_TIMEOUT constant is used to clear the interval and it is set in
* CLEAR_INTERVAL constant is used to clear the interval and it is set in
* the id property of the request.data property.
*
* {
* id: CLEAR_TIMEOUT
* id: CLEAR_INTERVAL
* }
*/
export const CLEAR_TIMEOUT = 2;
export const CLEAR_INTERVAL = 2;
/**
* TIMEOUT_TICK constant is used as response and it is set in the id property.
* INTERVAL_TIMEOUT constant is used as response and it is set in the id property.
*
* {
* id: TIMEOUT_TICK
* id: INTERVAL_TIMEOUT
* }
*/
export const TIMEOUT_TICK = 3;
export const INTERVAL_TIMEOUT = 3;
/**
* The following code is needed as string to create a URL from a Blob.
@@ -40,15 +40,15 @@ const code = `
onmessage = function(request) {
switch (request.data.id) {
case ${SET_TIMEOUT}: {
timer = setTimeout(() => {
postMessage({ id: ${TIMEOUT_TICK} });
case ${SET_INTERVAL}: {
timer = setInterval(() => {
postMessage({ id: ${INTERVAL_TIMEOUT} });
}, request.data.timeMs);
break;
}
case ${CLEAR_TIMEOUT}: {
case ${CLEAR_INTERVAL}: {
if (timer) {
clearTimeout(timer);
clearInterval(timer);
}
break;
}

View File

@@ -33,7 +33,6 @@ import {
import { connect, equals } from '../../../base/redux';
import { OverflowMenuItem } from '../../../base/toolbox/components';
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
import { isVpaasMeeting } from '../../../billing-counter/functions';
import { VideoBlurButton } from '../../../blur';
import { CHAT_SIZE, ChatCounter, toggleChat } from '../../../chat';
import { EmbedMeetingDialog } from '../../../embed-meeting';
@@ -137,11 +136,6 @@ type Props = {
*/
_isGuest: boolean,
/**
* Whether or not the current meeting belongs to a JaaS user.
*/
_isVpaasMeeting: boolean,
/**
* The ID of the local participant.
*/
@@ -977,15 +971,6 @@ class Toolbox extends Component<Props, State> {
);
}
/**
* Returns true if the profile button is visible and false otherwise.
*
* @returns {boolean}
*/
_isEmbedMeetingVisible() {
return !this.props._isVpaasMeeting && this._shouldShowButton('embedmeeting');
}
/**
* Returns true if the profile button is visible and false otherwise.
*
@@ -1062,7 +1047,7 @@ class Toolbox extends Component<Props, State> {
key = 'stats'
onClick = { this._onToolbarOpenSpeakerStats }
text = { t('toolbar.speakerStats') } />,
this._isEmbedMeetingVisible()
this._shouldShowButton('embedmeeting')
&& <OverflowMenuItem
accessibilityLabel = { t('toolbar.accessibilityLabel.embedMeeting') }
icon = { IconCodeBlock }
@@ -1448,7 +1433,6 @@ function _mapStateToProps(state) {
_dialog: Boolean(state['features/base/dialog'].component),
_feedbackConfigured: Boolean(callStatsID),
_isGuest: state['features/base/jwt'].isGuest,
_isVpaasMeeting: isVpaasMeeting(state),
_fullScreen: fullScreen,
_tileViewEnabled: shouldDisplayTileView(state),
_localParticipantID: localParticipant.id,

View File

@@ -5,7 +5,7 @@ import debounce from 'lodash/debounce';
import { pinParticipant, getPinnedParticipant } from '../base/participants';
import { StateListenerRegistry, equals } from '../base/redux';
import { isFollowMeActive } from '../follow-me';
import { selectParticipant } from '../large-video/actions';
import { selectParticipant } from '../large-video';
import { setParticipantsWithScreenShare } from './actions';

View File

@@ -1,4 +1,3 @@
local new_throttle = require "util.throttle".create;
local st = require "util.stanza";
local token_util = module:require "token/util".new(module);
@@ -11,19 +10,12 @@ if token_util == nil then
return;
end
-- The maximum number of simultaneous calls,
-- and also the maximum number of new calls per minute that a session is allowed to create.
local limit_outgoing_calls;
local function load_config()
limit_outgoing_calls = module:get_option_number("max_number_outgoing_calls", -1);
end
load_config();
-- configuration to limit number of outgoing calls
local LIMIT_OUTGOING_CALLS = module:get_option_number("max_number_outgoing_calls", -1);
-- Header names to use to push extra data extracted from token, if any
local OUT_INITIATOR_USER_ATTR_NAME = "X-outbound-call-initiator-user";
local OUT_INITIATOR_GROUP_ATTR_NAME = "X-outbound-call-initiator-group";
local OUTGOING_CALLS_THROTTLE_INTERVAL = 60; -- if max_number_outgoing_calls is enabled it will be
-- the max number of outgoing calls a user can try for a minute
-- filters rayo iq in case of requested from not jwt authenticated sessions
-- or if the session has features in user context and it doesn't mention
@@ -61,21 +53,15 @@ module:hook("pre-iq/full", function(event)
end
-- now lets check any limits if configured
if limit_outgoing_calls > 0 then
if not session.dial_out_throttle then
module:log("debug", "Enabling dial-out throttle session=%s.", session);
session.dial_out_throttle = new_throttle(limit_outgoing_calls, OUTGOING_CALLS_THROTTLE_INTERVAL);
end
if not session.dial_out_throttle:poll(1) -- we first check the throttle so we can mark one incoming dial for the balance
or get_concurrent_outgoing_count(session.jitsi_meet_context_user["id"], session.jitsi_meet_context_group)
>= limit_outgoing_calls
then
module:log("warn",
"Filtering stanza dial, stanza:%s, outgoing calls limit reached", tostring(stanza));
session.send(st.error_reply(stanza, "cancel", "resource-constraint"));
return true;
end
if LIMIT_OUTGOING_CALLS > 0
and get_concurrent_outgoing_count(
session.jitsi_meet_context_user["id"],
session.jitsi_meet_context_group) >= LIMIT_OUTGOING_CALLS
then
module:log("warn",
"Filtering stanza dial, stanza:%s, outgoing calls limit reached", tostring(stanza));
session.send(st.error_reply(stanza, "cancel", "resource-constraint"));
return true;
end
-- now lets insert token information if any
@@ -177,4 +163,3 @@ function get_concurrent_outgoing_count(context_user, context_group)
return count;
end
module:hook_global('config-reloaded', load_config);

View File

@@ -11,7 +11,7 @@ const hints = [
];
/**
* Get a random hint message from hint array.
* Get a random hint meessage from hint array.
*
* @return {string} the hint message.
*/

View File

@@ -142,8 +142,7 @@ const config = {
// value that is a mock (/index.js).
__filename: true,
// Provide some empty Node modules (required by olm).
crypto: 'empty',
// Provide an empty 'fs' module.
fs: 'empty'
},
optimization: {
@@ -191,7 +190,7 @@ module.exports = [
entry: {
'app.bundle': './app.js'
},
performance: getPerformanceHints(4 * 1024 * 1024)
performance: getPerformanceHints(4.5 * 1024 * 1024)
}),
Object.assign({}, config, {
entry: {