mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-03-06 19:50:22 +00:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f0f8ea019 | ||
|
|
444f4a7abc | ||
|
|
ee6bf011e9 | ||
|
|
bea8a7f984 | ||
|
|
2edca5dacb | ||
|
|
69ac73c556 | ||
|
|
89556ecd66 | ||
|
|
462f91f070 | ||
|
|
d29a77b15f | ||
|
|
c31fe521c4 | ||
|
|
8f6f542e9c | ||
|
|
69d9e7d405 | ||
|
|
5e6748a88a | ||
|
|
8bc70f9c87 | ||
|
|
357d226987 | ||
|
|
6b1f7138c6 | ||
|
|
55219dc51b | ||
|
|
0eb3a9a43c | ||
|
|
4d7136b7a7 | ||
|
|
b7d9e1d85d | ||
|
|
a714058328 | ||
|
|
02ff4a1bac | ||
|
|
7833e1337e | ||
|
|
18e0e64ca0 | ||
|
|
80a3d88359 | ||
|
|
5d72028872 | ||
|
|
e89776848c | ||
|
|
70bc78e765 | ||
|
|
4fceae7733 | ||
|
|
23b7dd4abf | ||
|
|
0216bbd1d9 | ||
|
|
15a4fa45e0 | ||
|
|
f2d9ffd5f6 | ||
|
|
b0ba7c8671 | ||
|
|
e5fa25892e | ||
|
|
ae5fe24556 | ||
|
|
b9ef0aa27a | ||
|
|
f30625acf0 | ||
|
|
66d70305a0 | ||
|
|
9108b7ebec | ||
|
|
9454049220 | ||
|
|
2ce2e01803 | ||
|
|
ab25d6c5ab | ||
|
|
1b0dc0cfb0 | ||
|
|
33e484a847 | ||
|
|
67bebc0491 | ||
|
|
9186a74ae3 | ||
|
|
67d9a9819e | ||
|
|
16b88a29db | ||
|
|
9783793514 | ||
|
|
93de398a09 | ||
|
|
23e97a4284 | ||
|
|
9bb906551e | ||
|
|
b1ad82cef9 | ||
|
|
09c9f2930c | ||
|
|
74efbd7a61 | ||
|
|
1b1e7d9bce | ||
|
|
dc98fc4839 | ||
|
|
a815f97c7e | ||
|
|
8261cf2811 | ||
|
|
f2238935b5 | ||
|
|
5f12f76ada | ||
|
|
5a9464697f | ||
|
|
f44601a82b | ||
|
|
3d3de4a884 | ||
|
|
c02ad56b6d | ||
|
|
ea7c5ccd58 | ||
|
|
7ec3eae72b | ||
|
|
edf7d18308 | ||
|
|
6bf4a4e91d | ||
|
|
5fd966f042 | ||
|
|
e275f20055 | ||
|
|
ff624a34d8 | ||
|
|
c98050224c | ||
|
|
5bee373091 | ||
|
|
db4ab34ddf | ||
|
|
ef138fb5aa | ||
|
|
13bfdaed68 | ||
|
|
ff656f4e6b | ||
|
|
a27b78cef0 | ||
|
|
4fa426ace0 | ||
|
|
ac34f524fa | ||
|
|
31a4f2a4ec | ||
|
|
dc908512f9 | ||
|
|
ae983645d1 | ||
|
|
3514b22191 | ||
|
|
405af3af5f | ||
|
|
a6d333c07a | ||
|
|
0387cdc888 | ||
|
|
f670f39dd2 | ||
|
|
7262465777 | ||
|
|
75b4049529 | ||
|
|
ac6185424c | ||
|
|
9e15df8e3d | ||
|
|
83f83c17eb |
28
.github/workflows/ci.yml
vendored
28
.github/workflows/ci.yml
vendored
@@ -88,17 +88,17 @@ jobs:
|
||||
run: |
|
||||
uname -a
|
||||
xcode-select -p
|
||||
sudo xcode-select -s /Applications/Xcode_16.0.app/Contents/Developer
|
||||
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
|
||||
xcodebuild -version
|
||||
- name: setup-cocoapods
|
||||
uses: maxim-lobanov/setup-cocoapods@v1
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
podfile-path: ios/Podfile.lock
|
||||
ruby-version: '3.4'
|
||||
bundler-cache: true
|
||||
- run: npx react-native info
|
||||
- name: Install Pods
|
||||
run: |
|
||||
pod --version
|
||||
cd ios
|
||||
pod install --repo-update --deployment
|
||||
working-directory: ./ios
|
||||
run: bundle exec pod install --repo-update --deployment
|
||||
- run: npx react-native bundle --entry-file react/index.native.js --platform ios --bundle-output /tmp/ios.bundle --reset-cache
|
||||
android-sdk-build:
|
||||
name: Build mobile SDK (Android)
|
||||
@@ -137,17 +137,17 @@ jobs:
|
||||
run: |
|
||||
uname -a
|
||||
xcode-select -p
|
||||
sudo xcode-select -s /Applications/Xcode_16.0.app/Contents/Developer
|
||||
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
|
||||
xcodebuild -version
|
||||
- name: setup-cocoapods
|
||||
uses: maxim-lobanov/setup-cocoapods@v1
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
podfile-path: ios/Podfile.lock
|
||||
ruby-version: '3.4'
|
||||
bundler-cache: true
|
||||
- run: npx react-native info
|
||||
- name: Install Pods
|
||||
run: |
|
||||
pod --version
|
||||
cd ios
|
||||
pod install --repo-update --deployment
|
||||
working-directory: ./ios
|
||||
run: bundle exec pod install --repo-update --deployment
|
||||
- run: |
|
||||
xcodebuild clean \
|
||||
-workspace ios/jitsi-meet.xcworkspace \
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -62,6 +62,7 @@ buck-out/
|
||||
|
||||
# fastlane
|
||||
#
|
||||
.bundle/
|
||||
**/fastlane/report.xml
|
||||
**/fastlane/Preview.html
|
||||
**/fastlane/test_output
|
||||
|
||||
16
Gemfile
Normal file
16
Gemfile
Normal file
@@ -0,0 +1,16 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
ruby ">= 3.4.0"
|
||||
|
||||
gem "cocoapods", "~> 1.16"
|
||||
|
||||
# (Optional) Fastlane for automation
|
||||
gem "fastlane"
|
||||
gem "abbrev"
|
||||
gem "logger"
|
||||
gem "mutex_m"
|
||||
gem "csv"
|
||||
gem "bigdecimal"
|
||||
|
||||
# (Optional) Bundler itself to ensure consistency
|
||||
gem "bundler"
|
||||
331
Gemfile.lock
Normal file
331
Gemfile.lock
Normal file
@@ -0,0 +1,331 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
abbrev (0.1.2)
|
||||
activesupport (7.2.2.1)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.1)
|
||||
aws-partitions (1.1050.0)
|
||||
aws-sdk-core (3.218.1)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.98.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.181.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.11.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
benchmark (0.4.0)
|
||||
bigdecimal (3.1.9)
|
||||
claide (1.1.0)
|
||||
cocoapods (1.16.2)
|
||||
addressable (~> 2.8)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.16.2)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 2.1, < 3.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.6.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (>= 2.3.0, < 3.0)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.8.0)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (>= 2.3.0, < 3.0)
|
||||
xcodeproj (>= 1.27.0, < 2.0)
|
||||
cocoapods-core (1.16.2)
|
||||
activesupport (>= 5.0, < 8)
|
||||
addressable (~> 2.8)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
netrc (~> 0.11)
|
||||
public_suffix (~> 4.0)
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.5)
|
||||
cocoapods-downloader (2.1)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.1)
|
||||
cocoapods-trunk (1.6.0)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.2.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.0)
|
||||
csv (3.3.2)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
drb (2.2.1)
|
||||
emoji_regex (3.2.3)
|
||||
escape (0.0.4)
|
||||
ethon (0.16.0)
|
||||
ffi (>= 1.15.0)
|
||||
excon (0.112.0)
|
||||
faraday (1.10.4)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.1.0)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.4.0)
|
||||
fastlane (2.226.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
fastlane-sirp (>= 1.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.5)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (~> 3)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.4.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
ffi (1.17.1)
|
||||
ffi (1.17.1-aarch64-linux-gnu)
|
||||
ffi (1.17.1-aarch64-linux-musl)
|
||||
ffi (1.17.1-arm-linux-gnu)
|
||||
ffi (1.17.1-arm-linux-musl)
|
||||
ffi (1.17.1-arm64-darwin)
|
||||
ffi (1.17.1-x86-linux-gnu)
|
||||
ffi (1.17.1-x86-linux-musl)
|
||||
ffi (1.17.1-x86_64-darwin)
|
||||
ffi (1.17.1-x86_64-linux-gnu)
|
||||
ffi (1.17.1-x86_64-linux-musl)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.3)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.31.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.7.1)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.4.0)
|
||||
google-cloud-storage (1.47.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.31.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.8.1)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.8)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.10.1)
|
||||
jwt (2.10.1)
|
||||
base64
|
||||
logger (1.6.6)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.25.4)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.1)
|
||||
netrc (0.11.0)
|
||||
nkf (0.2.0)
|
||||
optparse (0.6.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.2)
|
||||
public_suffix (4.0.7)
|
||||
rake (13.2.1)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.4.1)
|
||||
rouge (3.28.0)
|
||||
ruby-macho (2.5.1)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.4.1)
|
||||
securerandom (0.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.19.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
sysrandom (1.0.5)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.2)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
typhoeus (1.4.1)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.27.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.4.0)
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.4.0)
|
||||
rouge (~> 3.28.0)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux-gnu
|
||||
aarch64-linux-musl
|
||||
arm-linux-gnu
|
||||
arm-linux-musl
|
||||
arm64-darwin
|
||||
ruby
|
||||
x86-linux-gnu
|
||||
x86-linux-musl
|
||||
x86_64-darwin
|
||||
x86_64-linux-gnu
|
||||
x86_64-linux-musl
|
||||
|
||||
DEPENDENCIES
|
||||
abbrev
|
||||
bigdecimal
|
||||
bundler
|
||||
cocoapods (~> 1.16)
|
||||
csv
|
||||
fastlane
|
||||
logger
|
||||
mutex_m
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.4.2p28
|
||||
|
||||
BUNDLED WITH
|
||||
2.6.3
|
||||
@@ -42,7 +42,7 @@ ext {
|
||||
googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild
|
||||
|
||||
//React Native and Hermes Version
|
||||
rnVersion = "0.75.4"
|
||||
rnVersion = "0.75.5"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -69,10 +69,29 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
// Due to a dependency conflict between React Native and the Fresco library used by GiphySDK,
|
||||
// GIFs appear as static images instead of animating
|
||||
// https://github.com/Giphy/giphy-react-native-sdk/commit/7fe466ed6fddfaec95f9cbc959d33bd75ad8f900
|
||||
|
||||
configurations.configureEach {
|
||||
resolutionStrategy {
|
||||
forcedModules = [
|
||||
'com.facebook.fresco:fresco:3.2.0',
|
||||
'com.facebook.fresco:animated-gif:3.2.0',
|
||||
'com.facebook.fresco:animated-base:3.2.0',
|
||||
'com.facebook.fresco:animated-drawable:3.2.0',
|
||||
'com.facebook.fresco:animated-webp:3.2.0',
|
||||
'com.facebook.fresco:webpsupport:3.2.0',
|
||||
'com.facebook.fresco:imagepipeline-okhttp3:3.2.0',
|
||||
'com.facebook.fresco:middleware:3.2.0',
|
||||
'com.facebook.fresco:nativeimagetranscoder:3.2.0'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Third-party react-native modules which Jitsi Meet SDK for Android depends
|
||||
// on and which are not available in third-party Maven repositories need to
|
||||
// be deployed in a Maven repository of ours.
|
||||
//
|
||||
|
||||
if (project.name.startsWith('react-native-')) {
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
@@ -78,7 +78,11 @@ public class BroadcastAction {
|
||||
SEND_CHAT_MESSAGE("org.jitsi.meet.SEND_CHAT_MESSAGE"),
|
||||
SET_VIDEO_MUTED("org.jitsi.meet.SET_VIDEO_MUTED"),
|
||||
SET_CLOSED_CAPTIONS_ENABLED("org.jitsi.meet.SET_CLOSED_CAPTIONS_ENABLED"),
|
||||
TOGGLE_CAMERA("org.jitsi.meet.TOGGLE_CAMERA");
|
||||
TOGGLE_CAMERA("org.jitsi.meet.TOGGLE_CAMERA"),
|
||||
SHOW_NOTIFICATION("org.jitsi.meet.SHOW_NOTIFICATION"),
|
||||
HIDE_NOTIFICATION("org.jitsi.meet.HIDE_NOTIFICATION"),
|
||||
START_RECORDING("org.jitsi.meet.START_RECORDING"),
|
||||
STOP_RECORDING("org.jitsi.meet.STOP_RECORDING");
|
||||
|
||||
private final String action;
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class BroadcastIntentHelper {
|
||||
public static Intent buildSetAudioMutedIntent(boolean muted) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SET_AUDIO_MUTED.getAction());
|
||||
intent.putExtra("muted", muted);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
@@ -17,18 +19,21 @@ public class BroadcastIntentHelper {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SEND_ENDPOINT_TEXT_MESSAGE.getAction());
|
||||
intent.putExtra("to", to);
|
||||
intent.putExtra("message", message);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildToggleScreenShareIntent(boolean enabled) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.TOGGLE_SCREEN_SHARE.getAction());
|
||||
intent.putExtra("enabled", enabled);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildOpenChatIntent(String participantId) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.OPEN_CHAT.getAction());
|
||||
intent.putExtra("to", participantId);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
@@ -40,28 +45,98 @@ public class BroadcastIntentHelper {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SEND_CHAT_MESSAGE.getAction());
|
||||
intent.putExtra("to", participantId);
|
||||
intent.putExtra("message", message);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildSetVideoMutedIntent(boolean muted) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SET_VIDEO_MUTED.getAction());
|
||||
intent.putExtra("muted", muted);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildSetClosedCaptionsEnabledIntent(boolean enabled) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SET_CLOSED_CAPTIONS_ENABLED.getAction());
|
||||
intent.putExtra("enabled", enabled);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
|
||||
public static Intent buildRetrieveParticipantsInfo(String requestId) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.RETRIEVE_PARTICIPANTS_INFO.getAction());
|
||||
intent.putExtra("requestId", requestId);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildToggleCameraIntent() {
|
||||
return new Intent(BroadcastAction.Type.TOGGLE_CAMERA.getAction());
|
||||
}
|
||||
|
||||
public static Intent buildShowNotificationIntent(
|
||||
String appearance, String description, String timeout, String title, String uid) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SHOW_NOTIFICATION.getAction());
|
||||
intent.putExtra("appearance", appearance);
|
||||
intent.putExtra("description", description);
|
||||
intent.putExtra("timeout", timeout);
|
||||
intent.putExtra("title", title);
|
||||
intent.putExtra("uid", uid);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildHideNotificationIntent(String uid) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.HIDE_NOTIFICATION.getAction());
|
||||
intent.putExtra("uid", uid);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public enum RecordingMode {
|
||||
FILE("file"),
|
||||
STREAM("stream");
|
||||
|
||||
private final String mode;
|
||||
|
||||
RecordingMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
public static Intent buildStartRecordingIntent(
|
||||
RecordingMode mode,
|
||||
String dropboxToken,
|
||||
boolean shouldShare,
|
||||
String rtmpStreamKey,
|
||||
String rtmpBroadcastID,
|
||||
String youtubeStreamKey,
|
||||
String youtubeBroadcastID,
|
||||
Bundle extraMetadata,
|
||||
boolean transcription) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.START_RECORDING.getAction());
|
||||
intent.putExtra("mode", mode.getMode());
|
||||
intent.putExtra("dropboxToken", dropboxToken);
|
||||
intent.putExtra("shouldShare", shouldShare);
|
||||
intent.putExtra("rtmpStreamKey", rtmpStreamKey);
|
||||
intent.putExtra("rtmpBroadcastID", rtmpBroadcastID);
|
||||
intent.putExtra("youtubeStreamKey", youtubeStreamKey);
|
||||
intent.putExtra("youtubeBroadcastID", youtubeBroadcastID);
|
||||
intent.putExtra("extraMetadata", extraMetadata);
|
||||
intent.putExtra("transcription", transcription);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildStopRecordingIntent(RecordingMode mode, boolean transcription) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.STOP_RECORDING.getAction());
|
||||
intent.putExtra("mode", mode.getMode());
|
||||
intent.putExtra("transcription", transcription);
|
||||
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,10 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
constants.put("SET_VIDEO_MUTED", BroadcastAction.Type.SET_VIDEO_MUTED.getAction());
|
||||
constants.put("SET_CLOSED_CAPTIONS_ENABLED", BroadcastAction.Type.SET_CLOSED_CAPTIONS_ENABLED.getAction());
|
||||
constants.put("TOGGLE_CAMERA", BroadcastAction.Type.TOGGLE_CAMERA.getAction());
|
||||
constants.put("SHOW_NOTIFICATION", BroadcastAction.Type.SHOW_NOTIFICATION.getAction());
|
||||
constants.put("HIDE_NOTIFICATION", BroadcastAction.Type.HIDE_NOTIFICATION.getAction());
|
||||
constants.put("START_RECORDING", BroadcastAction.Type.START_RECORDING.getAction());
|
||||
constants.put("STOP_RECORDING", BroadcastAction.Type.STOP_RECORDING.getAction());
|
||||
|
||||
return constants;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,9 @@ let room;
|
||||
|
||||
/*
|
||||
* Logic to open a desktop picker put on the window global for
|
||||
* lib-jitsi-meet to detect and invoke
|
||||
* lib-jitsi-meet to detect and invoke.
|
||||
*
|
||||
* TODO: remove once the Electron SDK supporting gDM has been out for a while.
|
||||
*/
|
||||
window.JitsiMeetScreenObtainer = {
|
||||
openDesktopPicker(options, onSourceChoose) {
|
||||
@@ -287,7 +289,7 @@ class ConferenceConnector {
|
||||
},
|
||||
descriptionKey: 'dialog.reservationErrorMsg',
|
||||
titleKey: 'dialog.reservationError'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -295,7 +297,7 @@ class ConferenceConnector {
|
||||
APP.store.dispatch(showErrorNotification({
|
||||
descriptionKey: 'dialog.gracefulShutdown',
|
||||
titleKey: 'dialog.serviceUnavailable'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
break;
|
||||
|
||||
// FIXME FOCUS_DISCONNECTED is a confusing event name.
|
||||
|
||||
@@ -600,6 +600,7 @@ var config = {
|
||||
// short: 2500,
|
||||
// medium: 5000,
|
||||
// long: 10000,
|
||||
// extraLong: 60000,
|
||||
// },
|
||||
|
||||
// // Options for the recording limit notification.
|
||||
@@ -1857,6 +1858,9 @@ var config = {
|
||||
|
||||
// Hide login button on auth dialog, you may want to enable this if you are using JWT tokens to authenticate users
|
||||
// hideLoginButton: true,
|
||||
|
||||
// If true remove the tint foreground on focused user camera in filmstrip
|
||||
// disableCameraTintForeground: false,
|
||||
};
|
||||
|
||||
// Set the default values for JaaS customers
|
||||
|
||||
20
ios/Podfile
20
ios/Podfile
@@ -78,8 +78,6 @@ target 'JitsiMeetSDKLite' do
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
|
||||
PLIST_BUDDY_PATH = '/usr/libexec/PlistBuddy'
|
||||
react_native_post_install(
|
||||
installer,
|
||||
use_native_modules![:reactNativePath],
|
||||
@@ -98,23 +96,5 @@ post_install do |installer|
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1'
|
||||
config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -no-verify-emitted-module-interface'
|
||||
end
|
||||
|
||||
# Can be removed when updated to RN 0.76
|
||||
# Issue https://github.com/facebook/react-native/issues/35863#issuecomment-1387465588
|
||||
if target.name == "hermes-engine"
|
||||
installer.pods_project.files.each do |fileref|
|
||||
if fileref.path.end_with? "hermes.xcframework"
|
||||
hermes_plist_file = "#{fileref.real_path}/Info.plist"
|
||||
# Patch Hermes to remove the debug symbols entry from the Info.plist (as it's not shipped with it)
|
||||
# This might be removed once Hermes starts to ship with Debug symbols or we remove our
|
||||
# direct dependency from the Main iOS target on "hermes.xcframework"
|
||||
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:0:DebugSymbolsPath', hermes_plist_file)
|
||||
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:1:DebugSymbolsPath', hermes_plist_file)
|
||||
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:2:DebugSymbolsPath', hermes_plist_file)
|
||||
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:3:DebugSymbolsPath', hermes_plist_file)
|
||||
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:4:DebugSymbolsPath', hermes_plist_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
578
ios/Podfile.lock
578
ios/Podfile.lock
File diff suppressed because it is too large
Load Diff
@@ -40,7 +40,7 @@
|
||||
jitsiMeet.defaultConferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
|
||||
|
||||
// For testing configOverrides a room needs to be set
|
||||
// builder.room = @"test0988test";
|
||||
// builder.room = @"https://meet.jit.si/test0988test";
|
||||
|
||||
[builder setFeatureFlag:@"welcomepage.enabled" withBoolean:YES];
|
||||
[builder setFeatureFlag:@"ios.screensharing.enabled" withBoolean:YES];
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
|
||||
static NSString * const sendEventNotificationName = @"org.jitsi.meet.SendEvent";
|
||||
|
||||
typedef NS_ENUM(NSInteger, RecordingMode) {
|
||||
RecordingModeFile,
|
||||
RecordingModeStream
|
||||
};
|
||||
|
||||
@interface ExternalAPI : RCTEventEmitter<RCTBridgeModule>
|
||||
|
||||
- (void)sendHangUp;
|
||||
@@ -27,9 +32,13 @@ static NSString * const sendEventNotificationName = @"org.jitsi.meet.SendEvent";
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completion;
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to ;
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to;
|
||||
- (void)sendSetVideoMuted:(BOOL)muted;
|
||||
- (void)sendSetClosedCaptionsEnabled:(BOOL)enabled;
|
||||
- (void)toggleCamera;
|
||||
- (void)showNotification:(NSString*)appearance :(NSString*)description :(NSString*)timeout :(NSString*)title :(NSString*)uid;
|
||||
- (void)hideNotification:(NSString*)uid;
|
||||
- (void)startRecording:(RecordingMode)mode :(NSString*)dropboxToken :(BOOL)shouldShare :(NSString*)rtmpStreamKey :(NSString*)rtmpBroadcastID :(NSString*)youtubeStreamKey :(NSString*)youtubeBroadcastID :(NSDictionary*)extraMetadata :(BOOL)transcription;
|
||||
- (void)stopRecording:(RecordingMode)mode :(BOOL)transcription;
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,6 +28,10 @@ static NSString * const sendChatMessageAction = @"org.jitsi.meet.SEND_CHAT_MESSA
|
||||
static NSString * const setVideoMutedAction = @"org.jitsi.meet.SET_VIDEO_MUTED";
|
||||
static NSString * const setClosedCaptionsEnabledAction = @"org.jitsi.meet.SET_CLOSED_CAPTIONS_ENABLED";
|
||||
static NSString * const toggleCameraAction = @"org.jitsi.meet.TOGGLE_CAMERA";
|
||||
static NSString * const showNotificationAction = @"org.jitsi.meet.SHOW_NOTIFICATION";
|
||||
static NSString * const hideNotificationAction = @"org.jitsi.meet.HIDE_NOTIFICATION";
|
||||
static NSString * const startRecordingAction = @"org.jitsi.meet.START_RECORDING";
|
||||
static NSString * const stopRecordingAction = @"org.jitsi.meet.STOP_RECORDING";
|
||||
|
||||
@implementation ExternalAPI
|
||||
|
||||
@@ -52,7 +56,11 @@ RCT_EXPORT_MODULE();
|
||||
@"SEND_CHAT_MESSAGE": sendChatMessageAction,
|
||||
@"SET_VIDEO_MUTED" : setVideoMutedAction,
|
||||
@"SET_CLOSED_CAPTIONS_ENABLED": setClosedCaptionsEnabledAction,
|
||||
@"TOGGLE_CAMERA": toggleCameraAction
|
||||
@"TOGGLE_CAMERA": toggleCameraAction,
|
||||
@"SHOW_NOTIFICATION": showNotificationAction,
|
||||
@"HIDE_NOTIFICATION": hideNotificationAction,
|
||||
@"START_RECORDING": startRecordingAction,
|
||||
@"STOP_RECORDING": stopRecordingAction
|
||||
};
|
||||
};
|
||||
|
||||
@@ -78,7 +86,11 @@ RCT_EXPORT_MODULE();
|
||||
sendChatMessageAction,
|
||||
setVideoMutedAction,
|
||||
setClosedCaptionsEnabledAction,
|
||||
toggleCameraAction
|
||||
toggleCameraAction,
|
||||
showNotificationAction,
|
||||
hideNotificationAction,
|
||||
startRecordingAction,
|
||||
stopRecordingAction
|
||||
];
|
||||
}
|
||||
|
||||
@@ -180,4 +192,59 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
[self sendEventWithName:toggleCameraAction body:nil];
|
||||
}
|
||||
|
||||
- (void)showNotification:(NSString*)appearance :(NSString*)description :(NSString*)timeout :(NSString*)title :(NSString*)uid {
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"appearance"] = appearance;
|
||||
data[@"description"] = description;
|
||||
data[@"timeout"] = timeout;
|
||||
data[@"title"] = title;
|
||||
data[@"uid"] = uid;
|
||||
|
||||
[self sendEventWithName:showNotificationAction body:data];
|
||||
}
|
||||
|
||||
- (void)hideNotification:(NSString*)uid {
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"uid"] = uid;
|
||||
|
||||
[self sendEventWithName:hideNotificationAction body:data];
|
||||
}
|
||||
|
||||
static inline NSString *RecordingModeToString(RecordingMode mode) {
|
||||
switch (mode) {
|
||||
case RecordingModeFile:
|
||||
return @"file";
|
||||
case RecordingModeStream:
|
||||
return @"stream";
|
||||
default:
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startRecording:(RecordingMode)mode :(NSString*)dropboxToken :(BOOL)shouldShare :(NSString*)rtmpStreamKey :(NSString*)rtmpBroadcastID :(NSString*)youtubeStreamKey :(NSString*)youtubeBroadcastID :(NSDictionary*)extraMetadata :(BOOL)transcription {
|
||||
NSString *modeString = RecordingModeToString(mode);
|
||||
NSDictionary *data = @{
|
||||
@"mode": modeString,
|
||||
@"dropboxToken": dropboxToken,
|
||||
@"shouldShare": @(shouldShare),
|
||||
@"rtmpStreamKey": rtmpStreamKey,
|
||||
@"rtmpBroadcastID": rtmpBroadcastID,
|
||||
@"youtubeStreamKey": youtubeStreamKey,
|
||||
@"youtubeBroadcastID": youtubeBroadcastID,
|
||||
@"extraMetadata": extraMetadata,
|
||||
@"transcription": @(transcription)
|
||||
};
|
||||
|
||||
[self sendEventWithName:startRecordingAction body:data];
|
||||
}
|
||||
|
||||
- (void)stopRecording:(RecordingMode)mode :(BOOL)transcription {
|
||||
NSString *modeString = RecordingModeToString(mode);
|
||||
NSDictionary *data = @{
|
||||
@"mode": modeString,
|
||||
@"transcription": @(transcription)
|
||||
};
|
||||
|
||||
[self sendEventWithName:stopRecordingAction body:data];
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#import "JitsiMeetConferenceOptions.h"
|
||||
#import "JitsiMeetViewDelegate.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, RecordingMode);
|
||||
|
||||
@interface JitsiMeetView : UIView
|
||||
|
||||
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
||||
@@ -47,5 +49,9 @@
|
||||
- (void)setVideoMuted:(BOOL)muted;
|
||||
- (void)setClosedCaptionsEnabled:(BOOL)enabled;
|
||||
- (void)toggleCamera;
|
||||
- (void)showNotification:(NSString * _Nonnull)appearance :(NSString * _Nullable)description :(NSString * _Nullable)timeout :(NSString * _Nullable)title :(NSString * _Nullable)uid;
|
||||
- (void)hideNotification:(NSString * _Nullable)uid;
|
||||
- (void)startRecording:(RecordingMode)mode :(NSString * _Nullable)dropboxToken :(BOOL)shouldShare :(NSString * _Nullable)rtmpStreamKey :(NSString * _Nullable)rtmpBroadcastID :(NSString * _Nullable)youtubeStreamKey :(NSString * _Nullable)youtubeBroadcastID :(NSString * _Nullable)extraMetadata :(BOOL)transcription;
|
||||
- (void)stopRecording:(RecordingMode)mode :(BOOL)transcription;
|
||||
|
||||
@end
|
||||
|
||||
@@ -143,6 +143,26 @@ static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
|
||||
[externalAPI toggleCamera];
|
||||
}
|
||||
|
||||
- (void)showNotification:(NSString *)appearance :(NSString *)description :(NSString *)timeout :(NSString *)title :(NSString *)uid {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI showNotification:appearance :description :timeout :title :uid];
|
||||
}
|
||||
|
||||
-(void)hideNotification:(NSString *)uid {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI hideNotification:uid];
|
||||
}
|
||||
|
||||
- (void)startRecording:(RecordingMode)mode :(NSString *)dropboxToken :(BOOL)shouldShare :(NSString *)rtmpStreamKey :(NSString *)rtmpBroadcastID :(NSString *)youtubeStreamKey :(NSString *)youtubeBroadcastID :(NSString *)extraMetadata :(BOOL)transcription {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI startRecording:mode :dropboxToken :shouldShare :rtmpStreamKey :rtmpBroadcastID :youtubeStreamKey :youtubeBroadcastID :extraMetadata :transcription];
|
||||
}
|
||||
|
||||
- (void)stopRecording:(RecordingMode)mode :(BOOL)transcription {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI stopRecording:mode :transcription];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
|
||||
- (void)registerObservers {
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"ml": "മലയാളം",
|
||||
"mn": "Монгол",
|
||||
"mr": "मराठी",
|
||||
"nb": "Norsk bokmål",
|
||||
"nl": "Nederlands",
|
||||
"oc": "Occitan",
|
||||
"pl": "Polski",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"failedToAdd": "Fehler beim Hinzufügen von Personen",
|
||||
"googleEmail": "Google-E-Mail",
|
||||
"inviteMoreHeader": "Sie sind alleine in der Sitzung",
|
||||
"inviteMoreMailSubject": "An {{appName}} Meeting teilnehmen",
|
||||
"inviteMoreMailSubject": "An {{appName}} Konferenz teilnehmen",
|
||||
"inviteMorePrompt": "Mehr Leute einladen",
|
||||
"linkCopied": "Link in die Zwischenablage kopiert",
|
||||
"noResults": "Keine passenden Ergebnisse",
|
||||
@@ -89,10 +89,10 @@
|
||||
"notSignedIn": "Ein Fehler ist während der Authentifizierung zur Anzeige von Kalenderterminen aufgetreten. Prüfen Sie Ihre Kalendereinstellungen oder versuchen Sie, sich erneut anzumelden."
|
||||
},
|
||||
"join": "Teilnehmen",
|
||||
"joinTooltip": "Am Meeting teilnehmen",
|
||||
"joinTooltip": "Am Konferenz teilnehmen",
|
||||
"nextMeeting": "Nächste Konferenz",
|
||||
"noEvents": "Es gibt keine bevorstehenden Termine.",
|
||||
"ongoingMeeting": "Laufendes Meeting",
|
||||
"ongoingMeeting": "Laufende Konferenz",
|
||||
"permissionButton": "Einstellungen öffnen",
|
||||
"permissionMessage": "Die App benötigt Zugriff auf den Kalender, um Termine und Konferenzen anzuzeigen.",
|
||||
"refresh": "Kalender aktualisieren",
|
||||
@@ -145,7 +145,7 @@
|
||||
"installExtensionText": "Installieren Sie die Erweiterung für die Integration von Google Calendar und Office 365"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "Eine Verbindung zu Ihrem Meeting wird hergestellt…"
|
||||
"joiningRoom": "Eine Verbindung zu Ihrer Konferenz wird hergestellt…"
|
||||
},
|
||||
"connection": {
|
||||
"ATTACHED": "Angehängt",
|
||||
@@ -215,7 +215,7 @@
|
||||
"downloadMobileApp": "Aus dem App Store herunterladen",
|
||||
"ifDoNotHaveApp": "Wenn Sie die App noch nicht haben:",
|
||||
"ifHaveApp": "Wenn Sie die App bereits haben:",
|
||||
"joinInApp": "Mit der App am Meeting teilnehmen",
|
||||
"joinInApp": "Mit der App an der Konferenz teilnehmen",
|
||||
"joinInAppNew": "Mit der App",
|
||||
"joinInBrowser": "Im Browser",
|
||||
"launchMeetingLabel": "Wie möchten Sie an der Konferenz teilnehmen?",
|
||||
@@ -258,7 +258,7 @@
|
||||
"dialog": {
|
||||
"Back": "Zurück",
|
||||
"Cancel": "Abbrechen",
|
||||
"IamHost": "Ich leite das Meeting",
|
||||
"IamHost": "Ich leite die Konferenz",
|
||||
"Ok": "OK",
|
||||
"Remove": "Entfernen",
|
||||
"Share": "Teilen",
|
||||
@@ -317,7 +317,7 @@
|
||||
"e2eeLabel": "Ende-zu-Ende-Verschlüsselung aktivieren",
|
||||
"e2eeWarning": "WARNUNG: Nicht alle Personen dieser Konferenz scheinen Ende-zu-Ende-Verschlüsselung zu unterstützen. Wenn Sie diese aktivieren, können die entsprechenden Personen nichts mehr sehen oder hören.",
|
||||
"e2eeWillDisableDueToMaxModeDescription": "WARNUNG: Ende-zu-Ende-Verschlüsselung wird automatisch deaktiviert, wenn weitere Anwesende an der Konferenz teilnehmen.",
|
||||
"embedMeeting": "Besprechung einbetten",
|
||||
"embedMeeting": "Konferenz einbetten",
|
||||
"enterDisplayName": "Bitte geben Sie hier Ihren Namen ein",
|
||||
"error": "Fehler",
|
||||
"errorRoomCreationRestriction": "Sie haben versucht, zu schnell beizutreten, bitte versuchen Sie es gleich noch einmal.",
|
||||
@@ -334,7 +334,8 @@
|
||||
"kickParticipantButton": "Entfernen",
|
||||
"kickParticipantDialog": "Wollen Sie diese Person wirklich entfernen?",
|
||||
"kickParticipantTitle": "Person entfernen?",
|
||||
"kickTitle": "Autsch! {{participantDisplayName}} hat Sie aus dem Meeting geworfen",
|
||||
"kickSystemTitle": "Autsch! Sie wurden aus der Konferenz geworfen",
|
||||
"kickTitle": "Autsch! {{participantDisplayName}} hat Sie aus der Konferenz geworfen",
|
||||
"linkMeeting": "Konferenz verlinken",
|
||||
"linkMeetingTitle": "Konferenz mit Salesforce verlinken",
|
||||
"liveStreaming": "Livestreaming",
|
||||
@@ -381,7 +382,7 @@
|
||||
"muteParticipantsVideoTitle": "Die Kamera von dieser Person ausschalten?",
|
||||
"noDropboxToken": "Kein gültiges Dropbox-Token",
|
||||
"password": "Passwort",
|
||||
"passwordLabel": "Dieses Meeting wurde gesichert. Bitte geben Sie das $t(lockRoomPasswordUppercase) ein, um dem Meeting beizutreten.",
|
||||
"passwordLabel": "Diese Konferenz wurde gesichert. Bitte geben Sie das $t(lockRoomPasswordUppercase) ein, um der Konferenz beizutreten.",
|
||||
"passwordNotSupported": "Das Festlegen eines Konferenzpassworts wird nicht unterstützt.",
|
||||
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) nicht unterstützt",
|
||||
"passwordRequired": "$t(lockRoomPasswordUppercase) erforderlich",
|
||||
@@ -555,13 +556,13 @@
|
||||
"invitePhone": "Wenn Sie stattdessen per Telefon beitreten möchten, wählen sie: {{number}},,{{conferenceID}}#\n",
|
||||
"invitePhoneAlternatives": "Suchen Sie nach einer anderen Einwahlnummer ?\nEinwahlnummern der Konferenz anzeigen: {{url}}\n\n\nWenn Sie sich auch über ein Raumtelefon einwählen, nehmen Sie teil, ohne sich mit dem Ton zu verbinden: {{silentUrl}}",
|
||||
"inviteSipEndpoint": "Um mit SIP teilzunehmen, folgende Adresse nutzen: {{sipUri}}",
|
||||
"inviteTextiOSInviteUrl": "Am Meeting teilnehmen: {{inviteUrl}}.",
|
||||
"inviteTextiOSInviteUrl": "An Konferenz teilnehmen: {{inviteUrl}}.",
|
||||
"inviteTextiOSJoinSilent": "Wenn Sie über ein Konferenztelefon teilnehmen, können Sie diesen Link nutzen um ohne Ton an der Konferenz teilzunehmen: {{silentUrl}}.",
|
||||
"inviteTextiOSPersonal": "{{name}} lädt Sie zu einem Meeting ein.",
|
||||
"inviteTextiOSPersonal": "{{name}} lädt Sie zu einer Konferenz ein.",
|
||||
"inviteTextiOSPhone": "Nutzen Sie folgende Nummer um via Telefon teilzunehmen: {{number}},,{{conferenceID}}#. Wenn Sie nach einer anderen Einwahlnummer suchen, finden Sie die vollständige Liste hier: {{didUrl}}.",
|
||||
"inviteURLFirstPartGeneral": "Sie wurden zur Teilnahme an einem Meeting eingeladen.",
|
||||
"inviteURLFirstPartPersonal": "{{name}} lädt Sie zu einem Meeting ein.\n",
|
||||
"inviteURLSecondPart": "\nAm Meeting teilnehmen:\n{{url}}\n",
|
||||
"inviteURLFirstPartGeneral": "Sie wurden zur Teilnahme an einer Konferenz eingeladen.",
|
||||
"inviteURLFirstPartPersonal": "{{name}} lädt Sie zu einer Konferenz ein.\n",
|
||||
"inviteURLSecondPart": "\nAm Konferenz teilnehmen:\n{{url}}\n",
|
||||
"label": "Einwahlinformationen",
|
||||
"liveStreamURL": "Livestream:",
|
||||
"moreNumbers": "Weitere Telefonnummern",
|
||||
@@ -575,7 +576,7 @@
|
||||
"sip": "SIP-Adresse",
|
||||
"sipAudioOnly": "SIP-Adresse (nur Ton)",
|
||||
"title": "Teilen",
|
||||
"tooltip": "Freigabe-Link und Einwahlinformationen für dieses Meeting",
|
||||
"tooltip": "Freigabe-Link und Einwahlinformationen für diese Konferenz",
|
||||
"upgradeOptions": "Bitte prüfen Sie Ihre Upgrade-Optionen auf",
|
||||
"whiteboardError": "Whiteboard konnte nicht geladen werden. Bitte versuchen Sie es später erneut."
|
||||
},
|
||||
@@ -627,7 +628,7 @@
|
||||
"errorAPI": "Beim Abrufen der YouTube-Livestreams ist ein Fehler aufgetreten. Bitte versuchen Sie, sich erneut anzumelden.",
|
||||
"errorLiveStreamNotEnabled": "Livestreaming ist für {{email}} nicht aktiviert. Aktivieren Sie das Livestreaming oder melden Sie sich bei einem Konto mit aktiviertem Livestreaming an.",
|
||||
"expandedOff": "Livestream wurde angehalten",
|
||||
"expandedOn": "Das Meeting wird momentan an YouTube gestreamt.",
|
||||
"expandedOn": "Die Konferenz wird momentan an YouTube gestreamt.",
|
||||
"expandedPending": "Livestream wird gestartet …",
|
||||
"failedToStart": "Livestream konnte nicht gestartet werden",
|
||||
"getStreamKeyManually": "Wir waren nicht in der Lage, Livestreams abzurufen. Versuchen Sie, Ihren Livestream-Schlüssel von YouTube zu erhalten.",
|
||||
@@ -731,14 +732,17 @@
|
||||
"me": "ich",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "Sicherheitslücke!",
|
||||
"allowAction": "Erlauben",
|
||||
"allowAudio": "Mikrofon einschalten",
|
||||
"allowBoth": "Beides",
|
||||
"allowVideo": "Kamera einschalten",
|
||||
"allowedUnmute": "Sie können die Stummschaltung aufheben, Ihre Kamera einschalten oder Ihren Bildschirm teilen.",
|
||||
"audioUnmuteBlockedDescription": "Díe Stummschaltung kann aus Überlastungsschutzgründen temporär nicht aufgehoben werden.",
|
||||
"audioUnmuteBlockedTitle": "Stummschaltung kann nicht aufgehoben werden!",
|
||||
"chatMessages": "Chatnachrichten",
|
||||
"connectedOneMember": "{{name}} nimmt am Meeting teil",
|
||||
"connectedThreePlusMembers": "{{name}} und {{count}} andere Personen nehmen am Meeting teil",
|
||||
"connectedTwoMembers": "{{first}} und {{second}} nehmen am Meeting teil",
|
||||
"connectedOneMember": "{{name}} nimmt an der Konferenz teil",
|
||||
"connectedThreePlusMembers": "{{name}} und {{count}} andere Personen nehmen an der Konferenz teil",
|
||||
"connectedTwoMembers": "{{first}} und {{second}} nehmen an der Konferenz teil",
|
||||
"connectionFailed": "Verbindung fehlgeschlagen. Bitte versuchen Sie es später noch einmal.",
|
||||
"dataChannelClosed": "Schlechte Videoqualität",
|
||||
"dataChannelClosedDescription": "Die Steuerungsverbindung (Bridge Channel) wurde unterbrochen, daher ist die Videoqulität auf die schlechteste Stufe limitiert.",
|
||||
"dataChannelClosedDescriptionWithAudio": "Die Steuerungsverbindung (Bridge Channel) wurde unterbrochen, daher können Video- und Tonprobleme auftreten.",
|
||||
@@ -753,6 +757,9 @@
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "Benachrichtigungen",
|
||||
"hostAskedUnmute": "Die Moderation bittet Sie, das Mikrofon zu aktivieren",
|
||||
"invalidTenant": "Ungültiger Tenant",
|
||||
"invalidTenantHyphenDescription": "Der von Ihnen genutzte Tenantname ist unfültig (beginnt oder endet mit '-').",
|
||||
"invalidTenantLengthDescription": "Der von Ihnen genutzte Tenantname ist zu lang.",
|
||||
"invitedOneMember": "{{name}} wurde eingeladen",
|
||||
"invitedThreePlusMembers": "{{name}} und {{count}} andere wurden eingeladen",
|
||||
"invitedTwoMembers": "{{first}} und {{second}} wurden eingeladen",
|
||||
@@ -783,7 +790,7 @@
|
||||
"moderationToggleDescription": "von {{participantDisplayName}}",
|
||||
"moderator": "Moderationsrechte vergeben!",
|
||||
"muted": "Der Konferenz wurde stumm beigetreten.",
|
||||
"mutedRemotelyDescription": "Sie können jederzeit die Stummschaltung aufheben, wenn Sie bereit sind zu sprechen. Wenn Sie fertig sind, können Sie sich wieder stummschalten, um Geräusche vom Meeting fernzuhalten.",
|
||||
"mutedRemotelyDescription": "Sie können jederzeit die Stummschaltung aufheben, wenn Sie bereit sind zu sprechen. Wenn Sie fertig sind, können Sie sich wieder stummschalten, um Geräusche von der Konferenz fernzuhalten.",
|
||||
"mutedRemotelyTitle": "Sie wurden von {{participantDisplayName}} stummgeschaltet!",
|
||||
"mutedTitle": "Stummschaltung aktiv!",
|
||||
"newDeviceAction": "Verwenden",
|
||||
@@ -811,7 +818,7 @@
|
||||
"screenSharingAudioOnlyTitle": "Modus \"Beste Leistung\"",
|
||||
"selfViewTitle": "Sie können die eigene Ansicht immer in den Einstellungen reaktivieren",
|
||||
"somebody": "Jemand",
|
||||
"startSilentDescription": "Treten Sie dem Meeting noch einmal bei, um Ihr Audio zu aktivieren",
|
||||
"startSilentDescription": "Treten Sie der Konferenz noch einmal bei, um Ihr Audio zu aktivieren",
|
||||
"startSilentTitle": "Sie sind ohne Audioausgabe beigetreten!",
|
||||
"suboptimalBrowserWarning": "Tut uns leid, aber die Konferenz wird mit {{appName}} kein großartiges Erlebnis. Wir versuchen immer die Situation zu verbessern, bis dahin empfehlen wir aber die Verwendung einer der <a href=\"{{recommendedBrowserPageLink}}\" target=\"_blank\">vollständig unterstützen Browser</a>.",
|
||||
"suboptimalExperienceTitle": "Browserwarnung",
|
||||
@@ -819,6 +826,7 @@
|
||||
"suggestRecordingDescription": "Möchten Sie eine Aufzeichnung starten?",
|
||||
"suggestRecordingTitle": "Konferenz aufzeichnen",
|
||||
"unmute": "Stummschaltung aufheben",
|
||||
"unmuteVideo": "Kamera einschalten",
|
||||
"videoMutedRemotelyDescription": "Sie können sie jederzeit wieder einschalten.",
|
||||
"videoMutedRemotelyTitle": "Ihre Kamera wurde von {{participantDisplayName}} ausgeschaltet!",
|
||||
"videoUnmuteBlockedDescription": "Die Kamera und Bildschirmfreigabe kann aus Überlastungsschutzgründen temporär nicht eingeschaltet werden.",
|
||||
@@ -1022,7 +1030,7 @@
|
||||
"error": "Die Aufzeichnung ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"errorFetchingLink": "Der Link zur Aufzeichnung konnte nicht geladen werden.",
|
||||
"expandedOff": "Aufzeichnung wurde gestoppt",
|
||||
"expandedOn": "Das Meeting wird momentan aufgezeichnet.",
|
||||
"expandedOn": "Die Konferenz wird momentan aufgezeichnet.",
|
||||
"expandedPending": "Aufzeichnung wird gestartet…",
|
||||
"failedToStart": "Die Aufnahme konnte nicht gestartet werden",
|
||||
"fileSharingdescription": "Aufzeichnung mit den Personen der Konferenz teilen",
|
||||
@@ -1030,7 +1038,7 @@
|
||||
"highlightMoment": "Moment als Highlight festhalten",
|
||||
"highlightMomentDisabled": "Sie können Momente als Highlights festhalten, sobald die Aufnahme startet",
|
||||
"highlightMomentSuccess": "Highlight festgehalten",
|
||||
"highlightMomentSucessDescription": "Ihr festgehaltener Moment wird zur Zusammenfassung des Meeting hinzugefügt.",
|
||||
"highlightMomentSucessDescription": "Ihr festgehaltener Moment wird zur Zusammenfassung der Konferenz hinzugefügt.",
|
||||
"inProgress": "Aufzeichnung gestartet",
|
||||
"limitNotificationDescriptionNative": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} Min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <3>{{app}}</3>.",
|
||||
"limitNotificationDescriptionWeb": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} Min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
@@ -1050,7 +1058,8 @@
|
||||
"on": "Aufnahme",
|
||||
"onBy": "{{name}} startete die Aufnahme",
|
||||
"onlyRecordSelf": "Nur eigenes Kamerabild und Ton aufzeichnen",
|
||||
"pending": "Aufzeichnung des Meetings wird vorbereitet…",
|
||||
"pending": "Aufzeichnung der Konferenz wird vorbereitet…",
|
||||
"policyError": "Sie haben die Aufzeichnung zu früh gestartet. Bitte versuchen Sie es später noch einmal.",
|
||||
"recordAudioAndVideo": "Kamera und Ton aufzeichnen",
|
||||
"recordTranscription": "Transkription aufzeichnen",
|
||||
"saveLocalRecording": "Aufzeichnung lokal abspeichern",
|
||||
@@ -1165,8 +1174,8 @@
|
||||
"version": "Version"
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "\n\n=====\n\nWollen Sie sich nur auf Ihrem Telefon einwählen?\n\n{{defaultDialInNumber}}Klicken Sie auf diesen Link, um die eingewählten Telefonnummern für dieses Meeting zu sehen\n{{dialInfoPageUrl}}",
|
||||
"mainText": "Klicken Sie auf den folgenden Link, um dem Meeting beizutreten:\n{{roomUrl}}"
|
||||
"dialInfoText": "\n\n=====\n\nWollen Sie sich nur auf Ihrem Telefon einwählen?\n\n{{defaultDialInNumber}}Klicken Sie auf diesen Link, um die eingewählten Telefonnummern für diese Konferenz zu sehen\n{{dialInfoPageUrl}}",
|
||||
"mainText": "Klicken Sie auf den folgenden Link, um der Konferenz beizutreten:\n{{roomUrl}}"
|
||||
},
|
||||
"speaker": "Sprecher/-in",
|
||||
"speakerStats": {
|
||||
@@ -1397,7 +1406,7 @@
|
||||
"ccButtonTooltip": "Untertitel ein-/ausschalten",
|
||||
"expandedLabel": "Transkribieren ist derzeit eingeschaltet",
|
||||
"failed": "Transkribieren fehlgeschlagen",
|
||||
"labelToolTip": "Das Meeting wird transkribiert",
|
||||
"labelToolTip": "Die Konferenz wird transkribiert",
|
||||
"sourceLanguageDesc": "Aktuell ist die Sprache der Konferenz auf <b>{{sourceLanguage}}</b> eingestellt. <br/> Sie könne dies hier ",
|
||||
"sourceLanguageHere": "ändern",
|
||||
"start": "Anzeige der Untertitel starten",
|
||||
@@ -1528,15 +1537,15 @@
|
||||
},
|
||||
"calendar": "Kalender",
|
||||
"connectCalendarButton": "Kalender verbinden",
|
||||
"connectCalendarText": "Verbinden Sie Ihren Kalender, um all Ihre Meetings in {{app}} anzuzeigen. Fügen Sie zudem {{provider}}-Meetings in Ihren Kalender ein und starten Sie sie mit nur einem Klick.",
|
||||
"enterRoomTitle": "Neues Meeting starten",
|
||||
"connectCalendarText": "Verbinden Sie Ihren Kalender, um all Ihre Konferenzen in {{app}} anzuzeigen. Fügen Sie zudem {{provider}}-Konferenzen in Ihren Kalender ein und starten Sie sie mit nur einem Klick.",
|
||||
"enterRoomTitle": "Neue Konferenz starten",
|
||||
"getHelp": "Hilfe",
|
||||
"go": "Los",
|
||||
"goSmall": "Los",
|
||||
"headerSubtitle": "Sichere und hochqualitative Meetings",
|
||||
"headerSubtitle": "Sichere und hochqualitative Konferenzen",
|
||||
"headerTitle": "Jitsi Meet",
|
||||
"info": "Einwahlinformationen",
|
||||
"jitsiOnMobile": "Jitsi unterwegs – einfach unsere Apps herunterladen und Meetings von überall starten",
|
||||
"jitsiOnMobile": "Jitsi unterwegs – einfach unsere Apps herunterladen und Konferenzen von überall starten",
|
||||
"join": "ERSTELLEN / BEITRETEN",
|
||||
"logo": {
|
||||
"calendar": "Kalender Logo",
|
||||
@@ -1562,7 +1571,7 @@
|
||||
"roomnameHint": "Name oder URL der Konferenz, der Sie beitreten möchten. Sie können einen Namen erfinden, er muss nur den anderen Personen übermittelt werden, damit diese der gleichen Konferenz beitreten.",
|
||||
"sendFeedback": "Feedback senden",
|
||||
"settings": "Einstellungen",
|
||||
"startMeeting": "Meeting starten",
|
||||
"startMeeting": "Konferenz starten",
|
||||
"terms": "AGB",
|
||||
"title": "Sichere, voll funktionale und komplett kostenlose Videokonferenzen",
|
||||
"upcomingMeetings": "Ihre zukünftigen Konferenzen"
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
"alreadySharedVideoTitle": "एक समय में केवल एक साझा वीडियो की अनुमति है",
|
||||
"applicationWindow": "एप्लिकेशन विंडो",
|
||||
"authenticationRequired": "प्रमाणीकरण आवश्यक है",
|
||||
"cameraConstraintFailedError": "Your camera does not satisfy some of the required constraints.",
|
||||
"cameraConstraintFailedError": "आपका कैमरा आवश्यक बाधाओं में से कुछ को पूरा नहीं करता है।",
|
||||
"cameraNotFoundError": "कैमरा नहीं मिला।",
|
||||
"cameraNotSendingData": "हम आपके कैमरे का उपयोग करने में असमर्थ हैं। कृपया जांचें कि क्या कोई अन्य एप्लिकेशन इस डिवाइस का उपयोग तो नहीं कर रहा है, सेटिंग मेनू से किसी अन्य डिवाइस का चयन करें या एप्लिकेशन को फिर से लोड करने का प्रयास करें।",
|
||||
"cameraNotSendingDataTitle": "कैमरा उपयोग करने में असमर्थ",
|
||||
@@ -222,7 +222,7 @@
|
||||
"e2eeWarning": "चेतावनी: इस मीटिंग में सभी प्रतिभागियों के पास एंड-टू-एंड एन्क्रिप्शन के लिए समक्षता नहीं है। यदि आप इसे सक्षम करते हैं तो वे आपको देखने और सुनने में सक्षम नहीं होंगे।",
|
||||
"enterDisplayName": "कृपया यहाँ अपना नाम लिखें",
|
||||
"error": "त्रुटि",
|
||||
"gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
|
||||
"gracefulShutdown": "हमारी सेवा वर्तमान में रखरखाव के लिए बंद है। कृपया बाद में पुनः प्रयास करें।",
|
||||
"grantModeratorDialog": "क्या आप वाकई इस प्रतिभागी को एक मध्यस्थ बनाना चाहते हैं?",
|
||||
"grantModeratorTitle": "मध्यस्थ स्वीकृती दे ",
|
||||
"incorrectPassword": "गलत उपयोगकर्ता नाम या पासवर्ड",
|
||||
@@ -230,7 +230,7 @@
|
||||
"internalError": "उफ़! कुछ गड़बड़ हो गई। निम्नलिखित त्रुटि हुई: {{error}}",
|
||||
"internalErrorTitle": "आंतरिक त्रुटि",
|
||||
"kickMessage": "आप अधिक जानकारी के लिए {{participantDisplayName}} से संपर्क कर सकते हैं।",
|
||||
"kickParticipantButton": "Kick",
|
||||
"kickParticipantButton": "निकालें",
|
||||
"kickParticipantDialog": "क्या आप वाकई इस प्रतिभागी को निकलना चाहते हैं?",
|
||||
"kickParticipantTitle": "इस प्रतिभागी को निकाले?",
|
||||
"kickTitle": "अरे! {{participantDisplayName}} ने आपको मीटिंग से बाहर कर दिया",
|
||||
@@ -245,7 +245,7 @@
|
||||
"logoutTitle": "लॉग आउट ",
|
||||
"maxUsersLimitReached": "अधिकतम प्रतिभागियों की सीमा पूरी हो चुकी है. कृपया बैठक के मालिक से संपर्क करें या बाद में पुनः प्रयास करें!!",
|
||||
"maxUsersLimitReachedTitle": "अधिकतम प्रतिभागियों सीमा पार हो गई",
|
||||
"micConstraintFailedError": "Your microphone does not satisfy some of the required constraints.",
|
||||
"micConstraintFailedError": "आपका माइक्रोफ़ोन आवश्यक प्रतिबंधों को पूरा नहीं करता।",
|
||||
"micNotFoundError": "माइक्रोफोन नहीं मिला।",
|
||||
"micNotSendingData": "अपने माइक को अनम्यूट करने और इसके स्तर को समायोजित करने के लिए अपने कंप्यूटर की सेटिंग पर जाएं",
|
||||
"micNotSendingDataTitle": "आपका माइक आपकी सिस्टम सेटिंग्स द्वारा मौन है",
|
||||
@@ -285,18 +285,18 @@
|
||||
"remoteControlDeniedMessage": "{{user}} ने आपका रिमोट कंट्रोल अनुरोध अस्वीकार कर दिया!",
|
||||
"remoteControlErrorMessage": "{{user}}से रिमोट कंट्रोल की अनुमति का अनुरोध करते समय एक त्रुटि हुई!",
|
||||
"remoteControlRequestMessage": "क्या आप {{user}} को दूर से अपने डेस्कटॉप को नियंत्रित करने की अनुमति देंगे?",
|
||||
"remoteControlShareScreenWarning": "Note that if you press \"Allow\" you will share your screen!",
|
||||
"remoteControlShareScreenWarning": "ध्यान दें कि यदि आप \"अनुमति दें\" दबाते हैं, तो आप अपनी स्क्रीन साझा करेंगे!",
|
||||
"remoteControlStopMessage": "रिमोट कंट्रोल सत्र समाप्त हो गया!",
|
||||
"remoteControlTitle": "रिमोट डेस्कटॉप कंट्रोल",
|
||||
"removePassword": "निकालें $t(lockRoomPassword)",
|
||||
"removeSharedVideoMsg": "क्या आप वाकई अपने साझा किए गए वीडियो को निकालना चाहते हैं?",
|
||||
"removeSharedVideoTitle": "साझा किया गया वीडियो निकालें",
|
||||
"reservationError": "Reservation system error",
|
||||
"reservationError": "आरक्षण प्रणाली में त्रुटि",
|
||||
"reservationErrorMsg": "Error code: {{code}}, message: {{msg}}",
|
||||
"retry": "पुनः प्रयास करें",
|
||||
"screenSharingAudio": "Share audio",
|
||||
"screenSharingAudio": "ऑडियो साझा करें",
|
||||
"screenSharingFailed": "उफ़! कुछ गड़बड़ हो गई, हम स्क्रीन शेयरिंग शुरू करने में सक्षम नहीं थे!",
|
||||
"screenSharingFailedTitle": "Screen sharing failed!",
|
||||
"screenSharingFailedTitle": "स्क्रीन साझा करना विफल हुआ!",
|
||||
"screenSharingPermissionDeniedError": "उफ़! आपकी स्क्रीन शेयरिंग अनुमतियों में कुछ गड़बड़ हो गई है। कृपया पुनः लोड करें और पुनः प्रयास करें।",
|
||||
"sendPrivateMessage": "आपने हाल ही में एक निजी संदेश प्राप्त किया है। क्या आप उसका निजी रूप से जवाब देने का इरादा रखते हैं? या आप अपना संदेश समूह को भेजना चाहते हैं?",
|
||||
"sendPrivateMessageCancel": "समूह को भेजें",
|
||||
@@ -304,7 +304,7 @@
|
||||
"sendPrivateMessageTitle": "निजी तौर पर भेजें?",
|
||||
"serviceUnavailable": "सेवा अनुपलब्ध",
|
||||
"sessTerminated": "कॉल समाप्त",
|
||||
"sessionRestarted": "Call restarted because of a connection issue",
|
||||
"sessionRestarted": "कनेक्शन समस्या के कारण कॉल पुनः प्रारंभ की गई",
|
||||
"shareVideoLinkError": "कृपया एक सही यूट्यूब लिंक प्रदान करें।.",
|
||||
"shareVideoTitle": "एक वीडियो साझा करें",
|
||||
"shareYourScreen": "अपनी स्क्रीन साझा करें",
|
||||
@@ -313,10 +313,10 @@
|
||||
"startRecording": "रिकॉर्डिंग प्रारंभ करें",
|
||||
"startRemoteControlErrorMessage": "रिमोट कंट्रोल सत्र शुरू करने की कोशिश करते समय एक त्रुटि हुई!",
|
||||
"stopLiveStreaming": "लाइव स्ट्रीम बंद करें",
|
||||
"stopRecording": "Stop recording",
|
||||
"stopRecording": "रिकॉर्डिंग बंद करें",
|
||||
"stopRecordingWarning": "क्या आप वाकई रिकॉर्डिंग को रोकना चाहते हैं?",
|
||||
"stopStreamingWarning": "क्या आप वाकई लाइव स्ट्रीमिंग को रोकना चाहते हैं?",
|
||||
"streamKey": "Live stream key",
|
||||
"streamKey": "लाइव स्ट्रीम कुंजी",
|
||||
"thankYou": " {{appName}} का उपयोग करने के लिए धन्यवाद!",
|
||||
"token": "टोकन",
|
||||
"tokenAuthFailed": "क्षमा करें, आपको इस कॉल में शामिल होने की अनुमति नहीं है।",
|
||||
@@ -336,7 +336,7 @@
|
||||
"labelToolTip": "इस कॉल पर ऑडियो और वीडियो संचार एंड-टू-एंड एन्क्रिप्टेड है"
|
||||
},
|
||||
"embedMeeting": {
|
||||
"title": "Embed this meeting"
|
||||
"title": "इस बैठक को एम्बेड करें"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "औसत",
|
||||
@@ -381,7 +381,7 @@
|
||||
"moreNumbers": "अधिक संख्या",
|
||||
"noNumbers": "कोई डायल-इन नंबर नहीं।",
|
||||
"noPassword": "कोई नहीं",
|
||||
"noRoom": "No room was specified to dial-in into.",
|
||||
"noRoom": "डायल-इन करने के लिए कोई कक्ष निर्दिष्ट नहीं किया गया।",
|
||||
"numbers": "डायल-इन नंबर",
|
||||
"password": "$t(lockRoomPasswordUppercase):",
|
||||
"title": "साझा करें",
|
||||
@@ -404,11 +404,11 @@
|
||||
"keyboardShortcuts": {
|
||||
"focusLocal": "अपने वीडियो पर केंद्रित करें",
|
||||
"focusRemote": "किसी अन्य व्यक्ति के वीडियो पर केंद्रित करें",
|
||||
"fullScreen": "View or exit full screen",
|
||||
"fullScreen": "पूर्ण स्क्रीन देखें या बाहर निकलें",
|
||||
"keyboardShortcuts": "कीबोर्ड शॉर्टकट्स",
|
||||
"localRecording": "स्थानीय रिकॉर्डिंग नियंत्रण दिखाएं या छिपाएँ",
|
||||
"mute": "अपने माइक्रोफ़ोन को म्यूट या अनम्यूट करें",
|
||||
"pushToTalk": "Push to talk",
|
||||
"pushToTalk": "बोलने के लिए दबाएं",
|
||||
"raiseHand": "अपना हाथ उठाएँ या नीचे करें",
|
||||
"showSpeakerStats": "स्पीकर आंकड़े दिखाएं",
|
||||
"toggleChat": "चैट खोलें या बंद करें",
|
||||
@@ -418,39 +418,39 @@
|
||||
"videoMute": "अपना कैमरा प्रारंभ या बंद करें"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "We're working on freeing streaming resources. Please try again in a few minutes.",
|
||||
"busyTitle": "All streamers are currently busy",
|
||||
"changeSignIn": "Switch accounts.",
|
||||
"choose": "Choose a live stream",
|
||||
"chooseCTA": "Choose a streaming option. You're currently logged in as {{email}}.",
|
||||
"enterStreamKey": "Enter your YouTube live stream key here.",
|
||||
"error": "Live Streaming failed. Please try again.",
|
||||
"errorAPI": "An error occurred while accessing your YouTube broadcasts. Please try logging in again.",
|
||||
"errorLiveStreamNotEnabled": "Live Streaming is not enabled on {{email}}. Please enable live streaming or log into an account with live streaming enabled.",
|
||||
"expandedOff": "The live streaming has stopped",
|
||||
"expandedOn": "The meeting is currently being streamed to YouTube.",
|
||||
"expandedPending": "The live streaming is being started…",
|
||||
"failedToStart": "Live Streaming failed to start",
|
||||
"getStreamKeyManually": "We weren’t able to fetch any live streams. Try getting your live stream key from YouTube.",
|
||||
"googlePrivacyPolicy": "Google Privacy Policy",
|
||||
"invalidStreamKey": "Live stream key may be incorrect.",
|
||||
"limitNotificationDescriptionNative": "Your streaming will be limited to {{limit}} min. For unlimited streaming try {{app}}.",
|
||||
"limitNotificationDescriptionWeb": "Due to high demand your streaming will be limited to {{limit}} min. For unlimited streaming try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"off": "Live Streaming stopped",
|
||||
"offBy": "{{name}} stopped the live streaming",
|
||||
"on": "Live Streaming started",
|
||||
"onBy": "{{name}} started the live streaming",
|
||||
"pending": "Starting Live Stream…",
|
||||
"serviceName": "Live Streaming service",
|
||||
"signIn": "Sign in with Google",
|
||||
"signInCTA": "Sign in or enter your live stream key from YouTube.",
|
||||
"signOut": "Sign out",
|
||||
"signedInAs": "You are currently signed in as:",
|
||||
"start": "Start a live stream",
|
||||
"streamIdHelp": "What's this?",
|
||||
"title": "सीधा प्रसारण",
|
||||
"unavailableTitle": "Live Streaming unavailable",
|
||||
"youtubeTerms": "YouTube terms of services"
|
||||
"busy": "हम स्ट्रीमिंग संसाधनों को मुक्त करने पर काम कर रहे हैं। कृपया कुछ मिनटों में पुनः प्रयास करें।",
|
||||
"busyTitle": "सभी स्ट्रीमर वर्तमान में व्यस्त हैं",
|
||||
"changeSignIn": "खाता बदलें।",
|
||||
"choose": "एक लाइव स्ट्रीम चुनें",
|
||||
"chooseCTA": "स्ट्रीमिंग विकल्प चुनें। आप वर्तमान में {{email}} के रूप में लॉग इन हैं।",
|
||||
"enterStreamKey": "अपनी YouTube लाइव स्ट्रीम कुंजी यहाँ दर्ज करें।",
|
||||
"error": "लाइव स्ट्रीमिंग विफल रही। कृपया पुनः प्रयास करें।",
|
||||
"errorAPI": "आपके YouTube प्रसारण तक पहुँचने में त्रुटि हुई। कृपया पुनः लॉगिन करें।",
|
||||
"errorLiveStreamNotEnabled": "{{email}} पर लाइव स्ट्रीमिंग सक्षम नहीं है। कृपया लाइव स्ट्रीमिंग सक्षम करें या ऐसे खाते में लॉग इन करें जिसमें लाइव स्ट्रीमिंग सक्षम हो।",
|
||||
"expandedOff": "लाइव स्ट्रीमिंग बंद हो गई है",
|
||||
"expandedOn": "बैठक वर्तमान में YouTube पर स्ट्रीम की जा रही है।",
|
||||
"expandedPending": "लाइव स्ट्रीमिंग शुरू की जा रही है…",
|
||||
"failedToStart": "लाइव स्ट्रीमिंग शुरू करने में विफल रहा",
|
||||
"getStreamKeyManually": "हम कोई लाइव स्ट्रीम प्राप्त नहीं कर सके। कृपया YouTube से अपनी लाइव स्ट्रीम कुंजी प्राप्त करने का प्रयास करें।",
|
||||
"googlePrivacyPolicy": "Google गोपनीयता नीति",
|
||||
"invalidStreamKey": "लाइव स्ट्रीम कुंजी गलत हो सकती है।",
|
||||
"limitNotificationDescriptionNative": "आपकी स्ट्रीमिंग {{limit}} मिनट तक सीमित होगी। असीमित स्ट्रीमिंग के लिए {{app}} आज़माएँ।",
|
||||
"limitNotificationDescriptionWeb": "अधिक मांग के कारण आपकी स्ट्रीमिंग {{limit}} मिनट तक सीमित होगी। असीमित स्ट्रीमिंग के लिए <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a> आज़माएँ।",
|
||||
"off": "लाइव स्ट्रीमिंग बंद हो गई",
|
||||
"offBy": "{{name}} ने लाइव स्ट्रीमिंग बंद कर दी",
|
||||
"on": "लाइव स्ट्रीमिंग शुरू हो गई",
|
||||
"onBy": "{{name}} ने लाइव स्ट्रीमिंग शुरू की",
|
||||
"pending": "लाइव स्ट्रीम शुरू हो रही है…",
|
||||
"serviceName": "लाइव स्ट्रीमिंग सेवा",
|
||||
"signIn": "Google से साइन इन करें",
|
||||
"signInCTA": "साइन इन करें या YouTube से अपनी लाइव स्ट्रीम कुंजी दर्ज करें।",
|
||||
"signOut": "साइन आउट करें",
|
||||
"signedInAs": "आप वर्तमान में इस रूप में साइन इन हैं:",
|
||||
"start": "एक लाइव स्ट्रीम शुरू करें",
|
||||
"streamIdHelp": "यह क्या है?",
|
||||
"title": "लाइव स्ट्रीमिंग",
|
||||
"unavailableTitle": "लाइव स्ट्रीमिंग उपलब्ध नहीं है",
|
||||
"youtubeTerms": "YouTube सेवा की शर्तें"
|
||||
},
|
||||
"lobby": {
|
||||
"allow": "अनुमति दें",
|
||||
@@ -481,38 +481,38 @@
|
||||
"notificationLobbyEnabled": "लॉबी को {{originParticipantName}}द्वारा सक्षम किया गया",
|
||||
"notificationTitle": "लॉबी",
|
||||
"passwordField": "मीटिंग पासवर्ड दर्ज करें",
|
||||
"passwordJoinButton": "Join",
|
||||
"passwordJoinButton": "शामिल हों",
|
||||
"title": "लॉबी",
|
||||
"toggleLabel": "लॉबी सक्षम करें"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
"off": "Off",
|
||||
"on": "On",
|
||||
"unknown": "Unknown"
|
||||
"off": "बंद",
|
||||
"on": "चालू",
|
||||
"unknown": "अज्ञात"
|
||||
},
|
||||
"dialogTitle": "Local Recording Controls",
|
||||
"duration": "Duration",
|
||||
"durationNA": "N/A",
|
||||
"encoding": "Encoding",
|
||||
"label": "LOR",
|
||||
"labelToolTip": "Local recording is engaged",
|
||||
"localRecording": "Local Recording",
|
||||
"me": "Me",
|
||||
"dialogTitle": "स्थानीय रिकॉर्डिंग नियंत्रण",
|
||||
"duration": "अवधि",
|
||||
"durationNA": "उपलब्ध नहीं",
|
||||
"encoding": "एन्कोडिंग",
|
||||
"label": "स्थानीय रिकॉर्डिंग",
|
||||
"labelToolTip": "स्थानीय रिकॉर्डिंग सक्रिय है",
|
||||
"localRecording": "स्थानीय रिकॉर्डिंग",
|
||||
"me": "मैं",
|
||||
"messages": {
|
||||
"engaged": "Local recording engaged.",
|
||||
"finished": "Recording session {{token}} finished. Please send the recorded file to the moderator.",
|
||||
"finishedModerator": "Recording session {{token}} finished. The recording of the local track has been saved. Please ask the other participants to submit their recordings.",
|
||||
"notModerator": "You are not the moderator. You cannot start or stop local recording."
|
||||
"engaged": "स्थानीय रिकॉर्डिंग सक्रिय हो गई।",
|
||||
"finished": "रिकॉर्डिंग सत्र {{token}} समाप्त हो गया। कृपया रिकॉर्ड की गई फ़ाइल मॉडरेटर को भेजें।",
|
||||
"finishedModerator": "रिकॉर्डिंग सत्र {{token}} समाप्त हो गया। स्थानीय ट्रैक की रिकॉर्डिंग सहेज ली गई है। कृपया अन्य प्रतिभागियों से उनकी रिकॉर्डिंग जमा करने के लिए कहें।",
|
||||
"notModerator": "आप मॉडरेटर नहीं हैं। आप स्थानीय रिकॉर्डिंग प्रारंभ या बंद नहीं कर सकते।"
|
||||
},
|
||||
"moderator": "Moderator",
|
||||
"no": "No",
|
||||
"participant": "Participant",
|
||||
"participantStats": "Participant Stats",
|
||||
"sessionToken": "Session Token",
|
||||
"start": "Start Recording",
|
||||
"stop": "Stop Recording",
|
||||
"yes": "Yes"
|
||||
"moderator": "मॉडरेटर",
|
||||
"no": "नहीं",
|
||||
"participant": "प्रतिभागी",
|
||||
"participantStats": "प्रतिभागी आँकड़े",
|
||||
"sessionToken": "सत्र टोकन",
|
||||
"start": "रिकॉर्डिंग प्रारंभ करें",
|
||||
"stop": "रिकॉर्डिंग बंद करें",
|
||||
"yes": "हाँ"
|
||||
},
|
||||
"lockRoomPassword": "पासवर्ड",
|
||||
"lockRoomPasswordUppercase": "पासवर्ड",
|
||||
@@ -536,8 +536,8 @@
|
||||
"kickParticipant": "{{kicked}} को {{kicker}} द्वारा किक किया गया",
|
||||
"me": "मैं",
|
||||
"moderator": "मॉडरेटर के अधिकार दिए गए!",
|
||||
"muted": "You have started the conversation muted.",
|
||||
"mutedRemotelyDescription": "You can always unmute when you're ready to speak. Mute back when you're done to keep noise away from the meeting.",
|
||||
"muted": "आपने वार्तालाप को म्यूट करके शुरू किया है।",
|
||||
"mutedRemotelyDescription": "जब आप बोलने के लिए तैयार हों, तो आप हमेशा अनम्यूट कर सकते हैं। बैठक में शोर कम रखने के लिए बोलने के बाद म्यूट कर दें।",
|
||||
"mutedRemotelyTitle": "आपको {{participantDisplayName}} द्वारा म्यूट कर दिया गया है!",
|
||||
"mutedTitle": "आप मौन हैं!",
|
||||
"newDeviceAction": "उपयोग करें",
|
||||
@@ -563,7 +563,7 @@
|
||||
"reject": "अस्वीकार"
|
||||
}
|
||||
},
|
||||
"passwordDigitsOnly": "Up to {{number}} digits",
|
||||
"passwordDigitsOnly": "अधिकतम {{number}} अंक",
|
||||
"passwordSetRemotely": "दूसरे प्रतिभागी द्वारा निर्धारित",
|
||||
"polls": {
|
||||
"errors": {
|
||||
@@ -580,25 +580,25 @@
|
||||
"callMeAtNumber": "मुझे इस नंबर पर कॉल करें:",
|
||||
"calling": "कॉलिंग",
|
||||
"configuringDevices": "डिवाइस कॉन्फ़िगर कर रहा है…",
|
||||
"connectedWithAudioQ": "You’re connected with audio?",
|
||||
"connectedWithAudioQ": "क्या आप ऑडियो से जुड़े हैं?",
|
||||
"connection": {
|
||||
"good": "Your internet connection looks good!",
|
||||
"nonOptimal": "Your internet connection is not optimal",
|
||||
"poor": "आपके पास एक खराब इंटरनेट कनेक्शन है"
|
||||
"good": "आपका इंटरनेट कनेक्शन अच्छा है!",
|
||||
"nonOptimal": "आपका इंटरनेट कनेक्शन आदर्श नहीं है",
|
||||
"poor": "आपका इंटरनेट कनेक्शन खराब है"
|
||||
},
|
||||
"connectionDetails": {
|
||||
"audioClipping": "We expect your audio to be clipped.",
|
||||
"audioHighQuality": "We expect your audio to have excellent quality.",
|
||||
"audioLowNoVideo": "We expect your audio quality to be low and no video.",
|
||||
"goodQuality": "Awesome! Your media quality is going to be great.",
|
||||
"noMediaConnectivity": "We could not find a way to establish media connectivity for this test. This is typically caused by a firewall or NAT.",
|
||||
"noVideo": "We expect that your video will be terrible.",
|
||||
"undetectable": "If you still can not make calls in browser, we recommend that you make sure your speakers, microphone and camera are properly set up, that you have granted your browser rights to use your microphone and camera, and that your browser version is up-to-date. If you still have trouble calling, you should contact the web application developer.",
|
||||
"veryPoorConnection": "We expect your call quality to be really terrible.",
|
||||
"videoFreezing": "We expect your video to freeze, turn black, and be pixelated.",
|
||||
"videoHighQuality": "We expect your video to have good quality.",
|
||||
"videoLowQuality": "We expect your video to have low quality in terms of frame rate and resolution.",
|
||||
"videoTearing": "We expect your video to be pixelated or have visual artefacts."
|
||||
"audioClipping": "हमें उम्मीद है कि आपका ऑडियो कट सकता है।",
|
||||
"audioHighQuality": "हमें उम्मीद है कि आपका ऑडियो बेहतरीन गुणवत्ता का होगा।",
|
||||
"audioLowNoVideo": "हमें उम्मीद है कि आपकी ऑडियो गुणवत्ता कम होगी और वीडियो उपलब्ध नहीं होगा।",
|
||||
"goodQuality": "बहुत बढ़िया! आपकी मीडिया गुणवत्ता शानदार होगी।",
|
||||
"noMediaConnectivity": "हम इस परीक्षण के लिए मीडिया कनेक्टिविटी स्थापित करने में असमर्थ हैं। यह आमतौर पर फ़ायरवॉल या NAT के कारण होता है।",
|
||||
"noVideo": "हमें उम्मीद है कि आपका वीडियो बहुत खराब होगा।",
|
||||
"undetectable": "यदि आप अभी भी ब्राउज़र में कॉल नहीं कर पा रहे हैं, तो हम अनुशंसा करते हैं कि आप सुनिश्चित करें कि आपके स्पीकर, माइक्रोफ़ोन और कैमरा सही तरीके से सेट किए गए हैं, कि आपने अपने ब्राउज़र को माइक्रोफ़ोन और कैमरा उपयोग की अनुमति दी है, और आपका ब्राउज़र संस्करण अपडेट है। यदि समस्या बनी रहती है, तो आपको वेब एप्लिकेशन डेवलपर से संपर्क करना चाहिए।",
|
||||
"veryPoorConnection": "हमें उम्मीद है कि आपकी कॉल गुणवत्ता बहुत खराब होगी।",
|
||||
"videoFreezing": "हमें उम्मीद है कि आपका वीडियो फ्रीज़ होगा, काला हो जाएगा और धुंधला दिखेगा।",
|
||||
"videoHighQuality": "हमें उम्मीद है कि आपका वीडियो अच्छी गुणवत्ता का होगा।",
|
||||
"videoLowQuality": "हमें उम्मीद है कि आपका वीडियो फ्रेम दर और रिज़ॉल्यूशन के मामले में निम्न गुणवत्ता का होगा।",
|
||||
"videoTearing": "हमें उम्मीद है कि आपका वीडियो धुंधला होगा या इसमें दृश्य गड़बड़ियां हो सकती हैं।"
|
||||
},
|
||||
"copyAndShare": "मीटिंग लिंक कॉपी और साझा करे ",
|
||||
"dialInMeeting": "मीटिंग में डायल करें",
|
||||
@@ -637,7 +637,7 @@
|
||||
"disconnected": "डिस्कनेक्ट किया गया",
|
||||
"expired": "एक्सपायर्ड",
|
||||
"ignored": "Ignored",
|
||||
"initializingCall": "Initializing Call…",
|
||||
"initializingCall": "कॉल प्रारंभ की जा रही है…",
|
||||
"invited": "आमंत्रित",
|
||||
"rejected": "अस्वीकृत",
|
||||
"ringing": "Ringing…"
|
||||
@@ -650,38 +650,38 @@
|
||||
},
|
||||
"raisedHand": "बोलना चाहेंगे",
|
||||
"recording": {
|
||||
"authDropboxText": "Upload to Dropbox",
|
||||
"availableSpace": "Available space: {{spaceLeft}} MB (approximately {{duration}} minutes of recording)",
|
||||
"beta": "BETA",
|
||||
"busy": "We're working on freeing recording resources. Please try again in a few minutes.",
|
||||
"authDropboxText": "ड्रॉपबॉक्स पर अपलोड करें",
|
||||
"availableSpace": "उपलब्ध स्थान: {{spaceLeft}} MB (लगभग {{duration}} मिनट की रिकॉर्डिंग)",
|
||||
"beta": "बीटा",
|
||||
"busy": "हम रिकॉर्डिंग संसाधनों को मुक्त करने पर काम कर रहे हैं। कृपया कुछ मिनटों में पुनः प्रयास करें।",
|
||||
"busyTitle": "सभी रिकॉर्डर अभी व्यस्त हैं",
|
||||
"error": "रिकॉर्डिंग विफल हुई। कृपया पुन: प्रयास करें।",
|
||||
"error": "रिकॉर्डिंग विफल हुई। कृपया पुनः प्रयास करें।",
|
||||
"expandedOff": "रिकॉर्डिंग बंद हो गई है",
|
||||
"expandedOn": "The meeting is currently being recorded.",
|
||||
"expandedOn": "बैठक की रिकॉर्डिंग की जा रही है।",
|
||||
"expandedPending": "रिकॉर्डिंग शुरू की जा रही है…",
|
||||
"failedToStart": "रिकॉर्डिंग शुरू करने में विफलता हुई।",
|
||||
"fileSharingdescription": "Share recording with meeting participants",
|
||||
"limitNotificationDescriptionNative": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <3>{{app}}</3>.",
|
||||
"limitNotificationDescriptionWeb": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"live": "LIVE",
|
||||
"loggedIn": "Logged in as {{userName}}",
|
||||
"off": "Recording stopped",
|
||||
"offBy": "{{name}} stopped the recording",
|
||||
"on": "Recording started",
|
||||
"onBy": "{{name}} started the recording",
|
||||
"pending": "Preparing to record the meeting…",
|
||||
"rec": "REC",
|
||||
"serviceDescription": "Your recording will be saved by the recording service",
|
||||
"serviceDescriptionCloud": "Cloud recording",
|
||||
"serviceName": "Recording service",
|
||||
"signIn": "Sign in",
|
||||
"signOut": "Sign out",
|
||||
"fileSharingdescription": "रिकॉर्डिंग को बैठक प्रतिभागियों के साथ साझा करें",
|
||||
"limitNotificationDescriptionNative": "उच्च मांग के कारण आपकी रिकॉर्डिंग {{limit}} मिनट तक सीमित रहेगी। असीमित रिकॉर्डिंग के लिए <3>{{app}}</3> आज़माएँ।",
|
||||
"limitNotificationDescriptionWeb": "उच्च मांग के कारण आपकी रिकॉर्डिंग {{limit}} मिनट तक सीमित रहेगी। असीमित रिकॉर्डिंग के लिए <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a> आज़माएँ।",
|
||||
"live": "लाइव",
|
||||
"loggedIn": "{{userName}} के रूप में लॉग इन किया गया",
|
||||
"off": "रिकॉर्डिंग बंद हो गई",
|
||||
"offBy": "{{name}} ने रिकॉर्डिंग बंद की",
|
||||
"on": "रिकॉर्डिंग शुरू हो गई",
|
||||
"onBy": "{{name}} ने रिकॉर्डिंग शुरू की",
|
||||
"pending": "बैठक की रिकॉर्डिंग की तैयारी हो रही है…",
|
||||
"rec": "रिकॉर्डिंग",
|
||||
"serviceDescription": "आपकी रिकॉर्डिंग को रिकॉर्डिंग सेवा द्वारा सहेजा जाएगा",
|
||||
"serviceDescriptionCloud": "क्लाउड रिकॉर्डिंग",
|
||||
"serviceName": "रिकॉर्डिंग सेवा",
|
||||
"signIn": "साइन इन करें",
|
||||
"signOut": "साइन आउट करें",
|
||||
"title": "रिकॉर्डिंग",
|
||||
"unavailable": "ओह! {{serviceName}} वर्तमान में अनुपलब्ध है। हम समस्या को हल करने पर काम कर रहे हैं। कृपया बाद में पुनः प्रयास करें।",
|
||||
"unavailable": "ओह! {{serviceName}} वर्तमान में अनुपलब्ध है। हम इस समस्या को हल करने पर काम कर रहे हैं। कृपया बाद में पुनः प्रयास करें।",
|
||||
"unavailableTitle": "रिकॉर्डिंग उपलब्ध नहीं है"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Pull to refresh"
|
||||
"pullToRefresh": "रीफ़्रेश करने के लिए नीचे खींचें"
|
||||
},
|
||||
"security": {
|
||||
"about": "आप अपनी मीटिंग में $t(lockRoomPassword) जोड़ सकते हैं। सहभागियों को मीटिंग में शामिल होने से पहले $t(lockRoomPassword) प्रदान करना होगा।",
|
||||
@@ -691,16 +691,16 @@
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "The {{appName}} calendar integration is used to securely access your calendar so it can read upcoming events.",
|
||||
"disconnect": "Disconnect",
|
||||
"microsoftSignIn": "Sign in with Microsoft",
|
||||
"signedIn": "Currently accessing calendar events for {{email}}. Click the Disconnect button below to stop accessing calendar events.",
|
||||
"title": "Calendar"
|
||||
"about": "{{appName}} कैलेंडर एकीकरण आपके कैलेंडर तक सुरक्षित रूप से पहुंचने के लिए उपयोग किया जाता है ताकि यह आगामी कार्यक्रम पढ़ सके।",
|
||||
"disconnect": "डिस्कनेक्ट करें",
|
||||
"microsoftSignIn": "Microsoft से साइन इन करें",
|
||||
"signedIn": "वर्तमान में {{email}} के कैलेंडर कार्यक्रमों तक पहुंच रही है। कैलेंडर कार्यक्रमों की पहुंच बंद करने के लिए नीचे दिए गए डिस्कनेक्ट बटन पर क्लिक करें।",
|
||||
"title": "कैलेंडर"
|
||||
},
|
||||
"devices": "डिवाइस",
|
||||
"followMe": "Everyone follows me",
|
||||
"followMe": "हर कोई मेरा अनुसरण करेगा",
|
||||
"language": "भाषा",
|
||||
"loggedIn": "Logged in as {{name}}",
|
||||
"loggedIn": "{{name}} के रूप में लॉग इन किया",
|
||||
"microphones": "माइक्रोफोन",
|
||||
"moderator": "Moderator",
|
||||
"more": "More",
|
||||
@@ -710,8 +710,8 @@
|
||||
"selectCamera": "कैमरा",
|
||||
"selectMic": "माइक्रोफोन",
|
||||
"speakers": "Speakers",
|
||||
"startAudioMuted": "Everyone starts muted",
|
||||
"startVideoMuted": "Everyone starts hidden",
|
||||
"startAudioMuted": "सभी लोग म्यूट से शुरू करेंगे",
|
||||
"startVideoMuted": "सभी लोग छिपे हुए शुरू करेंगे",
|
||||
"title": "सेटिंग"
|
||||
},
|
||||
"settingsView": {
|
||||
@@ -720,9 +720,9 @@
|
||||
"alertOk": "ओके",
|
||||
"alertTitle": "चेतावनी",
|
||||
"alertURLText": "दर्ज किया गया सर्वर URL अमान्य है",
|
||||
"buildInfoSection": "Build Information",
|
||||
"buildInfoSection": "बिल्ड जानकारी",
|
||||
"conferenceSection": "सम्मेलन",
|
||||
"disableCallIntegration": "Disable native call integration",
|
||||
"disableCallIntegration": "मूल कॉल एकीकरण अक्षम करें",
|
||||
"disableCrashReporting": "क्रैश रिपोर्टिंग अक्षम करें",
|
||||
"disableCrashReportingWarning": "क्या आप वाकई क्रैश रिपोर्टिंग को अक्षम करना चाहते हैं? एप्लिकेशन को पुनरारंभ करने के बाद सेटिंग लागू की जाएगी",
|
||||
"disableP2P": "पीयर-टू-पीयर मोड को अक्षम करें",
|
||||
@@ -731,16 +731,16 @@
|
||||
"header": "सेटिंग",
|
||||
"profileSection": "प्रोफाइल",
|
||||
"serverURL": "सर्वर URL",
|
||||
"showAdvanced": "Show advanced settings",
|
||||
"startWithAudioMuted": "Start with audio muted",
|
||||
"startWithVideoMuted": "Start with video muted",
|
||||
"showAdvanced": "उन्नत सेटिंग्स दिखाएं",
|
||||
"startWithAudioMuted": "ऑडियो म्यूट के साथ शुरू करें",
|
||||
"startWithVideoMuted": "वीडियो म्यूट के साथ शुरू करें",
|
||||
"version": "संस्करण"
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "\n\n=====\n\nJust want to dial in on your phone?\n\n{{defaultDialInNumber}}Click this link to see the dial in phone numbers for this meeting\n{{dialInfoPageUrl}}",
|
||||
"mainText": "मीटिंग में शामिल होने के लिए निम्न लिंक पर क्लिक करें:\n{{roomUrl}}"
|
||||
},
|
||||
"speaker": "Speaker",
|
||||
"speaker": "स्पीकर",
|
||||
"speakerStats": {
|
||||
"hours": "{{count}}h",
|
||||
"minutes": "{{count}}m",
|
||||
@@ -748,8 +748,8 @@
|
||||
"search": "खोजें",
|
||||
"searchHint": "प्रतिभागियों को खोजें",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "Speaker Stats",
|
||||
"speakerTime": "Speaker Time"
|
||||
"speakerStats": "स्पीकर आंकड़े",
|
||||
"speakerTime": "स्पीकर समय"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"genericTitle": "मीटिंग को आपके माइक्रोफ़ोन और कैमरे का उपयोग करने की आवश्यकता है।",
|
||||
@@ -825,10 +825,10 @@
|
||||
"download": "हमारे एप्लिकेशन डाउनलोड करें",
|
||||
"e2ee": "एंड-टू-एंड एन्क्रिप्शन",
|
||||
"embedMeeting": "Embed meeting",
|
||||
"enterFullScreen": "View full screen",
|
||||
"enterTileView": "Enter tile view",
|
||||
"exitFullScreen": "Exit full screen",
|
||||
"exitTileView": "Exit tile view",
|
||||
"enterFullScreen": "पूर्ण स्क्रीन में देखें",
|
||||
"enterTileView": "टाइल दृश्य में प्रवेश करें",
|
||||
"exitFullScreen": "पूर्ण स्क्रीन से बाहर निकलें",
|
||||
"exitTileView": "टाइल दृश्य से बाहर निकलें",
|
||||
"feedback": "प्रतिक्रिया छोड़ें",
|
||||
"hangup": "छोड़ें",
|
||||
"help": "Help",
|
||||
@@ -837,7 +837,7 @@
|
||||
"lobbyButtonEnable": "लॉबी मोड सक्षम करें",
|
||||
"login": "लॉग इन",
|
||||
"logout": "लॉगआउट",
|
||||
"lowerYourHand": "Lower your hand",
|
||||
"lowerYourHand": "अपना हाथ नीचे करें",
|
||||
"moreActions": "More actions",
|
||||
"moreOptions": "अधिक विकल्प",
|
||||
"mute": "म्यूट / अनम्यूट",
|
||||
@@ -866,7 +866,7 @@
|
||||
"startSubtitles": "Start subtitles",
|
||||
"stopScreenSharing": "स्क्रीन शेयरिंग बंद करो",
|
||||
"stopSharedVideo": "YouTube वीडियो बंद करें",
|
||||
"stopSubtitles": "Stop subtitles",
|
||||
"stopSubtitles": "उपशीर्षक बंद करें",
|
||||
"talkWhileMutedPopup": "बोलने की कोशिश कर रहा है? आप मौन हैं",
|
||||
"tileViewToggle": "टॉगल टाइल दृश्य",
|
||||
"toggleCamera": "कैमरा टॉगल करें",
|
||||
@@ -874,13 +874,13 @@
|
||||
"videomute": "स्टार्ट / स्टॉप कैमरा"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Start / Stop subtitles",
|
||||
"ccButtonTooltip": "सबटाइटल शुरू / बंद करें",
|
||||
"error": "ट्रांसक्रिप्शनिंग विफल रही। कृपया पुन: प्रयास करें",
|
||||
"expandedLabel": "वर्तमान में ट्रांसक्रिप्शनिंग चालू है",
|
||||
"failedToStart": "ट्रांसक्रिप्शनिंग प्रारंभ करने में विफल",
|
||||
"labelToolTip": "The meeting is being transcribed",
|
||||
"labelToolTip": "बैठक का लिप्यंतरण किया जा रहा है",
|
||||
"off": "ट्रांसक्रिप्शनिंग बंद कर दिया",
|
||||
"pending": "Preparing to transcribe the meeting…",
|
||||
"pending": "बैठक के ट्रांसक्रिप्शन की तैयारी हो रही है…",
|
||||
"start": "उपशीर्षक दिखाना शुरू करें",
|
||||
"stop": "उपशीर्षक दिखाना बंद करें",
|
||||
"tr": "TR"
|
||||
@@ -899,20 +899,20 @@
|
||||
"pending": "{{displayName}} को आमंत्रित किया गया है"
|
||||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "AUD",
|
||||
"audioOnlyExpanded": "You are in low bandwidth mode. In this mode you will receive only audio and screen sharing.",
|
||||
"callQuality": "Video Quality",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "Viewing high definition video",
|
||||
"highDefinition": "High definition",
|
||||
"labelTooiltipNoVideo": "No video",
|
||||
"labelTooltipAudioOnly": "Low bandwidth mode enabled",
|
||||
"ld": "LD",
|
||||
"ldTooltip": "Viewing low definition video",
|
||||
"lowDefinition": "Low definition",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Viewing standard definition video",
|
||||
"standardDefinition": "Standard definition"
|
||||
"audioOnly": "केवल ऑडियो",
|
||||
"audioOnlyExpanded": "आप कम बैंडविड्थ मोड में हैं। इस मोड में आपको केवल ऑडियो और स्क्रीन शेयरिंग प्राप्त होगी।",
|
||||
"callQuality": "वीडियो गुणवत्ता",
|
||||
"hd": "एचडी",
|
||||
"hdTooltip": "हाई डेफिनिशन वीडियो देख रहे हैं",
|
||||
"highDefinition": "हाई डेफिनिशन",
|
||||
"labelTooiltipNoVideo": "कोई वीडियो नहीं",
|
||||
"labelTooltipAudioOnly": "कम बैंडविड्थ मोड सक्षम",
|
||||
"ld": "एलडी",
|
||||
"ldTooltip": "लो डेफिनिशन वीडियो देख रहे हैं",
|
||||
"lowDefinition": "लो डेफिनिशन",
|
||||
"sd": "एसडी",
|
||||
"sdTooltip": "स्टैंडर्ड डेफिनिशन वीडियो देख रहे हैं",
|
||||
"standardDefinition": "स्टैंडर्ड डेफिनिशन"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"connectionInfo": "कनेक्शन जानकारी",
|
||||
|
||||
@@ -371,6 +371,7 @@
|
||||
"sendPrivateMessageTitle": "Invio privatamente?",
|
||||
"serviceUnavailable": "Servizio non disponibile",
|
||||
"sessTerminated": "Chiamata terminata",
|
||||
"sessTerminatedReason": "La chiamata è stata terminata",
|
||||
"sessionRestarted": "Chiamata riavviata automaticamente",
|
||||
"shareAudio": "Continue",
|
||||
"shareAudioTitle": "Come condividere l'audio",
|
||||
|
||||
1584
lang/main-nb.json
Normal file
1584
lang/main-nb.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -733,7 +733,9 @@
|
||||
"me": "me",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "Security vulnerability!",
|
||||
"allowAction": "Allow",
|
||||
"allowAudio": "Allow Audio",
|
||||
"allowBoth": "Both",
|
||||
"allowVideo": "Allow Video",
|
||||
"allowedUnmute": "You can unmute your microphone, start your camera or share your screen.",
|
||||
"audioUnmuteBlockedDescription": "Mic unmute operation has been temporarily blocked because of system limits.",
|
||||
"audioUnmuteBlockedTitle": "Mic unmute blocked!",
|
||||
@@ -755,7 +757,7 @@
|
||||
"focusFail": "{{component}} not available - retry in {{ms}} sec",
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "Notifications",
|
||||
"hostAskedUnmute": "The moderator would like you to speak",
|
||||
"hostAskedUnmute": "The moderator would like you to participate.",
|
||||
"invalidTenant": "Invalid tenant",
|
||||
"invalidTenantHyphenDescription": "The tenant you are using is invalid (starts or ends with '-').",
|
||||
"invalidTenantLengthDescription": "The tenant you are using is too long.",
|
||||
@@ -807,7 +809,7 @@
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removed by another participant",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) set by another participant",
|
||||
"raiseHandAction": "Raise hand",
|
||||
"raisedHand": "Would like to speak.",
|
||||
"raisedHand": "Would like to participate.",
|
||||
"raisedHands": "{{participantName}} and {{raisedHands}} more people",
|
||||
"reactionSounds": "Disable sounds",
|
||||
"reactionSoundsForAll": "Disable sounds for all",
|
||||
@@ -824,7 +826,8 @@
|
||||
"suggestRecordingAction": "Start",
|
||||
"suggestRecordingDescription": "Would you like to start a recording?",
|
||||
"suggestRecordingTitle": "Record this meeting",
|
||||
"unmute": "Unmute",
|
||||
"unmute": "Unmute Audio",
|
||||
"unmuteVideo": "Unmute Video",
|
||||
"videoMutedRemotelyDescription": "You can always turn it on again.",
|
||||
"videoMutedRemotelyTitle": "Your video has been turned off by {{participantDisplayName}}",
|
||||
"videoUnmuteBlockedDescription": "Camera unmute and desktop sharing operation have been temporarily blocked because of system limits.",
|
||||
@@ -1405,7 +1408,8 @@
|
||||
"ccButtonTooltip": "Start / Stop subtitles",
|
||||
"expandedLabel": "Transcribing is currently on",
|
||||
"failed": "Transcribing failed",
|
||||
"labelToolTip": "The meeting is being transcribed",
|
||||
"labelTooltip": "This meeting is being transcribed.",
|
||||
"labelTooltipExtra": "In addition, a transcript will be available later.",
|
||||
"sourceLanguageDesc": "Currently the meeting language is set to <b>{{sourceLanguage}}</b>. <br/> You can change it from ",
|
||||
"sourceLanguageHere": "here",
|
||||
"start": "Start showing subtitles",
|
||||
@@ -1444,7 +1448,7 @@
|
||||
"ldTooltip": "Viewing low definition video",
|
||||
"lowDefinition": "Low definition",
|
||||
"performanceSettings": "Performance settings",
|
||||
"recording": "Recording in progress",
|
||||
"recording": "This meeting is being recorded.",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Viewing standard definition video",
|
||||
"standardDefinition": "Standard definition",
|
||||
|
||||
@@ -75,6 +75,7 @@ import {
|
||||
toggleChat
|
||||
} from '../../react/features/chat/actions';
|
||||
import { openChat } from '../../react/features/chat/actions.web';
|
||||
import { showDesktopPicker } from '../../react/features/desktop-picker/actions';
|
||||
import {
|
||||
processExternalDeviceRequest
|
||||
} from '../../react/features/device-selection/functions';
|
||||
@@ -491,7 +492,8 @@ function initCommands() {
|
||||
APP.store.dispatch(toggleRequestingSubtitles());
|
||||
},
|
||||
'set-subtitles': (enabled, displaySubtitles, language) => {
|
||||
APP.store.dispatch(setRequestingSubtitles(enabled, displaySubtitles, language));
|
||||
APP.store.dispatch(setRequestingSubtitles(
|
||||
enabled, displaySubtitles, language ? `translation-languages:${language}` : null));
|
||||
},
|
||||
'toggle-tile-view': () => {
|
||||
sendAnalytics(createApiEvent('tile-view.toggled'));
|
||||
@@ -594,9 +596,13 @@ function initCommands() {
|
||||
* Defaults to "normal" if not provided.
|
||||
* @param { string } arg.timeout - Timeout type, either `short`, `medium`, `long` or `sticky`.
|
||||
* Defaults to "short" if not provided.
|
||||
* @param { Array<Object> } arg.customActions - An array of custom actions to be displayed in the notification.
|
||||
* Each object should have a `label` and a `uuid` property. It should be used along a listener
|
||||
* for the `customNotificationActionTriggered` event to handle the custom action.
|
||||
* @returns {void}
|
||||
*/
|
||||
'show-notification': ({
|
||||
customActions = [],
|
||||
title,
|
||||
description,
|
||||
uid,
|
||||
@@ -618,7 +624,15 @@ function initCommands() {
|
||||
return;
|
||||
}
|
||||
|
||||
const handlers = customActions.map(({ uuid }) => () => {
|
||||
APP.API.notifyCustomNotificationActionTriggered(uuid);
|
||||
});
|
||||
|
||||
const keys = customActions.map(({ label }) => label);
|
||||
|
||||
APP.store.dispatch(showNotification({
|
||||
customActionHandler: handlers,
|
||||
customActionNameKey: keys,
|
||||
uid,
|
||||
title,
|
||||
description,
|
||||
@@ -1048,6 +1062,23 @@ function initCommands() {
|
||||
}
|
||||
case '_new_electron_screensharing_supported': {
|
||||
callback(true);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'open-desktop-picker': {
|
||||
const { desktopSharingSources } = APP.store.getState()['features/base/config'];
|
||||
const options = {
|
||||
desktopSharingSources: desktopSharingSources ?? [ 'screen', 'window' ]
|
||||
};
|
||||
const onSourceChoose = (_streamId, _type, screenShareAudio, source) => {
|
||||
callback({
|
||||
screenShareAudio,
|
||||
source
|
||||
});
|
||||
};
|
||||
|
||||
dispatch(showDesktopPicker(options, onSourceChoose));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -1482,6 +1513,21 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that a custom notification action has been triggered.
|
||||
*
|
||||
* @param {string} actionUuid - The UUID of the action that has been triggered.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyCustomNotificationActionTriggered(actionUuid) {
|
||||
this._sendEvent({
|
||||
name: 'custom-notification-action-triggered',
|
||||
data: {
|
||||
id: actionUuid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that the list of sharing participants changed.
|
||||
*
|
||||
|
||||
14
modules/API/external/external_api.js
vendored
14
modules/API/external/external_api.js
vendored
@@ -114,6 +114,7 @@ const events = {
|
||||
'compute-pressure-changed': 'computePressureChanged',
|
||||
'conference-created-timestamp': 'conferenceCreatedTimestamp',
|
||||
'content-sharing-participants-changed': 'contentSharingParticipantsChanged',
|
||||
'custom-notification-action-triggered': 'customNotificationActionTriggered',
|
||||
'data-channel-closed': 'dataChannelClosed',
|
||||
'data-channel-opened': 'dataChannelOpened',
|
||||
'device-list-changed': 'deviceListChanged',
|
||||
@@ -1243,7 +1244,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* @returns {Promise}
|
||||
*
|
||||
* TODO: should be removed after we make sure that all Electron clients use only versions
|
||||
* after with the legacy SS suport was removed from the electron SDK. If we remove it now the SS for Electron
|
||||
* after with the legacy SS support was removed from the electron SDK. If we remove it now the SS for Electron
|
||||
* clients with older versions wont work.
|
||||
*/
|
||||
_isNewElectronScreensharingSupported() {
|
||||
@@ -1455,4 +1456,15 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
setVirtualBackground(enabled, backgroundImage) {
|
||||
this.executeCommand('setVirtualBackground', enabled, backgroundImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the desktop picker. This is invoked by the Electron SDK when gDM is used.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_openDesktopPicker() {
|
||||
return this._transport.sendRequest({
|
||||
name: 'open-desktop-picker'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,9 @@ UI.unbindEvents = () => {
|
||||
* @param {string} name etherpad id
|
||||
*/
|
||||
UI.initEtherpad = name => {
|
||||
const etherpadBaseUrl = sanitizeUrl(config.etherpad_base);
|
||||
const { getState, dispatch } = APP.store;
|
||||
const configState = getState()['features/base/config'];
|
||||
const etherpadBaseUrl = sanitizeUrl(configState.etherpad_base);
|
||||
|
||||
if (etherpadManager || !etherpadBaseUrl || !name) {
|
||||
return;
|
||||
@@ -131,9 +133,9 @@ UI.initEtherpad = name => {
|
||||
|
||||
const url = new URL(name, etherpadBaseUrl);
|
||||
|
||||
APP.store.dispatch(setDocumentUrl(url.toString()));
|
||||
dispatch(setDocumentUrl(url.toString()));
|
||||
|
||||
if (config.openSharedDocumentOnJoin) {
|
||||
if (configState.openSharedDocumentOnJoin) {
|
||||
etherpadManager.toggleEtherpad();
|
||||
}
|
||||
};
|
||||
|
||||
242
package-lock.json
generated
242
package-lock.json
generated
@@ -62,7 +62,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1915.0.0+6e9b9c01/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -77,7 +77,7 @@
|
||||
"react-focus-on": "3.8.1",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "0.75.4",
|
||||
"react-native": "0.75.5",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
@@ -128,7 +128,7 @@
|
||||
"@babel/preset-env": "7.25.9",
|
||||
"@babel/preset-react": "7.25.9",
|
||||
"@jitsi/eslint-config": "5.0.9",
|
||||
"@react-native/metro-config": "0.75.4",
|
||||
"@react-native/metro-config": "0.75.5",
|
||||
"@stylistic/eslint-plugin": "2.12.1",
|
||||
"@types/amplitude-js": "8.16.5",
|
||||
"@types/audioworklet": "0.0.29",
|
||||
@@ -5959,30 +5959,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/assets-registry": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.4.tgz",
|
||||
"integrity": "sha512-WX6/LNHwyjislSFM+h3qQjBiPaXXPJW5ZV4TdgNKb6QOPO0g1KGYRQj44cI2xSpZ3fcWrvQFZfQgSMbVK9Sg7A==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.5.tgz",
|
||||
"integrity": "sha512-5Arp90qqwT4svF9izcFxM2tiDZlHSVKBuLT6gS1FbOABzHkKDK06Pv+CWNKZ2TM0l7qxRxNAd6e86tvnkMlGZw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/babel-plugin-codegen": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.4.tgz",
|
||||
"integrity": "sha512-gu5ZRIdr7+ufi09DJROhfDtbF4biTnCDJqtqcmtsku4cXOXPHE36QbC/vAmKEZ0PMPURBI8lwF2wfaeHLn7gig==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.5.tgz",
|
||||
"integrity": "sha512-WUYzABcKhjCYCFsMTkvJ4CPSSeQ0ayLoIQjN87NtgrKzRq6yISTxF2J/2vgUuJgT99U7J6x6AgdNqlQ/U6M+qw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-native/codegen": "0.75.4"
|
||||
"@react-native/codegen": "0.75.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/babel-preset": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.4.tgz",
|
||||
"integrity": "sha512-UtyYCDJ3rZIeggyFEfh/q5t/FZ5a1h9F8EI37Nbrwyk/OKPH+1XS4PbHROHJzBARlJwOAfmT75+ovYUO0eakJA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.5.tgz",
|
||||
"integrity": "sha512-VEJvWUu2zg9PacO6RL3n10TQ05IdbTIKiCpRAulexSAZZamURelX8Ko9VbWpIP0Wg/zFyYoDwsKOhCe+rFfhvA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
@@ -6027,7 +6027,7 @@
|
||||
"@babel/plugin-transform-typescript": "^7.5.0",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.0.0",
|
||||
"@babel/template": "^7.0.0",
|
||||
"@react-native/babel-plugin-codegen": "0.75.4",
|
||||
"@react-native/babel-plugin-codegen": "0.75.5",
|
||||
"babel-plugin-transform-flow-enums": "^0.0.2",
|
||||
"react-refresh": "^0.14.0"
|
||||
},
|
||||
@@ -6048,9 +6048,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/codegen": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.4.tgz",
|
||||
"integrity": "sha512-0FplNAD/S5FUvm8YIn6uyarOcP4jdJPqWz17K4a/Gp2KSsG/JJKEskX3aj5wpePzVfNQl3WyvBJ0whODdCocIA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.5.tgz",
|
||||
"integrity": "sha512-xIhr7UpnUySYJGgQmLrTPtTcaDlri7YECkAJwcLAxdDu2KVJzGoxNdHa2poCkTLceDBxvL+3hYYsoevl1PD45w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.20.0",
|
||||
@@ -6085,15 +6085,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/community-cli-plugin": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.4.tgz",
|
||||
"integrity": "sha512-k/hevYPjEpW0MNVVyb3v9PJosOP+FzenS7+oqYNLXdEmgTnGHrAtYX9ABrJJgzeJt7I6g8g+RDvm8PSE+tnM5w==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.5.tgz",
|
||||
"integrity": "sha512-rtZqsTb+2gtPw8BZgJlZ7p5ycXSGu4uql1gxybcv9qNgqlsNBZofiIdZEAade67/GfeQEvy/UY96SvoIsmVW6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-native-community/cli-server-api": "14.1.0",
|
||||
"@react-native-community/cli-tools": "14.1.0",
|
||||
"@react-native/dev-middleware": "0.75.4",
|
||||
"@react-native/metro-babel-transformer": "0.75.4",
|
||||
"@react-native/dev-middleware": "0.75.5",
|
||||
"@react-native/metro-babel-transformer": "0.75.5",
|
||||
"chalk": "^4.0.0",
|
||||
"execa": "^5.1.1",
|
||||
"metro": "^0.80.3",
|
||||
@@ -6177,22 +6177,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/debugger-frontend": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.4.tgz",
|
||||
"integrity": "sha512-QfGurR5hV6bhMPn/6VxS2RomYrPRFGwA03jJr+zKyWHnxDAu5jOqYVyKAktIIbhYe5sPp78QVl1ZYuhcnsRbEw==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.5.tgz",
|
||||
"integrity": "sha512-/mW0ELovNVAdZZFs0JC9147zF/GlvgrFPHhLhkgT95lOir/8cACLvR1kdVnH59sAQw2zKxyrFbrwhO80zPfp9g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/dev-middleware": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.4.tgz",
|
||||
"integrity": "sha512-UhyBeQOG2wNcvrUGw3+IBrHBk/lIu7hHGmWt4j8W9Aqv9BwktHKkPyko+5A1yoUeO1O/VDnHWYqWeOejcA9wpQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.5.tgz",
|
||||
"integrity": "sha512-agVYocKP6YjW2g296jS1fipvC4U1vaykPnIgIgQfXhhVAYsFFJwO4Fh8oLcI82ceGc3mikv9cPrv8IrUoIWKsg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@isaacs/ttlcache": "^1.4.1",
|
||||
"@react-native/debugger-frontend": "0.75.4",
|
||||
"@react-native/debugger-frontend": "0.75.5",
|
||||
"chrome-launcher": "^0.15.2",
|
||||
"chromium-edge-launcher": "^0.2.0",
|
||||
"connect": "^3.6.5",
|
||||
@@ -6224,31 +6224,31 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-native/gradle-plugin": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.4.tgz",
|
||||
"integrity": "sha512-kKTmw7cF7p1raT30DC0L6N+xiVXN7dlRy0J+hYPiCRRVHplwgvyS7pszjxfzwXmHFqOxwpxQVI3du8opsma1Mg==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.5.tgz",
|
||||
"integrity": "sha512-s6lPOVPLoGdQNbfgkVHvCC5Mg/mdTkg+5l5gChG6tNI4LZ6NVvaN0Ettt8bIKMoYlExsXQ2zpdBLopnjro5JgA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/js-polyfills": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.4.tgz",
|
||||
"integrity": "sha512-NF5ID5FjcVHBYk1LQ4JMRjPmxBWEo4yoqW1m6vGOQZPT8D5Qs9afgx3f7gQatxbn3ivMh0FVbLW0zBx6LyxEzA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.5.tgz",
|
||||
"integrity": "sha512-zDMMq2mtWZ1/P3CnOguBucMSsvzPSDuLHZPn33DjZv1VFSOjqyAbGy9F7ZEQ0Y+vNiApOH9tPcsBZRFuTATzng==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/metro-babel-transformer": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.4.tgz",
|
||||
"integrity": "sha512-O0WMW/K8Ny/MAAeRebqGEQhrbzcioxcPHZtos+EH2hWeBTEKHQV8fMYYxfYDabpr392qdhSBwg3LlXUD4U3PXQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.5.tgz",
|
||||
"integrity": "sha512-As/3zryghF12L983GaoBIgJFsDsepZfMCGGYz3zsfRqh4JjYaaGR5QAoWkk+NE+VDOwnvDp3zoVYtAqsu3wPbQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@react-native/babel-preset": "0.75.4",
|
||||
"@react-native/babel-preset": "0.75.5",
|
||||
"hermes-parser": "0.22.0",
|
||||
"nullthrows": "^1.1.1"
|
||||
},
|
||||
@@ -6275,14 +6275,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/metro-config": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.75.4.tgz",
|
||||
"integrity": "sha512-gIIVlPUtZ1UKCxMJRtG88FoWS5REbI4YUmiyoM8eBUA85Zvk27b67iBX5Lkuxg8FGc7t9tjWQRpVGs2IK5uZpQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.75.5.tgz",
|
||||
"integrity": "sha512-OrWZtYEznRRoTa4Cjj7zN2F6fZqfgzased7UQt7qRx1/O8DTrdVJDin4FBJ0mD570jTFF4yLHXcpgTbthPlBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-native/js-polyfills": "0.75.4",
|
||||
"@react-native/metro-babel-transformer": "0.75.4",
|
||||
"@react-native/js-polyfills": "0.75.5",
|
||||
"@react-native/metro-babel-transformer": "0.75.5",
|
||||
"metro-config": "^0.80.3",
|
||||
"metro-runtime": "^0.80.3"
|
||||
},
|
||||
@@ -6291,15 +6291,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/normalize-colors": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.4.tgz",
|
||||
"integrity": "sha512-90QrQDLg0/k9xqYesaKuIkayOSjD+FKa0hsHollbwT5h3kuGMY+lU7UZxnb8tU55Y1PKdvjYxqQsYWI/ql79zA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.5.tgz",
|
||||
"integrity": "sha512-8yooxuLIwxI11O+pJ9LDtiaAiyXQeubROXALdAhU3kZQI49Nz0WB1ugLWj0Et+D8miD+79XPOFflcUqqoIY1nw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-native/virtualized-lists": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.4.tgz",
|
||||
"integrity": "sha512-iEauRiXjvWG/iOH8bV+9MfepCS+72cuL5rhkrenYZS0NUnDcNjF+wtaoS9+Gx5z1UJOfEXxSmyXRtQJZne8SnA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.5.tgz",
|
||||
"integrity": "sha512-B1XMDXgptPpeWmmZABverRE9tBjDYuP97wQ/FeU7gHhZ+cwD+w68mwFZxcivr5fDz0Z4FaGlM7pR6oGjEn8NXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"invariant": "^2.2.4",
|
||||
@@ -16909,8 +16909,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-POz5n9QppExZNX4ZguKoQOOLL0Gev2RL49ZtwiWs7QBHTat4gzBA9kYAeviRrmW4kVn7xcX0xeAxInbHDWs4lg==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1915.0.0+6e9b9c01/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-erPBz93xzWDIvW9EdvSfiraHFi0TMo1W68zxe7rKvIQWX1DCjmKxWKnxdq5WirSD7MXwoSIxgdX4PB7Wz3aTmg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -20457,22 +20457,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.4.tgz",
|
||||
"integrity": "sha512-Jehg4AMNIAXu9cn0/1jbTCoNg3tc+t6EekwucCalN8YoRmxGd/PY6osQTI/5fSAM40JQ4O8uv8Qg09Ycpb5sxQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.5.tgz",
|
||||
"integrity": "sha512-9vyUHhkmesL2A++b3dKuhprEq7Nh/1gKgB7MD0ybjCgI4ARob0+j6Myfphzes8fYRcVSAwuiNdp8H+N96+eXSA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jest/create-cache-key-function": "^29.6.3",
|
||||
"@react-native-community/cli": "14.1.0",
|
||||
"@react-native-community/cli-platform-android": "14.1.0",
|
||||
"@react-native-community/cli-platform-ios": "14.1.0",
|
||||
"@react-native/assets-registry": "0.75.4",
|
||||
"@react-native/codegen": "0.75.4",
|
||||
"@react-native/community-cli-plugin": "0.75.4",
|
||||
"@react-native/gradle-plugin": "0.75.4",
|
||||
"@react-native/js-polyfills": "0.75.4",
|
||||
"@react-native/normalize-colors": "0.75.4",
|
||||
"@react-native/virtualized-lists": "0.75.4",
|
||||
"@react-native/assets-registry": "0.75.5",
|
||||
"@react-native/codegen": "0.75.5",
|
||||
"@react-native/community-cli-plugin": "0.75.5",
|
||||
"@react-native/gradle-plugin": "0.75.5",
|
||||
"@react-native/js-polyfills": "0.75.5",
|
||||
"@react-native/normalize-colors": "0.75.5",
|
||||
"@react-native/virtualized-lists": "0.75.5",
|
||||
"abort-controller": "^3.0.0",
|
||||
"anser": "^1.4.9",
|
||||
"ansi-regex": "^5.0.0",
|
||||
@@ -29742,22 +29742,22 @@
|
||||
"integrity": "sha512-9hhIlnNMfB6NO2738M437egA1p6inKdXXazh0qf6HJeBuLBTePSecePCOkVPczivM1mglquYXWCHJlx4D3Z7xw=="
|
||||
},
|
||||
"@react-native/assets-registry": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.4.tgz",
|
||||
"integrity": "sha512-WX6/LNHwyjislSFM+h3qQjBiPaXXPJW5ZV4TdgNKb6QOPO0g1KGYRQj44cI2xSpZ3fcWrvQFZfQgSMbVK9Sg7A=="
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.5.tgz",
|
||||
"integrity": "sha512-5Arp90qqwT4svF9izcFxM2tiDZlHSVKBuLT6gS1FbOABzHkKDK06Pv+CWNKZ2TM0l7qxRxNAd6e86tvnkMlGZw=="
|
||||
},
|
||||
"@react-native/babel-plugin-codegen": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.4.tgz",
|
||||
"integrity": "sha512-gu5ZRIdr7+ufi09DJROhfDtbF4biTnCDJqtqcmtsku4cXOXPHE36QbC/vAmKEZ0PMPURBI8lwF2wfaeHLn7gig==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.5.tgz",
|
||||
"integrity": "sha512-WUYzABcKhjCYCFsMTkvJ4CPSSeQ0ayLoIQjN87NtgrKzRq6yISTxF2J/2vgUuJgT99U7J6x6AgdNqlQ/U6M+qw==",
|
||||
"requires": {
|
||||
"@react-native/codegen": "0.75.4"
|
||||
"@react-native/codegen": "0.75.5"
|
||||
}
|
||||
},
|
||||
"@react-native/babel-preset": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.4.tgz",
|
||||
"integrity": "sha512-UtyYCDJ3rZIeggyFEfh/q5t/FZ5a1h9F8EI37Nbrwyk/OKPH+1XS4PbHROHJzBARlJwOAfmT75+ovYUO0eakJA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.5.tgz",
|
||||
"integrity": "sha512-VEJvWUu2zg9PacO6RL3n10TQ05IdbTIKiCpRAulexSAZZamURelX8Ko9VbWpIP0Wg/zFyYoDwsKOhCe+rFfhvA==",
|
||||
"requires": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||
@@ -29801,7 +29801,7 @@
|
||||
"@babel/plugin-transform-typescript": "^7.5.0",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.0.0",
|
||||
"@babel/template": "^7.0.0",
|
||||
"@react-native/babel-plugin-codegen": "0.75.4",
|
||||
"@react-native/babel-plugin-codegen": "0.75.5",
|
||||
"babel-plugin-transform-flow-enums": "^0.0.2",
|
||||
"react-refresh": "^0.14.0"
|
||||
},
|
||||
@@ -29814,9 +29814,9 @@
|
||||
}
|
||||
},
|
||||
"@react-native/codegen": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.4.tgz",
|
||||
"integrity": "sha512-0FplNAD/S5FUvm8YIn6uyarOcP4jdJPqWz17K4a/Gp2KSsG/JJKEskX3aj5wpePzVfNQl3WyvBJ0whODdCocIA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.5.tgz",
|
||||
"integrity": "sha512-xIhr7UpnUySYJGgQmLrTPtTcaDlri7YECkAJwcLAxdDu2KVJzGoxNdHa2poCkTLceDBxvL+3hYYsoevl1PD45w==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.20.0",
|
||||
"glob": "^7.1.1",
|
||||
@@ -29844,14 +29844,14 @@
|
||||
}
|
||||
},
|
||||
"@react-native/community-cli-plugin": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.4.tgz",
|
||||
"integrity": "sha512-k/hevYPjEpW0MNVVyb3v9PJosOP+FzenS7+oqYNLXdEmgTnGHrAtYX9ABrJJgzeJt7I6g8g+RDvm8PSE+tnM5w==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.5.tgz",
|
||||
"integrity": "sha512-rtZqsTb+2gtPw8BZgJlZ7p5ycXSGu4uql1gxybcv9qNgqlsNBZofiIdZEAade67/GfeQEvy/UY96SvoIsmVW6A==",
|
||||
"requires": {
|
||||
"@react-native-community/cli-server-api": "14.1.0",
|
||||
"@react-native-community/cli-tools": "14.1.0",
|
||||
"@react-native/dev-middleware": "0.75.4",
|
||||
"@react-native/metro-babel-transformer": "0.75.4",
|
||||
"@react-native/dev-middleware": "0.75.5",
|
||||
"@react-native/metro-babel-transformer": "0.75.5",
|
||||
"chalk": "^4.0.0",
|
||||
"execa": "^5.1.1",
|
||||
"metro": "^0.80.3",
|
||||
@@ -29907,17 +29907,17 @@
|
||||
}
|
||||
},
|
||||
"@react-native/debugger-frontend": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.4.tgz",
|
||||
"integrity": "sha512-QfGurR5hV6bhMPn/6VxS2RomYrPRFGwA03jJr+zKyWHnxDAu5jOqYVyKAktIIbhYe5sPp78QVl1ZYuhcnsRbEw=="
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.5.tgz",
|
||||
"integrity": "sha512-/mW0ELovNVAdZZFs0JC9147zF/GlvgrFPHhLhkgT95lOir/8cACLvR1kdVnH59sAQw2zKxyrFbrwhO80zPfp9g=="
|
||||
},
|
||||
"@react-native/dev-middleware": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.4.tgz",
|
||||
"integrity": "sha512-UhyBeQOG2wNcvrUGw3+IBrHBk/lIu7hHGmWt4j8W9Aqv9BwktHKkPyko+5A1yoUeO1O/VDnHWYqWeOejcA9wpQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.5.tgz",
|
||||
"integrity": "sha512-agVYocKP6YjW2g296jS1fipvC4U1vaykPnIgIgQfXhhVAYsFFJwO4Fh8oLcI82ceGc3mikv9cPrv8IrUoIWKsg==",
|
||||
"requires": {
|
||||
"@isaacs/ttlcache": "^1.4.1",
|
||||
"@react-native/debugger-frontend": "0.75.4",
|
||||
"@react-native/debugger-frontend": "0.75.5",
|
||||
"chrome-launcher": "^0.15.2",
|
||||
"chromium-edge-launcher": "^0.2.0",
|
||||
"connect": "^3.6.5",
|
||||
@@ -29946,22 +29946,22 @@
|
||||
}
|
||||
},
|
||||
"@react-native/gradle-plugin": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.4.tgz",
|
||||
"integrity": "sha512-kKTmw7cF7p1raT30DC0L6N+xiVXN7dlRy0J+hYPiCRRVHplwgvyS7pszjxfzwXmHFqOxwpxQVI3du8opsma1Mg=="
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.5.tgz",
|
||||
"integrity": "sha512-s6lPOVPLoGdQNbfgkVHvCC5Mg/mdTkg+5l5gChG6tNI4LZ6NVvaN0Ettt8bIKMoYlExsXQ2zpdBLopnjro5JgA=="
|
||||
},
|
||||
"@react-native/js-polyfills": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.4.tgz",
|
||||
"integrity": "sha512-NF5ID5FjcVHBYk1LQ4JMRjPmxBWEo4yoqW1m6vGOQZPT8D5Qs9afgx3f7gQatxbn3ivMh0FVbLW0zBx6LyxEzA=="
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.5.tgz",
|
||||
"integrity": "sha512-zDMMq2mtWZ1/P3CnOguBucMSsvzPSDuLHZPn33DjZv1VFSOjqyAbGy9F7ZEQ0Y+vNiApOH9tPcsBZRFuTATzng=="
|
||||
},
|
||||
"@react-native/metro-babel-transformer": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.4.tgz",
|
||||
"integrity": "sha512-O0WMW/K8Ny/MAAeRebqGEQhrbzcioxcPHZtos+EH2hWeBTEKHQV8fMYYxfYDabpr392qdhSBwg3LlXUD4U3PXQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.5.tgz",
|
||||
"integrity": "sha512-As/3zryghF12L983GaoBIgJFsDsepZfMCGGYz3zsfRqh4JjYaaGR5QAoWkk+NE+VDOwnvDp3zoVYtAqsu3wPbQ==",
|
||||
"requires": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@react-native/babel-preset": "0.75.4",
|
||||
"@react-native/babel-preset": "0.75.5",
|
||||
"hermes-parser": "0.22.0",
|
||||
"nullthrows": "^1.1.1"
|
||||
},
|
||||
@@ -29982,26 +29982,26 @@
|
||||
}
|
||||
},
|
||||
"@react-native/metro-config": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.75.4.tgz",
|
||||
"integrity": "sha512-gIIVlPUtZ1UKCxMJRtG88FoWS5REbI4YUmiyoM8eBUA85Zvk27b67iBX5Lkuxg8FGc7t9tjWQRpVGs2IK5uZpQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.75.5.tgz",
|
||||
"integrity": "sha512-OrWZtYEznRRoTa4Cjj7zN2F6fZqfgzased7UQt7qRx1/O8DTrdVJDin4FBJ0mD570jTFF4yLHXcpgTbthPlBbA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@react-native/js-polyfills": "0.75.4",
|
||||
"@react-native/metro-babel-transformer": "0.75.4",
|
||||
"@react-native/js-polyfills": "0.75.5",
|
||||
"@react-native/metro-babel-transformer": "0.75.5",
|
||||
"metro-config": "^0.80.3",
|
||||
"metro-runtime": "^0.80.3"
|
||||
}
|
||||
},
|
||||
"@react-native/normalize-colors": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.4.tgz",
|
||||
"integrity": "sha512-90QrQDLg0/k9xqYesaKuIkayOSjD+FKa0hsHollbwT5h3kuGMY+lU7UZxnb8tU55Y1PKdvjYxqQsYWI/ql79zA=="
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.5.tgz",
|
||||
"integrity": "sha512-8yooxuLIwxI11O+pJ9LDtiaAiyXQeubROXALdAhU3kZQI49Nz0WB1ugLWj0Et+D8miD+79XPOFflcUqqoIY1nw=="
|
||||
},
|
||||
"@react-native/virtualized-lists": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.4.tgz",
|
||||
"integrity": "sha512-iEauRiXjvWG/iOH8bV+9MfepCS+72cuL5rhkrenYZS0NUnDcNjF+wtaoS9+Gx5z1UJOfEXxSmyXRtQJZne8SnA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.5.tgz",
|
||||
"integrity": "sha512-B1XMDXgptPpeWmmZABverRE9tBjDYuP97wQ/FeU7gHhZ+cwD+w68mwFZxcivr5fDz0Z4FaGlM7pR6oGjEn8NXA==",
|
||||
"requires": {
|
||||
"invariant": "^2.2.4",
|
||||
"nullthrows": "^1.1.1"
|
||||
@@ -37637,8 +37637,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-POz5n9QppExZNX4ZguKoQOOLL0Gev2RL49ZtwiWs7QBHTat4gzBA9kYAeviRrmW4kVn7xcX0xeAxInbHDWs4lg==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1915.0.0+6e9b9c01/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-erPBz93xzWDIvW9EdvSfiraHFi0TMo1W68zxe7rKvIQWX1DCjmKxWKnxdq5WirSD7MXwoSIxgdX4PB7Wz3aTmg==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.2.1",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
@@ -40182,21 +40182,21 @@
|
||||
}
|
||||
},
|
||||
"react-native": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.4.tgz",
|
||||
"integrity": "sha512-Jehg4AMNIAXu9cn0/1jbTCoNg3tc+t6EekwucCalN8YoRmxGd/PY6osQTI/5fSAM40JQ4O8uv8Qg09Ycpb5sxQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.5.tgz",
|
||||
"integrity": "sha512-9vyUHhkmesL2A++b3dKuhprEq7Nh/1gKgB7MD0ybjCgI4ARob0+j6Myfphzes8fYRcVSAwuiNdp8H+N96+eXSA==",
|
||||
"requires": {
|
||||
"@jest/create-cache-key-function": "^29.6.3",
|
||||
"@react-native-community/cli": "14.1.0",
|
||||
"@react-native-community/cli-platform-android": "14.1.0",
|
||||
"@react-native-community/cli-platform-ios": "14.1.0",
|
||||
"@react-native/assets-registry": "0.75.4",
|
||||
"@react-native/codegen": "0.75.4",
|
||||
"@react-native/community-cli-plugin": "0.75.4",
|
||||
"@react-native/gradle-plugin": "0.75.4",
|
||||
"@react-native/js-polyfills": "0.75.4",
|
||||
"@react-native/normalize-colors": "0.75.4",
|
||||
"@react-native/virtualized-lists": "0.75.4",
|
||||
"@react-native/assets-registry": "0.75.5",
|
||||
"@react-native/codegen": "0.75.5",
|
||||
"@react-native/community-cli-plugin": "0.75.5",
|
||||
"@react-native/gradle-plugin": "0.75.5",
|
||||
"@react-native/js-polyfills": "0.75.5",
|
||||
"@react-native/normalize-colors": "0.75.5",
|
||||
"@react-native/virtualized-lists": "0.75.5",
|
||||
"abort-controller": "^3.0.0",
|
||||
"anser": "^1.4.9",
|
||||
"ansi-regex": "^5.0.0",
|
||||
|
||||
11
package.json
11
package.json
@@ -68,7 +68,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1915.0.0+6e9b9c01/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -83,7 +83,7 @@
|
||||
"react-focus-on": "3.8.1",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "0.75.4",
|
||||
"react-native": "0.75.5",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
@@ -134,7 +134,7 @@
|
||||
"@babel/preset-env": "7.25.9",
|
||||
"@babel/preset-react": "7.25.9",
|
||||
"@jitsi/eslint-config": "5.0.9",
|
||||
"@react-native/metro-config": "0.75.4",
|
||||
"@react-native/metro-config": "0.75.5",
|
||||
"@stylistic/eslint-plugin": "2.12.1",
|
||||
"@types/amplitude-js": "8.16.5",
|
||||
"@types/audioworklet": "0.0.29",
|
||||
@@ -226,8 +226,11 @@
|
||||
"test-ff-single": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.firefox.conf.ts --spec",
|
||||
"test-ff": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.firefox.conf.ts",
|
||||
"test-dev": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.dev.conf.ts",
|
||||
"test-dev-single": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.dev.conf.ts --spec",
|
||||
"test-grid": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.grid.conf.ts",
|
||||
"test-grid-single": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.grid.conf.ts --spec"
|
||||
"test-grid-single": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.grid.conf.ts --spec",
|
||||
"test-grid-ff": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.grid.firefox.conf.ts",
|
||||
"test-grid-ff-single": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.grid.firefox.conf.ts --spec"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "17.0.14",
|
||||
|
||||
@@ -152,30 +152,6 @@ export async function createHandlers({ getState }: IStore) {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a url is a data URL or not.
|
||||
*
|
||||
* @param {string} url - The URL to be checked.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isDataURL(url?: string): boolean {
|
||||
if (typeof url !== 'string') { // The icon will be ignored
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const urlObject = new URL(url);
|
||||
|
||||
if (urlObject.protocol === 'data:') {
|
||||
return false;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits JitsiMeetJS.analytics by setting permanent properties and setting the handlers from the loaded scripts.
|
||||
* NOTE: Has to be used after JitsiMeetJS.init. Otherwise analytics will be null.
|
||||
@@ -208,20 +184,11 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
|
||||
isPromotedFromVisitor?: boolean;
|
||||
isVisitor?: boolean;
|
||||
overwritesCustomButtonsWithURL?: boolean;
|
||||
overwritesCustomParticipantButtonsWithURL?: boolean;
|
||||
overwritesDefaultLogoUrl?: boolean;
|
||||
overwritesDeploymentUrls?: boolean;
|
||||
overwritesEtherpadBase?: boolean;
|
||||
overwritesHosts?: boolean;
|
||||
overwritesIceServers?: boolean;
|
||||
overwritesLiveStreamingUrls?: boolean;
|
||||
overwritesPeopleSearchUrl?: boolean;
|
||||
overwritesPrejoinConfigICEUrl?: boolean;
|
||||
overwritesSalesforceUrl?: boolean;
|
||||
overwritesSupportUrl?: boolean;
|
||||
overwritesWatchRTCConfigParams?: boolean;
|
||||
overwritesWatchRTCProxyUrl?: boolean;
|
||||
overwritesWatchRTCWSUrl?: boolean;
|
||||
server?: string;
|
||||
tenant?: string;
|
||||
wasLobbyVisible?: boolean;
|
||||
@@ -263,27 +230,8 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
|
||||
// TODO: Temporary metric. To be removed once we don't need it.
|
||||
permanentProperties.overwritesSupportUrl = 'interfaceConfig.SUPPORT_URL' in params;
|
||||
permanentProperties.overwritesSalesforceUrl = 'config.salesforceUrl' in params;
|
||||
permanentProperties.overwritesPeopleSearchUrl = 'config.peopleSearchUrl' in params;
|
||||
permanentProperties.overwritesDefaultLogoUrl = 'config.defaultLogoUrl' in params;
|
||||
permanentProperties.overwritesEtherpadBase = 'config.etherpad_base' in params;
|
||||
const hosts = params['config.hosts'] ?? {};
|
||||
const hostsProps = [ 'anonymousdomain', 'authdomain', 'domain', 'focus', 'muc', 'visitorFocus' ];
|
||||
|
||||
permanentProperties.overwritesHosts = 'config.hosts' in params
|
||||
|| Boolean(hostsProps.find(p => `config.hosts.${p}` in params || (typeof hosts === 'object' && p in hosts)));
|
||||
|
||||
permanentProperties.overwritesWatchRTCConfigParams = 'config.watchRTCConfigParams' in params;
|
||||
const watchRTCConfigParams = params['config.watchRTCConfigParams'] ?? {};
|
||||
|
||||
permanentProperties.overwritesWatchRTCProxyUrl = ('config.watchRTCConfigParams.proxyUrl' in params)
|
||||
|| (typeof watchRTCConfigParams === 'object' && 'proxyUrl' in watchRTCConfigParams);
|
||||
permanentProperties.overwritesWatchRTCWSUrl = ('config.watchRTCConfigParams.wsUrl' in params)
|
||||
|| (typeof watchRTCConfigParams === 'object' && 'wsUrl' in watchRTCConfigParams);
|
||||
|
||||
const prejoinConfig = params['config.prejoinConfig'] ?? {};
|
||||
|
||||
permanentProperties.overwritesPrejoinConfigICEUrl = ('config.prejoinConfig.preCallTestICEUrl' in params)
|
||||
|| (typeof prejoinConfig === 'object' && 'preCallTestICEUrl' in prejoinConfig);
|
||||
const deploymentUrlsConfig = params['config.deploymentUrls'] ?? {};
|
||||
|
||||
permanentProperties.overwritesDeploymentUrls
|
||||
@@ -305,17 +253,7 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
|
||||
)
|
||||
);
|
||||
|
||||
permanentProperties.overwritesIceServers = Boolean(Object.keys(params).find(k => k.startsWith('iceServers')));
|
||||
|
||||
const customToolbarButtons = params['config.customToolbarButtons'] ?? [];
|
||||
|
||||
permanentProperties.overwritesCustomButtonsWithURL = Boolean(
|
||||
customToolbarButtons.find(({ icon }: { icon: string; }) => isDataURL(icon)));
|
||||
|
||||
const customParticipantMenuButtons = params['config.customParticipantMenuButtons'] ?? [];
|
||||
|
||||
permanentProperties.overwritesCustomParticipantButtonsWithURL = Boolean(
|
||||
customParticipantMenuButtons.find(({ icon }: { icon: string; }) => isDataURL(icon)));
|
||||
permanentProperties.overwritesCustomButtonsWithURL = 'config.customToolbarButtons' in params;
|
||||
|
||||
// Optionally, include local deployment information based on the
|
||||
// contents of window.config.deploymentInfo.
|
||||
|
||||
@@ -13,6 +13,7 @@ import '../mobile/react-native-sdk/middleware';
|
||||
import '../mobile/watchos/middleware';
|
||||
import '../share-room/middleware';
|
||||
import '../shared-video/middleware';
|
||||
import '../toolbox/middleware.native';
|
||||
import '../whiteboard/middleware.native';
|
||||
|
||||
import './middlewares.any';
|
||||
|
||||
@@ -75,7 +75,7 @@ export const _getTokenAuthState = (
|
||||
const params = parseURLParams(locationURL);
|
||||
|
||||
for (const key of Object.keys(params)) {
|
||||
// we allow only config and interfaceConfig overrides in the state
|
||||
// we allow only config, interfaceConfig and iceServers overrides in the state
|
||||
if (key.startsWith('config.') || key.startsWith('interfaceConfig.') || key.startsWith('iceServers.')) {
|
||||
// @ts-ignore
|
||||
state[key] = params[key];
|
||||
|
||||
@@ -4,6 +4,7 @@ import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
|
||||
import { getConferenceState } from '../base/conference/functions';
|
||||
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE, MediaType } from '../base/media/constants';
|
||||
import { isAudioMuted, isVideoMuted } from '../base/media/functions';
|
||||
import { PARTICIPANT_UPDATED } from '../base/participants/actionTypes';
|
||||
import { raiseHand } from '../base/participants/actions';
|
||||
import {
|
||||
@@ -208,24 +209,46 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
state => state['features/base/conference'].conference,
|
||||
(conference, { dispatch }, previousConference) => {
|
||||
(conference, { dispatch, getState }, previousConference) => {
|
||||
if (conference && !previousConference) {
|
||||
// local participant is allowed to unmute
|
||||
conference.on(JitsiConferenceEvents.AV_MODERATION_APPROVED, ({ mediaType }: { mediaType: MediaType; }) => {
|
||||
dispatch(localParticipantApproved(mediaType));
|
||||
|
||||
// Audio & video moderation are both enabled at the same time.
|
||||
// Avoid displaying 2 different notifications.
|
||||
if (mediaType === MEDIA_TYPE.AUDIO) {
|
||||
dispatch(showNotification({
|
||||
titleKey: 'notify.hostAskedUnmute',
|
||||
sticky: true,
|
||||
customActionNameKey: [ 'notify.unmute' ],
|
||||
customActionHandler: [ () => dispatch(muteLocal(false, MEDIA_TYPE.AUDIO)) ],
|
||||
uid: ASKED_TO_UNMUTE_NOTIFICATION_ID
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
dispatch(playSound(ASKED_TO_UNMUTE_SOUND_ID));
|
||||
const customActionNameKey = [];
|
||||
const customActionHandler = [];
|
||||
|
||||
if ((mediaType === MEDIA_TYPE.AUDIO || getState()['features/av-moderation'].audioUnmuteApproved)
|
||||
&& isAudioMuted(getState())) {
|
||||
customActionNameKey.push('notify.unmute');
|
||||
customActionHandler.push(() => {
|
||||
dispatch(muteLocal(false, MEDIA_TYPE.AUDIO));
|
||||
dispatch(hideNotification(ASKED_TO_UNMUTE_NOTIFICATION_ID));
|
||||
});
|
||||
}
|
||||
|
||||
if ((mediaType === MEDIA_TYPE.VIDEO || getState()['features/av-moderation'].videoUnmuteApproved)
|
||||
&& isVideoMuted(getState())) {
|
||||
customActionNameKey.push('notify.unmuteVideo');
|
||||
customActionHandler.push(() => {
|
||||
dispatch(muteLocal(false, MEDIA_TYPE.VIDEO));
|
||||
dispatch(hideNotification(ASKED_TO_UNMUTE_NOTIFICATION_ID));
|
||||
|
||||
// lower hand as there will be no audio and change in dominant speaker to clear it
|
||||
dispatch(raiseHand(false));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
titleKey: 'notify.hostAskedUnmute',
|
||||
sticky: true,
|
||||
customActionNameKey,
|
||||
customActionHandler,
|
||||
uid: ASKED_TO_UNMUTE_NOTIFICATION_ID
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
|
||||
dispatch(playSound(ASKED_TO_UNMUTE_SOUND_ID));
|
||||
});
|
||||
|
||||
conference.on(JitsiConferenceEvents.AV_MODERATION_REJECTED, ({ mediaType }: { mediaType: MediaType; }) => {
|
||||
|
||||
@@ -177,7 +177,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
dispatch(showErrorNotification({
|
||||
description: 'Restart initiated because of a bridge failure',
|
||||
titleKey: 'dialog.sessionRestarted'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -190,7 +190,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
descriptionArguments: { msg },
|
||||
descriptionKey: msg ? 'dialog.connectErrorWithMsg' : 'dialog.connectError',
|
||||
titleKey: 'connection.CONNFAIL'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -199,7 +199,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
hideErrorSupportLink: true,
|
||||
descriptionKey: 'dialog.maxUsersLimitReached',
|
||||
titleKey: 'dialog.maxUsersLimitReachedTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
// In case of max users(it can be from a visitor node), let's restore
|
||||
// oldConfig if any as we will be back to the main prosody.
|
||||
@@ -236,7 +236,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
descriptionKey,
|
||||
hideErrorSupportLink: true,
|
||||
titleKey
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
}));
|
||||
|
||||
sendAnalytics(createNotAllowedErrorEvent(type, msg));
|
||||
|
||||
@@ -416,7 +416,7 @@ function _connectionFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
descriptionKey: errors ? 'dialog.tokenAuthFailedWithReasons' : 'dialog.tokenAuthFailed',
|
||||
descriptionArguments: { reason: errors },
|
||||
titleKey: 'dialog.tokenAuthFailedTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -292,6 +292,7 @@ export interface IConfig {
|
||||
disableAddingBackgroundImages?: boolean;
|
||||
disableAudioLevels?: boolean;
|
||||
disableBeforeUnloadHandlers?: boolean;
|
||||
disableCameraTintForeground?: boolean;
|
||||
disableChatSmileys?: boolean;
|
||||
disableDeepLinking?: boolean;
|
||||
disableFilmstripAutohiding?: boolean;
|
||||
@@ -479,6 +480,7 @@ export interface IConfig {
|
||||
noiseSuppression?: INoiseSuppressionConfig;
|
||||
noticeMessage?: string;
|
||||
notificationTimeouts?: {
|
||||
extraLong?: number;
|
||||
long?: number;
|
||||
medium?: number;
|
||||
short?: number;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { inIframe } from '../util/iframeUtils';
|
||||
|
||||
import extraConfigWhitelist from './extraConfigWhitelist';
|
||||
import inIframeConfigWhitelist from './inIframeConfigWhitelist';
|
||||
|
||||
/**
|
||||
* The config keys to whitelist, the keys that can be overridden.
|
||||
@@ -77,7 +80,6 @@ export default [
|
||||
'channelLastN',
|
||||
'connectionIndicators',
|
||||
'constraints',
|
||||
'customToolbarButtons',
|
||||
'deeplinking.disabled',
|
||||
'deeplinking.desktop.enabled',
|
||||
'defaultLocalDisplayName',
|
||||
@@ -91,6 +93,7 @@ export default [
|
||||
'disableAddingBackgroundImages',
|
||||
'disableAudioLevels',
|
||||
'disableBeforeUnloadHandlers',
|
||||
'disableCameraTintForeground',
|
||||
'disableChatSmileys',
|
||||
'disableDeepLinking',
|
||||
'disabledNotifications',
|
||||
@@ -242,4 +245,4 @@ export default [
|
||||
'webrtcIceTcpDisable',
|
||||
'webrtcIceUdpDisable',
|
||||
'whiteboard.enabled'
|
||||
].concat(extraConfigWhitelist);
|
||||
].concat(extraConfigWhitelist).concat(inIframe() ? inIframeConfigWhitelist : []);
|
||||
|
||||
@@ -327,6 +327,49 @@ export function setConfigFromURLParams(
|
||||
}
|
||||
|
||||
overrideConfigJSON(config, interfaceConfig, json);
|
||||
|
||||
// Print warning about depricated URL params
|
||||
if ('interfaceConfig.SUPPORT_URL' in params) {
|
||||
logger.warn('Using SUPPORT_URL interfaceConfig URL overwrite is deprecated.'
|
||||
+ ' Please use supportUrl from advanced branding!');
|
||||
}
|
||||
|
||||
if ('config.defaultLogoUrl' in params) {
|
||||
logger.warn('Using defaultLogoUrl config URL overwrite is deprecated.'
|
||||
+ ' Please use logoImageUrl from advanced branding!');
|
||||
}
|
||||
|
||||
const deploymentUrlsConfig = params['config.deploymentUrls'] ?? {};
|
||||
|
||||
if ('config.deploymentUrls.downloadAppsUrl' in params || 'config.deploymentUrls.userDocumentationURL' in params
|
||||
|| (typeof deploymentUrlsConfig === 'object'
|
||||
&& ('downloadAppsUrl' in deploymentUrlsConfig || 'userDocumentationURL' in deploymentUrlsConfig))) {
|
||||
logger.warn('Using deploymentUrls config URL overwrite is deprecated.'
|
||||
+ ' Please use downloadAppsUrl and/or userDocumentationURL from advanced branding!');
|
||||
}
|
||||
|
||||
const liveStreamingConfig = params['config.liveStreaming'] ?? {};
|
||||
|
||||
if (('interfaceConfig.LIVE_STREAMING_HELP_LINK' in params)
|
||||
|| ('config.liveStreaming.termsLink' in params)
|
||||
|| ('config.liveStreaming.dataPrivacyLink' in params)
|
||||
|| ('config.liveStreaming.helpLink' in params)
|
||||
|| (typeof params['config.liveStreaming'] === 'object' && 'config.liveStreaming' in params
|
||||
&& (
|
||||
'termsLink' in liveStreamingConfig
|
||||
|| 'dataPrivacyLink' in liveStreamingConfig
|
||||
|| 'helpLink' in liveStreamingConfig
|
||||
)
|
||||
)) {
|
||||
logger.warn('Using liveStreaming config URL overwrite and/or LIVE_STREAMING_HELP_LINK interfaceConfig URL'
|
||||
+ ' overwrite is deprecated. Please use liveStreaming from advanced branding!');
|
||||
}
|
||||
|
||||
if ('config.customToolbarButtons' in params) {
|
||||
logger.warn('Using customToolbarButtons config URL overwrite is deprecated.'
|
||||
+ ' Please use liveStreaming from advanced branding!');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* eslint-enable max-params */
|
||||
|
||||
4
react/features/base/config/inIframeConfigWhitelist.ts
Normal file
4
react/features/base/config/inIframeConfigWhitelist.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Additional config whitelist extending the original whitelist in the case where jitsi-meet is loaded in an iframe.
|
||||
*/
|
||||
export default [];
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Additional interface config whitelist extending the original whitelist in the case where jitsi-meet is loaded in an
|
||||
* iframe.
|
||||
*/
|
||||
export default [];
|
||||
@@ -1,4 +1,7 @@
|
||||
import { inIframe } from '../util/iframeUtils';
|
||||
|
||||
import extraInterfaceConfigWhitelistCopy from './extraInterfaceConfigWhitelist';
|
||||
import inIframeInterfaceConfigWhitelist from './inIframeInterfaceConfigWhitelist';
|
||||
|
||||
/**
|
||||
* The interface config keys to whitelist, the keys that can be overridden.
|
||||
@@ -51,4 +54,4 @@ export default [
|
||||
'VERTICAL_FILMSTRIP',
|
||||
'VIDEO_LAYOUT_FIT',
|
||||
'VIDEO_QUALITY_LABEL_DISABLED'
|
||||
].concat(extraInterfaceConfigWhitelistCopy);
|
||||
].concat(extraInterfaceConfigWhitelistCopy).concat(inIframe() ? inIframeInterfaceConfigWhitelist : []);
|
||||
|
||||
@@ -114,11 +114,15 @@ function _setConfig({ dispatch, getState }: IStore, next: Function, action: AnyA
|
||||
function _setDynamicBrandingData({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
const config: IConfig = {};
|
||||
const {
|
||||
customParticipantMenuButtons,
|
||||
customToolbarButtons,
|
||||
downloadAppsUrl,
|
||||
etherpadBase,
|
||||
liveStreamingDialogUrls = {},
|
||||
preCallTest = {},
|
||||
salesforceUrl,
|
||||
userDocumentationUrl
|
||||
userDocumentationUrl,
|
||||
peopleSearchUrl,
|
||||
} = action.value;
|
||||
|
||||
const { helpUrl, termsUrl, dataPrivacyUrl } = liveStreamingDialogUrls;
|
||||
@@ -154,6 +158,10 @@ function _setDynamicBrandingData({ dispatch }: IStore, next: Function, action: A
|
||||
config.salesforceUrl = salesforceUrl;
|
||||
}
|
||||
|
||||
if (peopleSearchUrl) {
|
||||
config.peopleSearchUrl = peopleSearchUrl;
|
||||
}
|
||||
|
||||
const { enabled, iceUrl } = preCallTest;
|
||||
|
||||
if (typeof enabled === 'boolean') {
|
||||
@@ -162,11 +170,24 @@ function _setDynamicBrandingData({ dispatch }: IStore, next: Function, action: A
|
||||
};
|
||||
}
|
||||
|
||||
if (etherpadBase) {
|
||||
// eslint-disable-next-line camelcase
|
||||
config.etherpad_base = etherpadBase;
|
||||
}
|
||||
|
||||
if (iceUrl) {
|
||||
config.prejoinConfig = config.prejoinConfig || {};
|
||||
config.prejoinConfig.preCallTestICEUrl = iceUrl;
|
||||
}
|
||||
|
||||
if (customToolbarButtons) {
|
||||
config.customToolbarButtons = customToolbarButtons;
|
||||
}
|
||||
|
||||
if (customParticipantMenuButtons) {
|
||||
config.customParticipantMenuButtons = customParticipantMenuButtons;
|
||||
}
|
||||
|
||||
dispatch(updateConfig(config));
|
||||
|
||||
return next(action);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { conferenceLeft, conferenceWillLeave, redirect } from '../conference/act
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { IConfigState } from '../config/reducer';
|
||||
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
|
||||
import { inIframe } from '../util/iframeUtils';
|
||||
import { parseURLParams } from '../util/parseURLParams';
|
||||
import {
|
||||
appendURLParam,
|
||||
@@ -119,7 +120,8 @@ export function constructOptions(state: IReduxState) {
|
||||
const params = parseURLParams(locationURL || '');
|
||||
const iceServersOverride = params['iceServers.replace'];
|
||||
|
||||
if (iceServersOverride) {
|
||||
// Allow iceServersOverride only when jitsi-meet is in an iframe.
|
||||
if (inIframe() && iceServersOverride) {
|
||||
options.iceServersOverride = iceServersOverride;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable require-jsdoc */
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { updateSettings } from '../settings/actions';
|
||||
@@ -157,7 +159,8 @@ export function getDevicesFromURL(state: IReduxState) {
|
||||
* @returns {Object} An object with the media devices split by type. The keys
|
||||
* are device type and the values are arrays with devices matching the device
|
||||
* type.
|
||||
*/
|
||||
*/
|
||||
// @ts-ignore
|
||||
export function groupDevicesByKind(devices: MediaDeviceInfo[]): IDevicesState['availableDevices'] {
|
||||
return {
|
||||
audioInput: devices.filter(device => device.kind === 'audioinput'),
|
||||
@@ -172,7 +175,10 @@ export function groupDevicesByKind(devices: MediaDeviceInfo[]): IDevicesState['a
|
||||
* @param {MediaDeviceInfo[]} devices - The devices to be filtered.
|
||||
* @returns {MediaDeviceInfo[]} - The filtered devices.
|
||||
*/
|
||||
// @ts-ignore
|
||||
export function filterIgnoredDevices(devices: MediaDeviceInfo[] = []) {
|
||||
|
||||
// @ts-ignore
|
||||
const ignoredDevices: MediaDeviceInfo[] = [];
|
||||
const filteredDevices = devices.filter(device => {
|
||||
if (!device.label) {
|
||||
@@ -201,6 +207,7 @@ export function filterIgnoredDevices(devices: MediaDeviceInfo[] = []) {
|
||||
* @param {MediaDeviceInfo[]} devices2 - Array with devices to be compared.
|
||||
* @returns {boolean} - True if the device arrays are different and false otherwise.
|
||||
*/
|
||||
// @ts-ignore
|
||||
export function areDevicesDifferent(devices1: MediaDeviceInfo[] = [], devices2: MediaDeviceInfo[] = []) {
|
||||
if (devices1.length !== devices2.length) {
|
||||
return true;
|
||||
@@ -304,6 +311,7 @@ export function getVideoDeviceIds(state: IReduxState) {
|
||||
* @param {MediaDeviceInfo[]} devices - The devices.
|
||||
* @returns {string}
|
||||
*/
|
||||
// @ts-ignore
|
||||
function devicesToStr(devices?: MediaDeviceInfo[]) {
|
||||
return devices?.map(device => `\t\t${device.label}[${device.deviceId}]`).join('\n');
|
||||
}
|
||||
@@ -315,6 +323,7 @@ function devicesToStr(devices?: MediaDeviceInfo[]) {
|
||||
* @param {string} title - The title that will be printed in the log.
|
||||
* @returns {void}
|
||||
*/
|
||||
// @ts-ignore
|
||||
export function logDevices(devices: MediaDeviceInfo[], title = '') {
|
||||
const deviceList = groupDevicesByKind(devices);
|
||||
const audioInputs = devicesToStr(deviceList.audioInput);
|
||||
|
||||
@@ -39,9 +39,10 @@ export default class JitsiMeetLogStorage {
|
||||
* <tt>false</tt> otherwise.
|
||||
*/
|
||||
isReady() {
|
||||
const { conference } = this.getState()['features/base/conference'];
|
||||
const { conference, error: conferenceError } = this.getState()['features/base/conference'];
|
||||
const { error: connectionError } = this.getState()['features/base/connection'];
|
||||
|
||||
return Boolean(conference);
|
||||
return Boolean(conference || conferenceError || connectionError);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AnyAction } from 'redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { APP_WILL_MOUNT } from '../app/actionTypes';
|
||||
import { CONFERENCE_JOINED } from '../conference/actionTypes';
|
||||
import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../conference/actionTypes';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { SET_CONFIG } from '../config/actionTypes';
|
||||
import JitsiMeetJS, {
|
||||
@@ -35,6 +35,15 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case CONFERENCE_JOINED:
|
||||
return _conferenceJoined(store, next, action);
|
||||
|
||||
case CONFERENCE_FAILED: {
|
||||
const result = next(action);
|
||||
const { logCollector } = store.getState()['features/base/logging'];
|
||||
|
||||
logCollector?.flush();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case LIB_WILL_INIT:
|
||||
return _libWillInit(store, next, action);
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ import { batch } from 'react-redux';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { approveParticipant } from '../../av-moderation/actions';
|
||||
import { approveParticipant, approveParticipantAudio, approveParticipantVideo } from '../../av-moderation/actions';
|
||||
import { UPDATE_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
|
||||
import { getBreakoutRooms } from '../../breakout-rooms/functions';
|
||||
import { toggleE2EE } from '../../e2ee/actions';
|
||||
import { MAX_MODE } from '../../e2ee/constants';
|
||||
import { showNotification } from '../../notifications/actions';
|
||||
import { hideNotification, showNotification } from '../../notifications/actions';
|
||||
import {
|
||||
LOCAL_RECORDING_NOTIFICATION_ID,
|
||||
NOTIFICATION_TIMEOUT_TYPE,
|
||||
@@ -782,20 +782,43 @@ function _raiseHandUpdated({ dispatch, getState }: IStore, conference: IJitsiCon
|
||||
|
||||
const isModerator = isLocalParticipantModerator(state);
|
||||
const participant = getParticipantById(state, participantId);
|
||||
let shouldDisplayAllowAction = false;
|
||||
let shouldDisplayAllowAudio = false;
|
||||
let shouldDisplayAllowVideo = false;
|
||||
|
||||
if (isModerator) {
|
||||
shouldDisplayAllowAction = isForceMuted(participant, MEDIA_TYPE.AUDIO, state)
|
||||
|| isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
|
||||
shouldDisplayAllowAudio = isForceMuted(participant, MEDIA_TYPE.AUDIO, state);
|
||||
shouldDisplayAllowVideo = isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
|
||||
}
|
||||
|
||||
let action;
|
||||
|
||||
if (shouldDisplayAllowAction) {
|
||||
if (shouldDisplayAllowAudio || shouldDisplayAllowVideo) {
|
||||
action = {
|
||||
customActionNameKey: [ 'notify.allowAction' ],
|
||||
customActionHandler: [ () => dispatch(approveParticipant(participantId)) ]
|
||||
customActionNameKey: [] as string[],
|
||||
customActionHandler: [] as Function[]
|
||||
};
|
||||
|
||||
if (shouldDisplayAllowAudio) {
|
||||
action.customActionNameKey.push('notify.allowAudio');
|
||||
action.customActionHandler.push(() => {
|
||||
dispatch(approveParticipantAudio(participantId));
|
||||
dispatch(hideNotification(RAISE_HAND_NOTIFICATION_ID));
|
||||
});
|
||||
}
|
||||
if (shouldDisplayAllowVideo) {
|
||||
action.customActionNameKey.push('notify.allowVideo');
|
||||
action.customActionHandler.push(() => {
|
||||
dispatch(approveParticipantVideo(participantId));
|
||||
dispatch(hideNotification(RAISE_HAND_NOTIFICATION_ID));
|
||||
});
|
||||
}
|
||||
if (shouldDisplayAllowAudio && shouldDisplayAllowVideo) {
|
||||
action.customActionNameKey.push('notify.allowBoth');
|
||||
action.customActionHandler.push(() => {
|
||||
dispatch(approveParticipant(participantId));
|
||||
dispatch(hideNotification(RAISE_HAND_NOTIFICATION_ID));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
action = {
|
||||
customActionNameKey: [ 'notify.viewParticipants' ],
|
||||
|
||||
@@ -5,7 +5,7 @@ import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import DeviceStatus from '../../../../prejoin/components/web/preview/DeviceStatus';
|
||||
import { isRoomNameEnabled } from '../../../../prejoin/functions';
|
||||
import { isRoomNameEnabled } from '../../../../prejoin/functions.web';
|
||||
import Toolbox from '../../../../toolbox/components/web/Toolbox';
|
||||
import { isButtonEnabled } from '../../../../toolbox/functions.web';
|
||||
import { getConferenceName } from '../../../conference/functions';
|
||||
|
||||
@@ -291,7 +291,7 @@ export function showNoDataFromSourceVideoError(jitsiTrack: any) {
|
||||
const notificationAction = dispatch(showErrorNotification({
|
||||
descriptionKey: 'dialog.cameraNotSendingData',
|
||||
titleKey: 'dialog.cameraNotSendingDataTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
notificationInfo = {
|
||||
uid: notificationAction?.uid
|
||||
@@ -838,16 +838,6 @@ export function toggleCamera() {
|
||||
const localVideoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||
const currentFacingMode = localVideoTrack.getCameraFacingMode();
|
||||
const { localFlipX } = state['features/base/settings'];
|
||||
|
||||
/**
|
||||
* FIXME: Ideally, we should be dispatching {@code replaceLocalTrack} here,
|
||||
* but it seems to not trigger the re-rendering of the local video on Chrome;
|
||||
* could be due to a plan B vs unified plan issue. Therefore, we use the legacy
|
||||
* method defined in conference.js that manually takes care of updating the local
|
||||
* video as well.
|
||||
*/
|
||||
await APP.conference.useVideoStream(null);
|
||||
|
||||
const targetFacingMode = currentFacingMode === CAMERA_FACING_MODE.USER
|
||||
? CAMERA_FACING_MODE.ENVIRONMENT
|
||||
: CAMERA_FACING_MODE.USER;
|
||||
@@ -857,7 +847,6 @@ export function toggleCamera() {
|
||||
|
||||
const newVideoTrack = await createLocalTrack('video', null, null, { facingMode: targetFacingMode });
|
||||
|
||||
// FIXME: See above.
|
||||
await APP.conference.useVideoStream(newVideoTrack);
|
||||
dispatch(replaceLocalTrack(localVideoTrack, newVideoTrack));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -157,9 +157,9 @@ async function _toggleScreenSharing(
|
||||
try {
|
||||
tracks = await createLocalTracksF(options) as any[];
|
||||
} catch (error) {
|
||||
dispatch(handleScreenSharingError(error, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
dispatch(handleScreenSharingError(error));
|
||||
|
||||
throw error;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,9 +171,9 @@ async function _toggleScreenSharing(
|
||||
desktopVideoTrack.dispose();
|
||||
|
||||
if (!desktopAudioTrack) {
|
||||
dispatch(handleScreenSharingError(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
dispatch(handleScreenSharingError(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK));
|
||||
|
||||
throw new Error(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK);
|
||||
return;
|
||||
}
|
||||
} else if (desktopVideoTrack) {
|
||||
if (localScreenshare) {
|
||||
@@ -457,7 +457,7 @@ export function displayErrorsForCreateInitialLocalTracks(errors: IInitialTracksE
|
||||
} = errors;
|
||||
|
||||
if (screenSharingError) {
|
||||
dispatch(handleScreenSharingError(screenSharingError, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
dispatch(handleScreenSharingError(screenSharingError));
|
||||
}
|
||||
if (audioOnlyError || videoOnlyError) {
|
||||
if (audioOnlyError) {
|
||||
@@ -476,12 +476,10 @@ export function displayErrorsForCreateInitialLocalTracks(errors: IInitialTracksE
|
||||
*
|
||||
* @private
|
||||
* @param {Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK} error - The error.
|
||||
* @param {NOTIFICATION_TIMEOUT_TYPE} timeout - The time for showing the notification.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function handleScreenSharingError(
|
||||
error: Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK,
|
||||
timeout: NOTIFICATION_TIMEOUT_TYPE) {
|
||||
error: Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
logger.error('failed to share local desktop', error);
|
||||
|
||||
@@ -508,6 +506,6 @@ export function handleScreenSharingError(
|
||||
dispatch(showErrorNotification({
|
||||
descriptionKey,
|
||||
titleKey
|
||||
}, timeout));
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ interface IState {
|
||||
/**
|
||||
* The id of the last read message.
|
||||
*/
|
||||
lastReadMessageId: string;
|
||||
lastReadMessageId: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -163,7 +163,7 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onFullScreenChange = this._onFullScreenChange.bind(this);
|
||||
this._onVidespaceTouchStart = this._onVidespaceTouchStart.bind(this);
|
||||
this._onVideospaceTouchStart = this._onVideospaceTouchStart.bind(this);
|
||||
this._setBackground = this._setBackground.bind(this);
|
||||
}
|
||||
|
||||
@@ -241,11 +241,11 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
className = { _layoutClassName }
|
||||
id = 'videoconference_page'
|
||||
onMouseMove = { isMobileBrowser() ? undefined : this._onShowToolbar }>
|
||||
<ConferenceInfo />
|
||||
{ _showPrejoin || _showLobby || <ConferenceInfo /> }
|
||||
<Notice />
|
||||
<div
|
||||
id = 'videospace'
|
||||
onTouchStart = { this._onVidespaceTouchStart }>
|
||||
onTouchStart = { this._onVideospaceTouchStart }>
|
||||
<LargeVideo />
|
||||
{
|
||||
_showPrejoin || _showLobby || (<>
|
||||
@@ -322,7 +322,7 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onVidespaceTouchStart() {
|
||||
_onVideospaceTouchStart() {
|
||||
this.props.dispatch(toggleToolboxVisible());
|
||||
}
|
||||
|
||||
|
||||
@@ -270,7 +270,14 @@ class DesktopPicker extends PureComponent<IProps, IState> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCloseModal(id = '', type?: string, screenShareAudio = false) {
|
||||
this.props.onSourceChoose(id, type, screenShareAudio);
|
||||
// Find the entire source object from the id. We need the name in order
|
||||
// to get getDisplayMedia working in Electron.
|
||||
const { sources } = this.state;
|
||||
|
||||
// @ts-ignore
|
||||
const source = sources.screen.concat(sources.window).find(s => s.id === id);
|
||||
|
||||
this.props.onSourceChoose(id, type, screenShareAudio, source);
|
||||
this.props.dispatch(hideDialog());
|
||||
}
|
||||
|
||||
|
||||
@@ -22,15 +22,19 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
backgroundColor,
|
||||
backgroundImageUrl,
|
||||
brandedIcons,
|
||||
customParticipantMenuButtons,
|
||||
customToolbarButtons,
|
||||
didPageUrl,
|
||||
downloadAppsUrl,
|
||||
etherpadBase,
|
||||
inviteDomain,
|
||||
labels,
|
||||
liveStreamingDialogUrls,
|
||||
peopleSearchUrl,
|
||||
salesforceUrl,
|
||||
sharedVideoAllowedURLDomains,
|
||||
supportUrl,
|
||||
userDocumentationUrl
|
||||
userDocumentationUrl,
|
||||
} = action.value;
|
||||
|
||||
action.value = {
|
||||
@@ -38,11 +42,15 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
backgroundColor,
|
||||
backgroundImageUrl,
|
||||
brandedIcons,
|
||||
customParticipantMenuButtons,
|
||||
customToolbarButtons,
|
||||
didPageUrl,
|
||||
downloadAppsUrl,
|
||||
etherpadBase,
|
||||
inviteDomain,
|
||||
labels,
|
||||
liveStreamingDialogUrls,
|
||||
peopleSearchUrl,
|
||||
salesforceUrl,
|
||||
sharedVideoAllowedURLDomains,
|
||||
supportUrl,
|
||||
|
||||
@@ -8,8 +8,8 @@ import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import Platform from '../../../base/react/Platform.native';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
||||
import { getHideSelfView } from '../../../base/settings/functions.any';
|
||||
import { isToolboxVisible } from '../../../toolbox/functions';
|
||||
import { setVisibleRemoteParticipants } from '../../actions';
|
||||
import { isToolboxVisible } from '../../../toolbox/functions.native';
|
||||
import { setVisibleRemoteParticipants } from '../../actions.native';
|
||||
import {
|
||||
getFilmstripDimensions,
|
||||
isFilmstripVisible,
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
setUserFilmstripWidth,
|
||||
setUserIsResizing,
|
||||
setVisibleRemoteParticipants
|
||||
} from '../../actions';
|
||||
} from '../../actions.web';
|
||||
import {
|
||||
ASPECT_RATIO_BREAKPOINT,
|
||||
DEFAULT_FILMSTRIP_WIDTH,
|
||||
@@ -39,10 +39,10 @@ import {
|
||||
} from '../../constants';
|
||||
import {
|
||||
getVerticalViewMaxWidth,
|
||||
isFilmstripDisabled,
|
||||
isStageFilmstripTopPanel,
|
||||
shouldRemoteVideosBeVisible
|
||||
} from '../../functions';
|
||||
import { isFilmstripDisabled } from '../../functions.web';
|
||||
} from '../../functions.web';
|
||||
|
||||
import AudioTracksContainer from './AudioTracksContainer';
|
||||
import Thumbnail from './Thumbnail';
|
||||
|
||||
@@ -1363,6 +1363,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any): Object {
|
||||
|
||||
// skip showing tint for owner participants that are screensharing.
|
||||
&& !screenshareParticipantIds.includes(id);
|
||||
const disableTintForeground = state['features/base/config'].disableCameraTintForeground ?? false;
|
||||
|
||||
return {
|
||||
_audioTrack,
|
||||
@@ -1384,7 +1385,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any): Object {
|
||||
_raisedHand: hasRaisedHand(participant),
|
||||
_stageFilmstripLayout: isStageFilmstripAvailable(state),
|
||||
_stageParticipantsVisible: _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW,
|
||||
_shouldDisplayTintBackground: shouldDisplayTintBackground,
|
||||
_shouldDisplayTintBackground: !disableTintForeground && shouldDisplayTintBackground,
|
||||
_thumbnailType: tileType,
|
||||
_videoObjectPosition: getVideoObjectPosition(state, participant?.id),
|
||||
_videoTrack,
|
||||
|
||||
@@ -190,7 +190,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
|
||||
}));
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'addPeople.failedToAdd'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}));
|
||||
} else if (!_callFlowsEnabled) {
|
||||
const invitedCount = invitees.length;
|
||||
let notificationProps: INotificationProps | undefined;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { debounce } from 'lodash-es';
|
||||
import { NativeEventEmitter, NativeModules } from 'react-native';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
// @ts-expect-error
|
||||
// @ts-ignore
|
||||
import { ENDPOINT_TEXT_MESSAGE_NAME } from '../../../../modules/API/constants';
|
||||
import { appNavigate } from '../../app/actions.native';
|
||||
import { IStore } from '../../app/types';
|
||||
@@ -32,8 +32,7 @@ import {
|
||||
JITSI_CONNECTION_URL_KEY
|
||||
} from '../../base/connection/constants';
|
||||
import { getURLWithoutParams } from '../../base/connection/utils';
|
||||
import {
|
||||
JitsiConferenceEvents } from '../../base/lib-jitsi-meet';
|
||||
import { JitsiConferenceEvents, JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
|
||||
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../../base/media/actionTypes';
|
||||
import { toggleCameraFacingMode } from '../../base/media/actions';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../../base/media/constants';
|
||||
@@ -51,6 +50,11 @@ import { getLocalTracks, isLocalTrackMuted } from '../../base/tracks/functions.n
|
||||
import { ITrack } from '../../base/tracks/types';
|
||||
import { CLOSE_CHAT, OPEN_CHAT } from '../../chat/actionTypes';
|
||||
import { closeChat, openChat, sendMessage, setPrivateMessageRecipient } from '../../chat/actions.native';
|
||||
import { isEnabled as isDropboxEnabled } from '../../dropbox/functions.native';
|
||||
import { hideNotification, showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../../notifications/constants';
|
||||
import { RECORDING_METADATA_ID, RECORDING_TYPES } from '../../recording/constants';
|
||||
import { getActiveSession } from '../../recording/functions';
|
||||
import { setRequestingSubtitles } from '../../subtitles/actions.any';
|
||||
import { CUSTOM_BUTTON_PRESSED } from '../../toolbox/actionTypes';
|
||||
import { muteLocal } from '../../video-menu/actions.native';
|
||||
@@ -419,6 +423,158 @@ function _registerForNativeEvents(store: IStore) {
|
||||
eventEmitter.addListener(ExternalAPI.TOGGLE_CAMERA, () => {
|
||||
dispatch(toggleCameraFacingMode());
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SHOW_NOTIFICATION,
|
||||
({ appearance, description, timeout, title, uid }: any) => {
|
||||
const validTypes = Object.values(NOTIFICATION_TYPE);
|
||||
const validTimeouts = Object.values(NOTIFICATION_TIMEOUT_TYPE);
|
||||
|
||||
if (!validTypes.includes(appearance)) {
|
||||
logger.error(`Invalid notification type "${appearance}". Expecting one of ${validTypes}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validTimeouts.includes(timeout)) {
|
||||
logger.error(`Invalid notification timeout "${timeout}". Expecting one of ${validTimeouts}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
appearance,
|
||||
description,
|
||||
title,
|
||||
uid
|
||||
}, timeout));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.HIDE_NOTIFICATION, ({ uid }: any) => {
|
||||
dispatch(hideNotification(uid));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.START_RECORDING, (
|
||||
{
|
||||
mode,
|
||||
dropboxToken,
|
||||
shouldShare,
|
||||
rtmpStreamKey,
|
||||
rtmpBroadcastID,
|
||||
youtubeStreamKey,
|
||||
youtubeBroadcastID,
|
||||
extraMetadata = {},
|
||||
transcription
|
||||
}: any) => {
|
||||
const state = store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (!conference) {
|
||||
logger.error('Conference is not defined');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (dropboxToken && !isDropboxEnabled(state)) {
|
||||
logger.error('Failed starting recording: dropbox is not enabled on this deployment');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.STREAM && !(youtubeStreamKey || rtmpStreamKey)) {
|
||||
logger.error('Failed starting recording: missing youtube or RTMP stream key');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let recordingConfig;
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.FILE) {
|
||||
const { recordingService } = state['features/base/config'];
|
||||
|
||||
if (!recordingService?.enabled && !dropboxToken) {
|
||||
logger.error('Failed starting recording: the recording service is not enabled');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (dropboxToken) {
|
||||
recordingConfig = {
|
||||
mode: JitsiRecordingConstants.mode.FILE,
|
||||
appData: JSON.stringify({
|
||||
'file_recording_metadata': {
|
||||
...extraMetadata,
|
||||
'upload_credentials': {
|
||||
'service_name': RECORDING_TYPES.DROPBOX,
|
||||
'token': dropboxToken
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
} else {
|
||||
recordingConfig = {
|
||||
mode: JitsiRecordingConstants.mode.FILE,
|
||||
appData: JSON.stringify({
|
||||
'file_recording_metadata': {
|
||||
...extraMetadata,
|
||||
'share': shouldShare
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
|
||||
recordingConfig = {
|
||||
broadcastId: youtubeBroadcastID || rtmpBroadcastID,
|
||||
mode: JitsiRecordingConstants.mode.STREAM,
|
||||
streamId: youtubeStreamKey || rtmpStreamKey
|
||||
};
|
||||
}
|
||||
|
||||
// Start audio / video recording, if requested.
|
||||
if (typeof recordingConfig !== 'undefined') {
|
||||
conference.startRecording(recordingConfig);
|
||||
}
|
||||
|
||||
if (transcription) {
|
||||
store.dispatch(setRequestingSubtitles(true, false, null));
|
||||
conference.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
|
||||
isTranscribingEnabled: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.STOP_RECORDING, ({ mode, transcription }: any) => {
|
||||
const state = store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (!conference) {
|
||||
logger.error('Conference is not defined');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (transcription) {
|
||||
store.dispatch(setRequestingSubtitles(false, false, null));
|
||||
conference.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
|
||||
isTranscribingEnabled: false
|
||||
});
|
||||
}
|
||||
|
||||
if (![ JitsiRecordingConstants.mode.FILE, JitsiRecordingConstants.mode.STREAM ].includes(mode)) {
|
||||
logger.error('Invalid recording mode provided!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const activeSession = getActiveSession(state, mode);
|
||||
|
||||
if (!activeSession?.id) {
|
||||
logger.error('No recording or streaming session found');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
conference.stopRecording(activeSession.id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,6 +595,10 @@ function _unregisterForNativeEvents() {
|
||||
eventEmitter.removeAllListeners(ExternalAPI.SEND_CHAT_MESSAGE);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.SET_CLOSED_CAPTIONS_ENABLED);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.TOGGLE_CAMERA);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.SHOW_NOTIFICATION);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.HIDE_NOTIFICATION);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.START_RECORDING);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.STOP_RECORDING);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { getLocalJitsiAudioTrack } from '../base/tracks/functions';
|
||||
import { showErrorNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
import { NoiseSuppressionEffect } from '../stream-effects/noise-suppression/NoiseSuppressionEffect';
|
||||
|
||||
import { SET_NOISE_SUPPRESSION_ENABLED } from './actionTypes';
|
||||
@@ -93,7 +92,7 @@ export function setNoiseSuppressionEnabled(enabled: boolean): any {
|
||||
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'notify.noiseSuppressionFailedTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { throttle } from 'lodash-es';
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
import { IConfig } from '../base/config/configType';
|
||||
import { NOTIFICATIONS_ENABLED } from '../base/flags/constants';
|
||||
import { getFeatureFlag } from '../base/flags/functions';
|
||||
import { getParticipantCount } from '../base/participants/functions';
|
||||
@@ -28,17 +29,15 @@ import { INotificationProps } from './types';
|
||||
* @param {Object} notificationTimeouts - Config notification timeouts.
|
||||
* @returns {number}
|
||||
*/
|
||||
function getNotificationTimeout(type?: string, notificationTimeouts?: {
|
||||
long?: number;
|
||||
medium?: number;
|
||||
short?: number;
|
||||
}) {
|
||||
function getNotificationTimeout(type?: string, notificationTimeouts?: IConfig['notificationTimeouts']) {
|
||||
if (type === NOTIFICATION_TIMEOUT_TYPE.SHORT) {
|
||||
return notificationTimeouts?.short ?? NOTIFICATION_TIMEOUT.SHORT;
|
||||
} else if (type === NOTIFICATION_TIMEOUT_TYPE.MEDIUM) {
|
||||
return notificationTimeouts?.medium ?? NOTIFICATION_TIMEOUT.MEDIUM;
|
||||
} else if (type === NOTIFICATION_TIMEOUT_TYPE.LONG) {
|
||||
return notificationTimeouts?.long ?? NOTIFICATION_TIMEOUT.LONG;
|
||||
} else if (type === NOTIFICATION_TIMEOUT_TYPE.EXTRA_LONG) {
|
||||
return notificationTimeouts?.extraLong ?? NOTIFICATION_TIMEOUT.EXTRA_LONG;
|
||||
}
|
||||
|
||||
return NOTIFICATION_TIMEOUT.STICKY;
|
||||
@@ -97,7 +96,7 @@ export function setNotificationsEnabled(enabled: boolean) {
|
||||
* @param {string} type - Notification type.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function showErrorNotification(props: INotificationProps, type?: string) {
|
||||
export function showErrorNotification(props: INotificationProps, type = NOTIFICATION_TIMEOUT_TYPE.STICKY) {
|
||||
return showNotification({
|
||||
...props,
|
||||
appearance: NOTIFICATION_TYPE.ERROR
|
||||
|
||||
@@ -5,6 +5,7 @@ export const NOTIFICATION_TIMEOUT = {
|
||||
SHORT: 2500,
|
||||
MEDIUM: 5000,
|
||||
LONG: 10000,
|
||||
EXTRA_LONG: 60000,
|
||||
STICKY: false
|
||||
};
|
||||
|
||||
@@ -12,6 +13,7 @@ export const NOTIFICATION_TIMEOUT = {
|
||||
* Notification timeout type.
|
||||
*/
|
||||
export enum NOTIFICATION_TIMEOUT_TYPE {
|
||||
EXTRA_LONG = 'extra_long',
|
||||
LONG = 'long',
|
||||
MEDIUM = 'medium',
|
||||
SHORT = 'short',
|
||||
@@ -126,10 +128,3 @@ export const SILENT_JOIN_THRESHOLD = 30;
|
||||
* Amount of participants beyond which no left notification will be emitted.
|
||||
*/
|
||||
export const SILENT_LEFT_THRESHOLD = 30;
|
||||
|
||||
/**
|
||||
* The identifier for the transcriber notifications.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const TRANSCRIBING_NOTIFICATION_ID = 'TRANSCRIBING_NOTIFICATION';
|
||||
|
||||
@@ -5,7 +5,6 @@ import { IStore } from '../app/types';
|
||||
import { APP_WILL_MOUNT } from '../base/app/actionTypes';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { showErrorNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
|
||||
import OldElectronAPPNotificationDescription from './components/OldElectronAPPNotificationDescription';
|
||||
import { isOldJitsiMeetElectronApp } from './functions';
|
||||
@@ -35,7 +34,7 @@ function _appWillMount(store: IStore, next: Function, action: AnyAction) {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'notify.OldElectronAPPTitle',
|
||||
description: <OldElectronAPPNotificationDescription />
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
||||
@@ -159,12 +159,12 @@ export function getQuickActionButtonType(
|
||||
if (!isVideoMuted) {
|
||||
return QUICK_ACTION_BUTTON.STOP_VIDEO;
|
||||
}
|
||||
if (isVideoForceMuted) {
|
||||
return QUICK_ACTION_BUTTON.ALLOW_VIDEO;
|
||||
}
|
||||
if (isSupported()(state) && !isParticipantSilent) {
|
||||
return QUICK_ACTION_BUTTON.ASK_TO_UNMUTE;
|
||||
}
|
||||
if (isVideoForceMuted) {
|
||||
return QUICK_ACTION_BUTTON.ALLOW_VIDEO;
|
||||
}
|
||||
}
|
||||
|
||||
return QUICK_ACTION_BUTTON.NONE;
|
||||
|
||||
@@ -45,7 +45,9 @@ const PollCreate = (props: AbstractProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
answerInputs.current = answerInputs.current.slice(0, answers.length);
|
||||
|
||||
setTimeout(() => {
|
||||
answerListRef.current?.scrollToEnd({ animated: true });
|
||||
}, 1000);
|
||||
}, [ answers ]);
|
||||
|
||||
/*
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
import { openURLInBrowser } from '../base/util/openURLInBrowser';
|
||||
import { executeDialOutRequest, executeDialOutStatusRequest, getDialInfoPageURL } from '../invite/functions';
|
||||
import { showErrorNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
import { INotificationProps } from '../notifications/types';
|
||||
|
||||
import {
|
||||
@@ -108,7 +107,7 @@ function pollForStatus(
|
||||
case DIAL_OUT_STATUS.DISCONNECTED: {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'prejoin.errorDialOutDisconnected'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
return onFail();
|
||||
}
|
||||
@@ -116,7 +115,7 @@ function pollForStatus(
|
||||
case DIAL_OUT_STATUS.FAILED: {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'prejoin.errorDialOutFailed'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
return onFail();
|
||||
}
|
||||
@@ -124,7 +123,7 @@ function pollForStatus(
|
||||
} catch (err) {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'prejoin.errorDialOutStatus'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
logger.error('Error getting dial out status', err);
|
||||
onFail();
|
||||
}
|
||||
@@ -177,7 +176,7 @@ export function dialOut(onSuccess: Function, onFail: Function) {
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(showErrorNotification(notification, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
dispatch(showErrorNotification(notification));
|
||||
logger.error('Error dialing out', err);
|
||||
onFail();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import RaiseHandButton from '../../../toolbox/components/native/RaiseHandButton';
|
||||
import { shouldDisplayReactionsButtons } from '../../functions.native';
|
||||
|
||||
import ReactionsMenuButton from './ReactionsMenuButton';
|
||||
|
||||
const RaiseHandContainerButtons = (props: AbstractButtonProps) => {
|
||||
const _shouldDisplayReactionsButtons = useSelector(shouldDisplayReactionsButtons);
|
||||
|
||||
return _shouldDisplayReactionsButtons
|
||||
? <ReactionsMenuButton
|
||||
{ ...props }
|
||||
showRaiseHand = { true } />
|
||||
: <RaiseHandButton { ...props } />;
|
||||
};
|
||||
|
||||
export default RaiseHandContainerButtons;
|
||||
@@ -189,7 +189,7 @@ export function highlightMeetingMoment() {
|
||||
* @returns {showErrorNotification}
|
||||
*/
|
||||
export function showRecordingError(props: Object) {
|
||||
return showErrorNotification(props, NOTIFICATION_TIMEOUT_TYPE.LONG);
|
||||
return showErrorNotification(props);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,7 +301,7 @@ export function showStartedRecordingNotification(
|
||||
} catch (err) {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'recording.errorFetchingLink'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}));
|
||||
|
||||
return logger.error('Could not fetch recording link', err);
|
||||
}
|
||||
@@ -468,6 +468,6 @@ export function showStartRecordingNotificationWithCallback(openRecordingDialog:
|
||||
dispatch(hideNotification(START_RECORDING_NOTIFICATION_ID));
|
||||
} ],
|
||||
appearance: NOTIFICATION_TYPE.NORMAL
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.EXTRA_LONG));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { updateDropboxToken } from '../../../dropbox/actions';
|
||||
import { getDropboxData, getNewAccessToken, isEnabled as isDropboxEnabled } from '../../../dropbox/functions.any';
|
||||
import { showErrorNotification } from '../../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
|
||||
import { setRequestingSubtitles } from '../../../subtitles/actions.any';
|
||||
import { setSelectedRecordingService, startLocalVideoRecording } from '../../actions';
|
||||
import { RECORDING_METADATA_ID, RECORDING_TYPES } from '../../constants';
|
||||
@@ -381,7 +380,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
|
||||
} else {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'dialog.noDropboxToken'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,8 +68,12 @@ class RecordingExpandedLabel extends ExpandedLabel<IProps> {
|
||||
|
||||
let content = t(`${prefix}.${postfix}`);
|
||||
|
||||
if (_status === JitsiRecordingConstants.status.ON && this.props._isTranscribing) {
|
||||
content += ` \u00B7 ${t('transcribing.labelToolTip')}`;
|
||||
if (this.props._isTranscribing) {
|
||||
if (_status === JitsiRecordingConstants.status.ON) {
|
||||
content += ` ${t('transcribing.labelTooltipExtra')}`;
|
||||
} else {
|
||||
content = t('transcribing.labelTooltip');
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
|
||||
@@ -60,12 +60,12 @@ class RecordingLabel extends AbstractRecordingLabel<IProps> {
|
||||
content = t(isRecording ? 'videoStatus.recording' : 'videoStatus.streaming');
|
||||
|
||||
if (_isTranscribing) {
|
||||
content += ` \u00B7 ${t('transcribing.labelToolTip')}`;
|
||||
content += ` ${t('transcribing.labelTooltipExtra')}`;
|
||||
}
|
||||
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
|
||||
return null;
|
||||
} else if (_isTranscribing) {
|
||||
content = t('transcribing.labelToolTip');
|
||||
content = t('transcribing.labelTooltip');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
false, 'local', err.message, isRecorderTranscriptionsRunning(getState()));
|
||||
}
|
||||
|
||||
dispatch(showErrorNotification(props, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
dispatch(showErrorNotification(props));
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -204,10 +204,14 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
= getSessionById(state, action.sessionData.id);
|
||||
const { initiator, mode = '', terminator } = updatedSessionData ?? {};
|
||||
const { PENDING, OFF, ON } = JitsiRecordingConstants.status;
|
||||
const isRecordingStarting = updatedSessionData?.status === PENDING && oldSessionData?.status !== PENDING;
|
||||
|
||||
if (updatedSessionData?.status === PENDING && oldSessionData?.status !== PENDING) {
|
||||
dispatch(showPendingRecordingNotification(mode));
|
||||
if (isRecordingStarting || updatedSessionData?.status === ON) {
|
||||
dispatch(hideNotification(START_RECORDING_NOTIFICATION_ID));
|
||||
}
|
||||
|
||||
if (isRecordingStarting) {
|
||||
dispatch(showPendingRecordingNotification(mode));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ class PasswordRequiredPrompt extends Component<IProps, IState> {
|
||||
|
||||
// We have used the password so let's clean it.
|
||||
this.setState({
|
||||
password: undefined
|
||||
password: ''
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
@@ -142,7 +142,7 @@ function _setPasswordFailed(store: IStore, next: Function, action: AnyAction) {
|
||||
APP.store.dispatch(showErrorNotification({
|
||||
descriptionKey,
|
||||
titleKey
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
||||
@@ -114,6 +114,7 @@ const ProfileView = ({ isInWelcomePage }: {
|
||||
return (
|
||||
<JitsiScreen
|
||||
disableForcedKeyboardDismiss = { true }
|
||||
hasBottomTextInput = { true }
|
||||
|
||||
// @ts-ignore
|
||||
safeAreaInsets = { [ !isInWelcomePage && 'bottom', 'left', 'right' ].filter(Boolean) as Edge[] }
|
||||
|
||||
@@ -6,11 +6,13 @@ import { setVideoMuted } from '../base/media/actions';
|
||||
import { VIDEO_MUTISM_AUTHORITY } from '../base/media/constants';
|
||||
|
||||
import {
|
||||
SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
|
||||
SET_TOOLBOX_ENABLED,
|
||||
SET_TOOLBOX_SHIFT_UP,
|
||||
SET_TOOLBOX_VISIBLE,
|
||||
TOGGLE_TOOLBOX_VISIBLE
|
||||
} from './actionTypes';
|
||||
import { IMainToolbarButtonThresholds } from './types';
|
||||
|
||||
/**
|
||||
* Enables/disables the toolbox.
|
||||
@@ -118,3 +120,54 @@ export function setShiftUp(shiftUp: boolean) {
|
||||
shiftUp
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mainToolbarButtonsThresholds.
|
||||
*
|
||||
* @param {IMainToolbarButtonThresholds} thresholds - Thresholds for screen size and visible main toolbar buttons.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setMainToolbarThresholds(thresholds: IMainToolbarButtonThresholds) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { mainToolbarButtons } = getState()['features/base/config'];
|
||||
|
||||
if (!Array.isArray(mainToolbarButtons) || mainToolbarButtons.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainToolbarButtonsThresholds: IMainToolbarButtonThresholds = [];
|
||||
|
||||
const mainToolbarButtonsLengthMap = new Map();
|
||||
let orderIsChanged = false;
|
||||
|
||||
mainToolbarButtons.forEach(buttons => {
|
||||
if (!Array.isArray(buttons) || buttons.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mainToolbarButtonsLengthMap.set(buttons.length, buttons);
|
||||
});
|
||||
|
||||
thresholds.forEach(({ width, order }) => {
|
||||
let finalOrder = mainToolbarButtonsLengthMap.get(order.length);
|
||||
|
||||
if (finalOrder) {
|
||||
orderIsChanged = true;
|
||||
} else {
|
||||
finalOrder = order;
|
||||
}
|
||||
|
||||
mainToolbarButtonsThresholds.push({
|
||||
order: finalOrder,
|
||||
width
|
||||
});
|
||||
});
|
||||
|
||||
if (orderIsChanged) {
|
||||
dispatch({
|
||||
type: SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
|
||||
mainToolbarButtonsThresholds
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export function setOverflowMenuVisible(_visible: boolean): any {
|
||||
* text: string
|
||||
* }}
|
||||
*/
|
||||
export function customButtonPressed(id: string, text: string) {
|
||||
export function customButtonPressed(id: string, text: string | undefined) {
|
||||
return {
|
||||
type: CUSTOM_BUTTON_PRESSED,
|
||||
id,
|
||||
|
||||
@@ -8,16 +8,13 @@ import {
|
||||
FULL_SCREEN_CHANGED,
|
||||
SET_FULL_SCREEN,
|
||||
SET_HANGUP_MENU_VISIBLE,
|
||||
SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
|
||||
SET_OVERFLOW_DRAWER,
|
||||
SET_OVERFLOW_MENU_VISIBLE,
|
||||
SET_TOOLBAR_HOVERED,
|
||||
SET_TOOLBOX_TIMEOUT
|
||||
} from './actionTypes';
|
||||
import { setToolboxVisible } from './actions.web';
|
||||
import { THRESHOLDS } from './constants';
|
||||
import { getToolbarTimeout } from './functions.web';
|
||||
import { IMainToolbarButtonThresholds } from './types';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
@@ -124,56 +121,6 @@ export function setFullScreen(fullScreen: boolean) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mainToolbarButtonsThresholds.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setMainToolbarThresholds() {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { mainToolbarButtons } = getState()['features/base/config'];
|
||||
|
||||
if (!mainToolbarButtons || !Array.isArray(mainToolbarButtons) || mainToolbarButtons.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainToolbarButtonsThresholds: IMainToolbarButtonThresholds = [];
|
||||
|
||||
const mainToolbarButtonsLenghtMap = new Map();
|
||||
let orderIsChanged = false;
|
||||
|
||||
mainToolbarButtons.forEach(buttons => {
|
||||
if (!Array.isArray(buttons) || buttons.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mainToolbarButtonsLenghtMap.set(buttons.length, buttons);
|
||||
});
|
||||
|
||||
THRESHOLDS.forEach(({ width, order }) => {
|
||||
let finalOrder = mainToolbarButtonsLenghtMap.get(order.length);
|
||||
|
||||
if (finalOrder) {
|
||||
orderIsChanged = true;
|
||||
} else {
|
||||
finalOrder = order;
|
||||
}
|
||||
|
||||
mainToolbarButtonsThresholds.push({
|
||||
order: finalOrder,
|
||||
width
|
||||
});
|
||||
});
|
||||
|
||||
if (orderIsChanged) {
|
||||
dispatch({
|
||||
type: SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
|
||||
mainToolbarButtonsThresholds
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the toolbox for specified timeout.
|
||||
*
|
||||
|
||||
@@ -10,12 +10,12 @@ import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
interface IProps extends AbstractButtonProps {
|
||||
export interface ICustomOptionButton extends AbstractButtonProps {
|
||||
backgroundColor?: string;
|
||||
icon: any;
|
||||
id?: string;
|
||||
isToolboxButton?: boolean;
|
||||
text?: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ interface IProps extends AbstractButtonProps {
|
||||
*
|
||||
* @returns {Component}
|
||||
*/
|
||||
class CustomOptionButton extends AbstractButton<IProps> {
|
||||
class CustomOptionButton extends AbstractButton<ICustomOptionButton> {
|
||||
backgroundColor = this.props.backgroundColor;
|
||||
iconSrc = this.props.icon;
|
||||
id = this.props.id;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import HangupButton from '../HangupButton';
|
||||
|
||||
import HangupMenuButton from './HangupMenuButton';
|
||||
|
||||
const HangupContainerButtons = (props: AbstractButtonProps) => {
|
||||
const { conference } = useSelector((state: IReduxState) => state['features/base/conference']);
|
||||
const endConferenceSupported = conference?.isEndConferenceSupported();
|
||||
|
||||
return endConferenceSupported
|
||||
|
||||
// @ts-ignore
|
||||
? <HangupMenuButton { ...props } />
|
||||
: <HangupButton { ...props } />;
|
||||
};
|
||||
|
||||
export default HangupContainerButtons;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { ViewStyle } from 'react-native';
|
||||
import { Divider } from 'react-native-paper';
|
||||
import { connect } from 'react-redux';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { hideSheet } from '../../../base/dialog/actions';
|
||||
@@ -22,18 +22,17 @@ import { isSharedVideoEnabled } from '../../../shared-video/functions';
|
||||
import SpeakerStatsButton from '../../../speaker-stats/components/native/SpeakerStatsButton';
|
||||
import { isSpeakerStatsDisabled } from '../../../speaker-stats/functions';
|
||||
import ClosedCaptionButton from '../../../subtitles/components/native/ClosedCaptionButton';
|
||||
import TileViewButton from '../../../video-layout/components/TileViewButton';
|
||||
import styles from '../../../video-menu/components/native/styles';
|
||||
import WhiteboardButton from '../../../whiteboard/components/native/WhiteboardButton';
|
||||
import { customButtonPressed } from '../../actions.native';
|
||||
import { getMovableButtons } from '../../functions.native';
|
||||
import { getVisibleNativeButtons } from '../../functions.native';
|
||||
import { useNativeToolboxButtons } from '../../hooks.native';
|
||||
import { IToolboxNativeButton } from '../../types';
|
||||
|
||||
import AudioOnlyButton from './AudioOnlyButton';
|
||||
import CustomOptionButton from './CustomOptionButton';
|
||||
import LinkToSalesforceButton from './LinkToSalesforceButton';
|
||||
import OpenCarmodeButton from './OpenCarmodeButton';
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
import ScreenSharingButton from './ScreenSharingButton';
|
||||
|
||||
|
||||
/**
|
||||
@@ -41,11 +40,6 @@ import ScreenSharingButton from './ScreenSharingButton';
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Custom Toolbar buttons.
|
||||
*/
|
||||
_customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>;
|
||||
|
||||
/**
|
||||
* True if breakout rooms feature is available, false otherwise.
|
||||
*/
|
||||
@@ -66,6 +60,16 @@ interface IProps {
|
||||
*/
|
||||
_isSpeakerStatsDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* Toolbar buttons.
|
||||
*/
|
||||
_mainMenuButtons?: Array<IToolboxNativeButton>;
|
||||
|
||||
/**
|
||||
* Overflow menu buttons.
|
||||
*/
|
||||
_overflowMenuButtons?: Array<IToolboxNativeButton>;
|
||||
|
||||
/**
|
||||
* Whether the recoding button should be enabled or not.
|
||||
*/
|
||||
@@ -76,11 +80,6 @@ interface IProps {
|
||||
*/
|
||||
_shouldDisplayReactionsButtons: boolean;
|
||||
|
||||
/**
|
||||
* The width of the screen.
|
||||
*/
|
||||
_width: number;
|
||||
|
||||
/**
|
||||
* Used for hiding the dialog when the selection was completed.
|
||||
*/
|
||||
@@ -128,11 +127,8 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
_isBreakoutRoomsSupported,
|
||||
_isSpeakerStatsDisabled,
|
||||
_isSharedVideoEnabled,
|
||||
_shouldDisplayReactionsButtons,
|
||||
_width,
|
||||
dispatch
|
||||
} = this.props;
|
||||
const toolbarButtons = getMovableButtons(_width);
|
||||
|
||||
const buttonProps = {
|
||||
afterClick: this._onCancel,
|
||||
@@ -156,18 +152,12 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
renderFooter = { _shouldDisplayReactionsButtons && !toolbarButtons.has('raisehand')
|
||||
? this._renderReactionMenu
|
||||
: undefined }>
|
||||
{ this._renderCustomOverflowMenuButtons(topButtonProps) }
|
||||
renderFooter = { this._renderReactionMenu }>
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
<OpenCarmodeButton { ...topButtonProps } />
|
||||
<AudioOnlyButton { ...buttonProps } />
|
||||
{
|
||||
!_shouldDisplayReactionsButtons && !toolbarButtons.has('raisehand')
|
||||
&& <RaiseHandButton { ...buttonProps } />
|
||||
}
|
||||
{ this._renderRaiseHandButton(buttonProps) }
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
<SecurityDialogButton { ...buttonProps } />
|
||||
<RecordButton { ...buttonProps } />
|
||||
<LiveStreamButton { ...buttonProps } />
|
||||
@@ -176,9 +166,8 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
{_isSharedVideoEnabled && <SharedVideoButton { ...buttonProps } />}
|
||||
{!toolbarButtons.has('screensharing') && <ScreenSharingButton { ...buttonProps } />}
|
||||
{ this._renderOverflowMenuButtons(topButtonProps) }
|
||||
{!_isSpeakerStatsDisabled && <SpeakerStatsButton { ...buttonProps } />}
|
||||
{!toolbarButtons.has('tileview') && <TileViewButton { ...buttonProps } />}
|
||||
{_isBreakoutRoomsSupported && <BreakoutRoomsButton { ...buttonProps } />}
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
@@ -205,42 +194,72 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
* @returns {React.ReactElement}
|
||||
*/
|
||||
_renderReactionMenu() {
|
||||
return (
|
||||
<ReactionMenu
|
||||
onCancel = { this._onCancel }
|
||||
overflowMenu = { true } />
|
||||
);
|
||||
const { _mainMenuButtons, _shouldDisplayReactionsButtons } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
const isRaiseHandInMainMenu = _mainMenuButtons?.some(item => item.key === 'raisehand');
|
||||
|
||||
if (_shouldDisplayReactionsButtons && !isRaiseHandInMainMenu) {
|
||||
return (
|
||||
<ReactionMenu
|
||||
onCancel = { this._onCancel }
|
||||
overflowMenu = { true } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to render the reaction menu as the footer of the bottom sheet.
|
||||
*
|
||||
* @param {Object} buttonProps - Styling button properties.
|
||||
* @returns {React.ReactElement}
|
||||
*/
|
||||
_renderRaiseHandButton(buttonProps: Object) {
|
||||
const { _mainMenuButtons, _shouldDisplayReactionsButtons } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
const isRaiseHandInMainMenu = _mainMenuButtons?.some(item => item.key === 'raisehand');
|
||||
|
||||
if (!_shouldDisplayReactionsButtons && !isRaiseHandInMainMenu) {
|
||||
return (
|
||||
<RaiseHandButton { ...buttonProps } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to render the custom buttons for the overflow menu.
|
||||
*
|
||||
* @param {Object} topButtonProps - Button properties.
|
||||
* @param {Object} topButtonProps - Styling button properties.
|
||||
* @returns {React.ReactElement}
|
||||
*/
|
||||
_renderCustomOverflowMenuButtons(topButtonProps: Object) {
|
||||
const { _customToolbarButtons, dispatch } = this.props;
|
||||
_renderOverflowMenuButtons(topButtonProps: Object) {
|
||||
const { _overflowMenuButtons, dispatch } = this.props;
|
||||
|
||||
if (!_customToolbarButtons?.length) {
|
||||
if (!_overflowMenuButtons?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
_customToolbarButtons.map(({ id, text, icon, backgroundColor }) => (
|
||||
<CustomOptionButton
|
||||
{ ...topButtonProps }
|
||||
backgroundColor = { backgroundColor }
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
handleClick = { () =>
|
||||
dispatch(customButtonPressed(id, text))
|
||||
}
|
||||
icon = { icon }
|
||||
isToolboxButton = { false }
|
||||
key = { id }
|
||||
text = { text } />
|
||||
))
|
||||
_overflowMenuButtons?.map(({ Content, key, text, ...rest }: IToolboxNativeButton) => {
|
||||
|
||||
if (key === 'raisehand') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Content
|
||||
{ ...topButtonProps }
|
||||
{ ...rest }
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
handleClick = { () => dispatch(customButtonPressed(key, text)) }
|
||||
isToolboxButton = { false }
|
||||
key = { key }
|
||||
text = { text } />
|
||||
);
|
||||
})
|
||||
}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
</>
|
||||
@@ -257,16 +276,38 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { customToolbarButtons } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_customToolbarButtons: customToolbarButtons,
|
||||
_isBreakoutRoomsSupported: conference?.getBreakoutRooms()?.isSupported(),
|
||||
_isSharedVideoEnabled: isSharedVideoEnabled(state),
|
||||
_isSpeakerStatsDisabled: isSpeakerStatsDisabled(state),
|
||||
_shouldDisplayReactionsButtons: shouldDisplayReactionsButtons(state),
|
||||
_width: state['features/base/responsive-ui'].clientWidth
|
||||
_shouldDisplayReactionsButtons: shouldDisplayReactionsButtons(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(OverflowMenu);
|
||||
export default connect(_mapStateToProps)(props => {
|
||||
const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
|
||||
const { customToolbarButtons } = useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const {
|
||||
mainToolbarButtonsThresholds,
|
||||
toolbarButtons
|
||||
} = useSelector((state: IReduxState) => state['features/toolbox']);
|
||||
|
||||
const allButtons = useNativeToolboxButtons(customToolbarButtons);
|
||||
|
||||
const { mainMenuButtons, overflowMenuButtons } = getVisibleNativeButtons({
|
||||
allButtons,
|
||||
clientWidth,
|
||||
mainToolbarButtonsThresholds,
|
||||
toolbarButtons
|
||||
});
|
||||
|
||||
return (
|
||||
<OverflowMenu
|
||||
|
||||
// @ts-ignore
|
||||
{ ... props }
|
||||
_mainMenuButtons = { mainMenuButtons }
|
||||
_overflowMenuButtons = { overflowMenuButtons } />
|
||||
);
|
||||
});
|
||||
|
||||
@@ -25,6 +25,8 @@ class OverflowMenuButton extends AbstractButton<AbstractButtonProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
|
||||
// @ts-ignore
|
||||
this.props.dispatch(openSheet(OverflowMenu));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,29 @@
|
||||
import React from 'react';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { connect } from 'react-redux';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
|
||||
import Platform from '../../../base/react/Platform.native';
|
||||
import ChatButton from '../../../chat/components/native/ChatButton';
|
||||
import ReactionsMenuButton from '../../../reactions/components/native/ReactionsMenuButton';
|
||||
import { shouldDisplayReactionsButtons } from '../../../reactions/functions.any';
|
||||
import TileViewButton from '../../../video-layout/components/TileViewButton';
|
||||
import { iAmVisitor } from '../../../visitors/functions';
|
||||
import { customButtonPressed } from '../../actions.native';
|
||||
import { getMovableButtons, isToolboxVisible } from '../../functions.native';
|
||||
import HangupButton from '../HangupButton';
|
||||
import { getVisibleNativeButtons, isToolboxVisible } from '../../functions.native';
|
||||
import { useNativeToolboxButtons } from '../../hooks.native';
|
||||
import { IToolboxNativeButton } from '../../types';
|
||||
|
||||
import AudioMuteButton from './AudioMuteButton';
|
||||
import CustomOptionButton from './CustomOptionButton';
|
||||
import HangupMenuButton from './HangupMenuButton';
|
||||
import OverflowMenuButton from './OverflowMenuButton';
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
import ScreenSharingButton from './ScreenSharingButton';
|
||||
import VideoMuteButton from './VideoMuteButton';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* The type of {@link Toolbox}'s React {@code Component} props.
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Custom Toolbar buttons.
|
||||
*/
|
||||
_customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>;
|
||||
|
||||
/**
|
||||
* Whether the end conference feature is supported.
|
||||
*/
|
||||
_endConferenceSupported: boolean;
|
||||
|
||||
/**
|
||||
* Whether we are in visitors mode.
|
||||
*/
|
||||
_iAmVisitor: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not any reactions buttons should be visible.
|
||||
*/
|
||||
_shouldDisplayReactionsButtons: boolean;
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
@@ -60,11 +34,6 @@ interface IProps {
|
||||
*/
|
||||
_visible: boolean;
|
||||
|
||||
/**
|
||||
* The width of the screen.
|
||||
*/
|
||||
_width: number;
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
@@ -79,13 +48,9 @@ interface IProps {
|
||||
*/
|
||||
function Toolbox(props: IProps) {
|
||||
const {
|
||||
_customToolbarButtons,
|
||||
_endConferenceSupported,
|
||||
_iAmVisitor,
|
||||
_shouldDisplayReactionsButtons,
|
||||
_styles,
|
||||
_visible,
|
||||
_width,
|
||||
dispatch
|
||||
} = props;
|
||||
|
||||
@@ -93,41 +58,47 @@ function Toolbox(props: IProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
|
||||
const { customToolbarButtons } = useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const {
|
||||
mainToolbarButtonsThresholds,
|
||||
toolbarButtons
|
||||
} = useSelector((state: IReduxState) => state['features/toolbox']);
|
||||
|
||||
const allButtons = useNativeToolboxButtons(customToolbarButtons);
|
||||
|
||||
const { mainMenuButtons } = getVisibleNativeButtons({
|
||||
allButtons,
|
||||
clientWidth,
|
||||
mainToolbarButtonsThresholds,
|
||||
toolbarButtons
|
||||
});
|
||||
|
||||
const bottomEdge = Platform.OS === 'ios' && _visible;
|
||||
const { buttonStylesBorderless, hangupButtonStyles, toggledButtonStyles } = _styles;
|
||||
const additionalButtons = getMovableButtons(_width);
|
||||
const backgroundToggledStyle = {
|
||||
...toggledButtonStyles,
|
||||
style: [
|
||||
toggledButtonStyles.style,
|
||||
_styles.backgroundToggle
|
||||
]
|
||||
};
|
||||
const { buttonStylesBorderless, hangupButtonStyles } = _styles;
|
||||
const style = { ...styles.toolbox };
|
||||
|
||||
// we have only hangup and raisehand button in _iAmVisitor mode
|
||||
// We have only hangup and raisehand button in _iAmVisitor mode
|
||||
if (_iAmVisitor) {
|
||||
additionalButtons.add('raisehand');
|
||||
style.justifyContent = 'center';
|
||||
}
|
||||
|
||||
const renderCustomToolboxButtons = () => {
|
||||
if (!_customToolbarButtons?.length) {
|
||||
const renderToolboxButtons = () => {
|
||||
if (!mainMenuButtons?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
_customToolbarButtons.map(({ backgroundColor, id, text, icon }) => (
|
||||
<CustomOptionButton
|
||||
backgroundColor = { backgroundColor }
|
||||
|
||||
mainMenuButtons?.map(({ Content, key, text, ...rest }: IToolboxNativeButton) => (
|
||||
<Content
|
||||
{ ...rest }
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
handleClick = { () => dispatch(customButtonPressed(id, text)) }
|
||||
icon = { icon }
|
||||
handleClick = { () => dispatch(customButtonPressed(key, text)) }
|
||||
isToolboxButton = { true }
|
||||
key = { id } />
|
||||
key = { key }
|
||||
styles = { key === 'hangup' ? hangupButtonStyles : buttonStylesBorderless } />
|
||||
))
|
||||
}
|
||||
</>
|
||||
@@ -144,44 +115,7 @@ function Toolbox(props: IProps) {
|
||||
edges = { [ bottomEdge && 'bottom' ].filter(Boolean) }
|
||||
pointerEvents = 'box-none'
|
||||
style = { style as ViewStyle }>
|
||||
{
|
||||
_customToolbarButtons
|
||||
? <>
|
||||
{ renderCustomToolboxButtons() }
|
||||
{ !_iAmVisitor && <OverflowMenuButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } /> }
|
||||
</>
|
||||
: <>
|
||||
{!_iAmVisitor && <AudioMuteButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } />}
|
||||
{!_iAmVisitor && <VideoMuteButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } />}
|
||||
{additionalButtons.has('chat')
|
||||
&& <ChatButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { backgroundToggledStyle } />}
|
||||
{!_iAmVisitor && additionalButtons.has('screensharing')
|
||||
&& <ScreenSharingButton styles = { buttonStylesBorderless } />}
|
||||
{additionalButtons.has('raisehand') && (_shouldDisplayReactionsButtons
|
||||
? <ReactionsMenuButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { backgroundToggledStyle } />
|
||||
: <RaiseHandButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { backgroundToggledStyle } />)}
|
||||
{additionalButtons.has('tileview')
|
||||
&& <TileViewButton styles = { buttonStylesBorderless } />}
|
||||
{!_iAmVisitor && <OverflowMenuButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } />}
|
||||
{ _endConferenceSupported
|
||||
? <HangupMenuButton />
|
||||
: <HangupButton styles = { hangupButtonStyles } />}
|
||||
</>
|
||||
}
|
||||
{ renderToolboxButtons() }
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
@@ -197,17 +131,10 @@ function Toolbox(props: IProps) {
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const endConferenceSupported = conference?.isEndConferenceSupported();
|
||||
|
||||
return {
|
||||
_customToolbarButtons: state['features/base/config']?.customToolbarButtons,
|
||||
_endConferenceSupported: Boolean(endConferenceSupported),
|
||||
_iAmVisitor: iAmVisitor(state),
|
||||
_styles: ColorSchemeRegistry.get(state, 'Toolbox'),
|
||||
_visible: isToolboxVisible(state),
|
||||
_iAmVisitor: iAmVisitor(state),
|
||||
_width: state['features/base/responsive-ui'].clientWidth,
|
||||
_shouldDisplayReactionsButtons: shouldDisplayReactionsButtons(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ToolbarButton } from './types';
|
||||
import { NativeToolbarButton, ToolbarButton } from './types';
|
||||
|
||||
/**
|
||||
* Thresholds for displaying toolbox buttons.
|
||||
@@ -34,6 +34,32 @@ export const THRESHOLDS = [
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Thresholds for displaying native toolbox buttons.
|
||||
*/
|
||||
export const NATIVE_THRESHOLDS = [
|
||||
{
|
||||
width: 560,
|
||||
order: [ 'microphone', 'camera', 'chat', 'screensharing', 'raisehand', 'tileview', 'overflowmenu', 'hangup' ]
|
||||
},
|
||||
{
|
||||
width: 500,
|
||||
order: [ 'microphone', 'camera', 'chat', 'raisehand', 'tileview', 'overflowmenu', 'hangup' ]
|
||||
},
|
||||
{
|
||||
width: 440,
|
||||
order: [ 'microphone', 'camera', 'chat', 'raisehand', 'overflowmenu', 'hangup' ]
|
||||
},
|
||||
{
|
||||
width: 380,
|
||||
order: [ 'microphone', 'camera', 'chat', 'overflowmenu', 'hangup' ]
|
||||
},
|
||||
{
|
||||
width: 320,
|
||||
order: [ 'microphone', 'camera', 'overflowmenu', 'hangup' ]
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Main toolbar buttons priority used to determine which button should be picked to fill empty spaces for disabled
|
||||
* buttons.
|
||||
@@ -47,6 +73,8 @@ export const MAIN_TOOLBAR_BUTTONS_PRIORITY = [
|
||||
'reactions',
|
||||
'participants-pane',
|
||||
'tileview',
|
||||
'overflowmenu',
|
||||
'hangup',
|
||||
'invite',
|
||||
'toggle-camera',
|
||||
'videoquality',
|
||||
@@ -128,17 +156,34 @@ export const TOOLBAR_BUTTONS: ToolbarButton[] = [
|
||||
'whiteboard'
|
||||
];
|
||||
|
||||
/**
|
||||
* The list of all possible native buttons.
|
||||
*
|
||||
* @protected
|
||||
* @type Array<string>
|
||||
*/
|
||||
export const NATIVE_TOOLBAR_BUTTONS: NativeToolbarButton[] = [
|
||||
'camera',
|
||||
'chat',
|
||||
'hangup',
|
||||
'microphone',
|
||||
'overflowmenu',
|
||||
'raisehand',
|
||||
'screensharing',
|
||||
'tileview'
|
||||
];
|
||||
|
||||
/**
|
||||
* The toolbar buttons to show when in visitors mode.
|
||||
*/
|
||||
export const VISITORS_MODE_BUTTONS: ToolbarButton[] = [
|
||||
'chat',
|
||||
'closedcaptions',
|
||||
'fullscreen',
|
||||
'hangup',
|
||||
'raisehand',
|
||||
'settings',
|
||||
'tileview',
|
||||
'fullscreen',
|
||||
'stats',
|
||||
'tileview',
|
||||
'videoquality'
|
||||
];
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { isJwtFeatureEnabledStateless } from '../base/jwt/functions';
|
||||
import { IGUMPendingState } from '../base/media/types';
|
||||
import { IParticipantFeatures } from '../base/participants/types';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { iAmVisitor } from '../visitors/functions';
|
||||
|
||||
import { VISITORS_MODE_BUTTONS } from './constants';
|
||||
|
||||
/**
|
||||
* Indicates if the audio mute button is disabled or not.
|
||||
*
|
||||
@@ -57,3 +61,41 @@ export function getJwtDisabledButtons(
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of enabled toolbar buttons.
|
||||
*
|
||||
* @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
|
||||
* @param {string[]} definedToolbarButtons - The list of all possible buttons.
|
||||
*
|
||||
* @returns {Array<string>} - The list of enabled toolbar buttons.
|
||||
*/
|
||||
export function getToolbarButtons(stateful: IStateful, definedToolbarButtons: string[]): Array<string> {
|
||||
const state = toState(stateful);
|
||||
const { toolbarButtons, customToolbarButtons } = state['features/base/config'];
|
||||
const customButtons = customToolbarButtons?.map(({ id }) => id);
|
||||
let buttons = Array.isArray(toolbarButtons) ? toolbarButtons : definedToolbarButtons;
|
||||
|
||||
if (iAmVisitor(state)) {
|
||||
buttons = VISITORS_MODE_BUTTONS.filter(button => buttons.indexOf(button) > -1);
|
||||
}
|
||||
|
||||
if (customButtons) {
|
||||
return [ ...buttons, ...customButtons ];
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified button is enabled.
|
||||
*
|
||||
* @param {string} buttonName - The name of the button. See {@link interfaceConfig}.
|
||||
* @param {Object|Array<string>} state - The redux state or the array with the enabled buttons.
|
||||
* @returns {boolean} - True if the button is enabled and false otherwise.
|
||||
*/
|
||||
export function isButtonEnabled(buttonName: string, state: IReduxState | Array<string>) {
|
||||
const buttons = Array.isArray(state) ? state : state['features/toolbox'].toolbarButtons || [];
|
||||
|
||||
return buttons.includes(buttonName);
|
||||
}
|
||||
|
||||
@@ -7,53 +7,12 @@ import { getParticipantCountWithFake } from '../base/participants/functions';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { isLocalVideoTrackDesktop } from '../base/tracks/functions.native';
|
||||
|
||||
import { MAIN_TOOLBAR_BUTTONS_PRIORITY } from './constants';
|
||||
import { isButtonEnabled } from './functions.any';
|
||||
import { IGetVisibleNativeButtonsParams, IToolboxNativeButton } from './types';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
const WIDTH = {
|
||||
FIT_9_ICONS: 560,
|
||||
FIT_8_ICONS: 500,
|
||||
FIT_7_ICONS: 440,
|
||||
FIT_6_ICONS: 380
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a set of the buttons that are shown in the toolbar
|
||||
* but removed from the overflow menu, based on the width of the screen.
|
||||
*
|
||||
* @param {number} width - The width of the screen.
|
||||
* @returns {Set}
|
||||
*/
|
||||
export function getMovableButtons(width: number): Set<string> {
|
||||
let buttons: string[] = [];
|
||||
|
||||
switch (true) {
|
||||
case width >= WIDTH.FIT_9_ICONS: {
|
||||
buttons = [ 'chat', 'togglecamera', 'screensharing', 'raisehand', 'tileview' ];
|
||||
break;
|
||||
}
|
||||
case width >= WIDTH.FIT_8_ICONS: {
|
||||
buttons = [ 'chat', 'togglecamera', 'raisehand', 'tileview' ];
|
||||
break;
|
||||
}
|
||||
|
||||
case width >= WIDTH.FIT_7_ICONS: {
|
||||
buttons = [ 'chat', 'togglecamera', 'raisehand' ];
|
||||
break;
|
||||
}
|
||||
|
||||
case width >= WIDTH.FIT_6_ICONS: {
|
||||
buttons = [ 'chat', 'togglecamera' ];
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
buttons = [ 'chat' ];
|
||||
}
|
||||
}
|
||||
|
||||
return new Set(buttons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the desktop share button is disabled or not.
|
||||
*
|
||||
@@ -99,3 +58,64 @@ export function isVideoMuteButtonDisabled(state: IReduxState) {
|
||||
return !hasAvailableDevices(state, 'videoInput')
|
||||
|| (unmuteBlocked && Boolean(muted));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all buttons that need to be rendered.
|
||||
*
|
||||
* @param {IGetVisibleButtonsParams} params - The parameters needed to extract the visible buttons.
|
||||
* @returns {Object} - The visible buttons arrays .
|
||||
*/
|
||||
export function getVisibleNativeButtons({ allButtons, clientWidth, mainToolbarButtonsThresholds, toolbarButtons
|
||||
}: IGetVisibleNativeButtonsParams) {
|
||||
const filteredButtons = Object.keys(allButtons).filter(key =>
|
||||
typeof key !== 'undefined' // filter invalid buttons that may be coming from config.mainToolbarButtons override
|
||||
&& isButtonEnabled(key, toolbarButtons));
|
||||
|
||||
const { order } = mainToolbarButtonsThresholds.find(({ width }) => clientWidth > width)
|
||||
|| mainToolbarButtonsThresholds[mainToolbarButtonsThresholds.length - 1];
|
||||
|
||||
const mainToolbarButtonKeysOrder = [
|
||||
...order.filter(key => filteredButtons.includes(key)),
|
||||
...MAIN_TOOLBAR_BUTTONS_PRIORITY.filter(key => !order.includes(key) && filteredButtons.includes(key)),
|
||||
...filteredButtons.filter(key => !order.includes(key) && !MAIN_TOOLBAR_BUTTONS_PRIORITY.includes(key))
|
||||
];
|
||||
|
||||
const mainButtonsKeys = mainToolbarButtonKeysOrder.slice(0, order.length);
|
||||
const overflowMenuButtons = filteredButtons.reduce((acc, key) => {
|
||||
if (!mainButtonsKeys.includes(key)) {
|
||||
acc.push(allButtons[key]);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, [] as IToolboxNativeButton[]);
|
||||
|
||||
// if we have 1 button in the overflow menu it is better to directly display it in the main toolbar by replacing
|
||||
// the "More" menu button with it.
|
||||
if (overflowMenuButtons.length === 1) {
|
||||
const button = overflowMenuButtons.shift()?.key;
|
||||
|
||||
button && mainButtonsKeys.push(button);
|
||||
}
|
||||
|
||||
const mainMenuButtons
|
||||
= mainButtonsKeys.map(key => allButtons[key]).sort((a, b) => {
|
||||
|
||||
// Native toolbox includes hangup and overflowmenu button keys, too
|
||||
// hangup goes last, overflowmenu goes second-to-last
|
||||
if (a.key === 'hangup' || a.key === 'overflowmenu') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (b.key === 'hangup' || b.key === 'overflowmenu') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0; // other buttons are sorted by priority
|
||||
});
|
||||
|
||||
return {
|
||||
mainMenuButtons,
|
||||
overflowMenuButtons
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { hasAvailableDevices } from '../base/devices/functions';
|
||||
import { hasAvailableDevices } from '../base/devices/functions.web';
|
||||
import { MEET_FEATURES } from '../base/jwt/constants';
|
||||
import { isJwtFeatureEnabled } from '../base/jwt/functions';
|
||||
import { IGUMPendingState } from '../base/media/types';
|
||||
@@ -7,7 +7,8 @@ import { isScreenMediaShared } from '../screen-share/functions';
|
||||
import { isWhiteboardVisible } from '../whiteboard/functions';
|
||||
|
||||
import { MAIN_TOOLBAR_BUTTONS_PRIORITY, TOOLBAR_TIMEOUT } from './constants';
|
||||
import { IMainToolbarButtonThresholds, IToolboxButton, NOTIFY_CLICK_MODE } from './types';
|
||||
import { isButtonEnabled } from './functions.any';
|
||||
import { IGetVisibleButtonsParams, IToolboxButton, NOTIFY_CLICK_MODE } from './types';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
@@ -22,19 +23,6 @@ export function getToolboxHeight() {
|
||||
return toolbox?.clientHeight || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified button is enabled.
|
||||
*
|
||||
* @param {string} buttonName - The name of the button. See {@link interfaceConfig}.
|
||||
* @param {Object|Array<string>} state - The redux state or the array with the enabled buttons.
|
||||
* @returns {boolean} - True if the button is enabled and false otherwise.
|
||||
*/
|
||||
export function isButtonEnabled(buttonName: string, state: IReduxState | Array<string>) {
|
||||
const buttons = Array.isArray(state) ? state : state['features/toolbox'].toolbarButtons || [];
|
||||
|
||||
return buttons.includes(buttonName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the toolbox is visible or not.
|
||||
*
|
||||
@@ -125,26 +113,6 @@ export function showOverflowDrawer(state: IReduxState) {
|
||||
return state['features/toolbox'].overflowDrawer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the overflow menu button is displayed and false otherwise.
|
||||
*
|
||||
* @param {IReduxState} state - The state from the Redux store.
|
||||
* @returns {boolean} - True if the overflow menu button is displayed and false otherwise.
|
||||
*/
|
||||
export function showOverflowMenu(state: IReduxState) {
|
||||
return state['features/toolbox'].overflowMenuVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the toolbox is enabled or not.
|
||||
*
|
||||
* @param {IReduxState} state - The state from the Redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isToolboxEnabled(state: IReduxState) {
|
||||
return state['features/toolbox'].enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the toolbar timeout from config or the default value.
|
||||
*
|
||||
@@ -176,15 +144,6 @@ function setButtonsNotifyClickMode(buttons: Object, buttonsWithNotifyClick: Map<
|
||||
});
|
||||
}
|
||||
|
||||
interface IGetVisibleButtonsParams {
|
||||
allButtons: { [key: string]: IToolboxButton; };
|
||||
buttonsWithNotifyClick: Map<string, NOTIFY_CLICK_MODE>;
|
||||
clientWidth: number;
|
||||
jwtDisabledButtons: string[];
|
||||
mainToolbarButtonsThresholds: IMainToolbarButtonThresholds;
|
||||
toolbarButtons: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all buttons that need to be rendered.
|
||||
*
|
||||
@@ -234,8 +193,10 @@ export function getVisibleButtons({
|
||||
button && mainButtonsKeys.push(button);
|
||||
}
|
||||
|
||||
const mainMenuButtons = mainButtonsKeys.map(key => allButtons[key]);
|
||||
|
||||
return {
|
||||
mainMenuButtons: mainButtonsKeys.map(key => allButtons[key]),
|
||||
mainMenuButtons,
|
||||
overflowMenuButtons
|
||||
};
|
||||
}
|
||||
|
||||
193
react/features/toolbox/hooks.native.ts
Normal file
193
react/features/toolbox/hooks.native.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import ChatButton from '../chat/components/native/ChatButton';
|
||||
import RaiseHandContainerButtons from '../reactions/components/native/RaiseHandContainerButtons';
|
||||
import TileViewButton from '../video-layout/components/TileViewButton';
|
||||
import { iAmVisitor } from '../visitors/functions';
|
||||
|
||||
import AudioMuteButton from './components/native/AudioMuteButton';
|
||||
import CustomOptionButton from './components/native/CustomOptionButton';
|
||||
import HangupContainerButtons from './components/native/HangupContainerButtons';
|
||||
import OverflowMenuButton from './components/native/OverflowMenuButton';
|
||||
import ScreenSharingButton from './components/native/ScreenSharingButton';
|
||||
import VideoMuteButton from './components/native/VideoMuteButton';
|
||||
import { isDesktopShareButtonDisabled } from './functions.native';
|
||||
import { ICustomToolbarButton, IToolboxNativeButton, NativeToolbarButton } from './types';
|
||||
|
||||
|
||||
const microphone = {
|
||||
key: 'microphone',
|
||||
Content: AudioMuteButton,
|
||||
group: 0
|
||||
};
|
||||
|
||||
const camera = {
|
||||
key: 'camera',
|
||||
Content: VideoMuteButton,
|
||||
group: 0
|
||||
};
|
||||
|
||||
const chat = {
|
||||
key: 'chat',
|
||||
Content: ChatButton,
|
||||
group: 1
|
||||
};
|
||||
|
||||
const screensharing = {
|
||||
key: 'screensharing',
|
||||
Content: ScreenSharingButton,
|
||||
group: 1
|
||||
};
|
||||
|
||||
const raisehand = {
|
||||
key: 'raisehand',
|
||||
Content: RaiseHandContainerButtons,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const tileview = {
|
||||
key: 'tileview',
|
||||
Content: TileViewButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const overflowmenu = {
|
||||
key: 'overflowmenu',
|
||||
Content: OverflowMenuButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
const hangup = {
|
||||
key: 'hangup',
|
||||
Content: HangupContainerButtons,
|
||||
group: 3
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the audio mute button.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getAudioMuteButton() {
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
|
||||
if (!_iAmVisitor) {
|
||||
return microphone;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the video mute button.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getVideoMuteButton() {
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
|
||||
if (!_iAmVisitor) {
|
||||
return camera;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the chat button.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getChatButton() {
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
|
||||
if (!_iAmVisitor) {
|
||||
return chat;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the screen sharing button.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getScreenSharingButton() {
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
const _isScreenShareButtonDisabled = useSelector(isDesktopShareButtonDisabled);
|
||||
|
||||
if (!_isScreenShareButtonDisabled && !_iAmVisitor) {
|
||||
return screensharing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the tile view button.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getTileViewButton() {
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
|
||||
if (!_iAmVisitor) {
|
||||
return tileview;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the overflow menu button.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getOverflowMenuButton() {
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
|
||||
if (!_iAmVisitor) {
|
||||
return overflowmenu;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all buttons that could be rendered.
|
||||
*
|
||||
* @param {Object} _customToolbarButtons - An array containing custom buttons objects.
|
||||
* @returns {Object} The button maps mainMenuButtons and overflowMenuButtons.
|
||||
*/
|
||||
export function useNativeToolboxButtons(
|
||||
_customToolbarButtons?: ICustomToolbarButton[]): { [key: string]: IToolboxNativeButton; } {
|
||||
const audioMuteButton = getAudioMuteButton();
|
||||
const videoMuteButton = getVideoMuteButton();
|
||||
const chatButton = getChatButton();
|
||||
const screenSharingButton = getScreenSharingButton();
|
||||
const tileViewButton = getTileViewButton();
|
||||
const overflowMenuButton = getOverflowMenuButton();
|
||||
|
||||
const buttons: { [key in NativeToolbarButton]?: IToolboxNativeButton; } = {
|
||||
microphone: audioMuteButton,
|
||||
camera: videoMuteButton,
|
||||
chat: chatButton,
|
||||
screensharing: screenSharingButton,
|
||||
raisehand,
|
||||
tileview: tileViewButton,
|
||||
overflowmenu: overflowMenuButton,
|
||||
hangup
|
||||
};
|
||||
const buttonKeys = Object.keys(buttons) as NativeToolbarButton[];
|
||||
|
||||
buttonKeys.forEach(
|
||||
key => typeof buttons[key] === 'undefined' && delete buttons[key]);
|
||||
|
||||
const customButtons = _customToolbarButtons?.reduce((prev, { backgroundColor, icon, id, text }) => {
|
||||
prev[id] = {
|
||||
backgroundColor,
|
||||
key: id,
|
||||
id,
|
||||
Content: CustomOptionButton,
|
||||
group: 4,
|
||||
icon,
|
||||
text
|
||||
};
|
||||
|
||||
return prev;
|
||||
}, {} as { [key: string]: ICustomToolbarButton; });
|
||||
|
||||
return {
|
||||
...buttons,
|
||||
...customButtons
|
||||
};
|
||||
}
|
||||
@@ -270,7 +270,7 @@ function useHelpButton() {
|
||||
*/
|
||||
export function useToolboxButtons(
|
||||
_customToolbarButtons?: ICustomToolbarButton[]): { [key: string]: IToolboxButton; } {
|
||||
const dekstopSharing = getDesktopSharingButton();
|
||||
const desktopSharing = getDesktopSharingButton();
|
||||
const toggleCameraButton = useToggleCameraButton();
|
||||
const _fullscreen = getFullscreenButton();
|
||||
const security = useSecurityDialogButton();
|
||||
@@ -297,7 +297,7 @@ export function useToolboxButtons(
|
||||
microphone,
|
||||
camera,
|
||||
profile,
|
||||
desktop: dekstopSharing,
|
||||
desktop: desktopSharing,
|
||||
chat,
|
||||
raisehand,
|
||||
reactions,
|
||||
|
||||
46
react/features/toolbox/middleware.native.ts
Normal file
46
react/features/toolbox/middleware.native.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { OVERWRITE_CONFIG, SET_CONFIG, UPDATE_CONFIG } from '../base/config/actionTypes';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { I_AM_VISITOR_MODE } from '../visitors/actionTypes';
|
||||
|
||||
import { SET_TOOLBAR_BUTTONS } from './actionTypes';
|
||||
import { setMainToolbarThresholds } from './actions.native';
|
||||
import { NATIVE_THRESHOLDS, NATIVE_TOOLBAR_BUTTONS } from './constants';
|
||||
import { getToolbarButtons } from './functions.native';
|
||||
|
||||
|
||||
/**
|
||||
* Middleware which intercepts Toolbox actions to handle changes to the
|
||||
* visibility timeout of the Toolbox.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
|
||||
case UPDATE_CONFIG:
|
||||
case OVERWRITE_CONFIG:
|
||||
case I_AM_VISITOR_MODE:
|
||||
case SET_CONFIG: {
|
||||
const result = next(action);
|
||||
const { dispatch } = store;
|
||||
const state = store.getState();
|
||||
|
||||
const toolbarButtons = getToolbarButtons(state, NATIVE_TOOLBAR_BUTTONS);
|
||||
|
||||
if (action.type !== I_AM_VISITOR_MODE) {
|
||||
dispatch(setMainToolbarThresholds(NATIVE_THRESHOLDS));
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SET_TOOLBAR_BUTTONS,
|
||||
toolbarButtons
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
@@ -1,12 +1,10 @@
|
||||
import { batch } from 'react-redux';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
import { OVERWRITE_CONFIG, SET_CONFIG, UPDATE_CONFIG } from '../base/config/actionTypes';
|
||||
import { NotifyClickButton } from '../base/config/configType';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { I_AM_VISITOR_MODE } from '../visitors/actionTypes';
|
||||
import { iAmVisitor } from '../visitors/functions';
|
||||
|
||||
import {
|
||||
CLEAR_TOOLBOX_TIMEOUT,
|
||||
@@ -17,7 +15,8 @@ import {
|
||||
SET_TOOLBOX_TIMEOUT
|
||||
} from './actionTypes';
|
||||
import { setMainToolbarThresholds } from './actions.web';
|
||||
import { TOOLBAR_BUTTONS, VISITORS_MODE_BUTTONS } from './constants';
|
||||
import { THRESHOLDS, TOOLBAR_BUTTONS } from './constants';
|
||||
import { getToolbarButtons } from './functions.web';
|
||||
import { NOTIFY_CLICK_MODE } from './types';
|
||||
|
||||
import './subscriber.web';
|
||||
@@ -55,7 +54,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
batch(() => {
|
||||
if (action.type !== I_AM_VISITOR_MODE) {
|
||||
dispatch(setMainToolbarThresholds());
|
||||
dispatch(setMainToolbarThresholds(THRESHOLDS));
|
||||
}
|
||||
dispatch({
|
||||
type: SET_BUTTONS_WITH_NOTIFY_CLICK,
|
||||
@@ -69,7 +68,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
});
|
||||
}
|
||||
|
||||
const toolbarButtons = _getToolbarButtons(state);
|
||||
const toolbarButtons = getToolbarButtons(state, TOOLBAR_BUTTONS);
|
||||
|
||||
dispatch({
|
||||
type: SET_TOOLBAR_BUTTONS,
|
||||
@@ -171,25 +170,3 @@ function _buildButtonsArray(
|
||||
|
||||
return new Map([ ...customButtonsWithNotifyClick, ...buttons ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of enabled toolbar buttons.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Array<string>} - The list of enabled toolbar buttons.
|
||||
*/
|
||||
function _getToolbarButtons(state: IReduxState): Array<string> {
|
||||
const { toolbarButtons, customToolbarButtons } = state['features/base/config'];
|
||||
const customButtons = customToolbarButtons?.map(({ id }) => id);
|
||||
let buttons = Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
|
||||
|
||||
if (iAmVisitor(state)) {
|
||||
buttons = VISITORS_MODE_BUTTONS.filter(button => buttons.indexOf(button) > -1);
|
||||
}
|
||||
|
||||
if (customButtons) {
|
||||
return [ ...buttons, ...customButtons ];
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
SET_TOOLBOX_VISIBLE,
|
||||
TOGGLE_TOOLBOX_VISIBLE
|
||||
} from './actionTypes';
|
||||
import { THRESHOLDS } from './constants';
|
||||
import { NATIVE_THRESHOLDS, THRESHOLDS } from './constants';
|
||||
import { IMainToolbarButtonThresholds, NOTIFY_CLICK_MODE } from './types';
|
||||
|
||||
/**
|
||||
@@ -52,7 +52,7 @@ const INITIAL_STATE = {
|
||||
/**
|
||||
* The thresholds for screen size and visible main toolbar buttons.
|
||||
*/
|
||||
mainToolbarButtonsThresholds: THRESHOLDS,
|
||||
mainToolbarButtonsThresholds: navigator.product === 'ReactNative' ? NATIVE_THRESHOLDS : THRESHOLDS,
|
||||
|
||||
participantMenuButtonsWithNotifyClick: new Map(),
|
||||
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import { CustomOptionButton } from './components';
|
||||
|
||||
export interface IToolboxButton {
|
||||
Content: ComponentType<any>;
|
||||
group: number;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface IToolboxNativeButton {
|
||||
Content: ComponentType<any>;
|
||||
backgroundColor?: string;
|
||||
group: number;
|
||||
icon?: string;
|
||||
id?: string;
|
||||
key: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export type ToolbarButton = 'camera' |
|
||||
'chat' |
|
||||
'closedcaptions' |
|
||||
@@ -28,6 +36,7 @@ export type ToolbarButton = 'camera' |
|
||||
'mute-everyone' |
|
||||
'mute-video-everyone' |
|
||||
'noisesuppression' |
|
||||
'overflowmenu' |
|
||||
'participants-pane' |
|
||||
'profile' |
|
||||
'raisehand' |
|
||||
@@ -52,12 +61,12 @@ export enum NOTIFY_CLICK_MODE {
|
||||
}
|
||||
|
||||
export type IMainToolbarButtonThresholds = Array<{
|
||||
order: Array<ToolbarButton | string>;
|
||||
order: Array<ToolbarButton | NativeToolbarButton | string>;
|
||||
width: number;
|
||||
}>;
|
||||
|
||||
export interface ICustomToolbarButton {
|
||||
Content?: typeof CustomOptionButton;
|
||||
Content?: ComponentType<any>;
|
||||
backgroundColor?: string;
|
||||
group?: number;
|
||||
icon: string;
|
||||
@@ -65,3 +74,28 @@ export interface ICustomToolbarButton {
|
||||
key?: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export type NativeToolbarButton = 'camera' |
|
||||
'chat' |
|
||||
'microphone' |
|
||||
'raisehand' |
|
||||
'screensharing' |
|
||||
'tileview' |
|
||||
'overflowmenu' |
|
||||
'hangup';
|
||||
|
||||
export interface IGetVisibleNativeButtonsParams {
|
||||
allButtons: { [key: string]: IToolboxNativeButton; };
|
||||
clientWidth: number;
|
||||
mainToolbarButtonsThresholds: IMainToolbarButtonThresholds;
|
||||
toolbarButtons: string[];
|
||||
}
|
||||
|
||||
export interface IGetVisibleButtonsParams {
|
||||
allButtons: { [key: string]: IToolboxButton; };
|
||||
buttonsWithNotifyClick: Map<string, NOTIFY_CLICK_MODE>;
|
||||
clientWidth: number;
|
||||
jwtDisabledButtons: string[];
|
||||
mainToolbarButtonsThresholds: IMainToolbarButtonThresholds;
|
||||
toolbarButtons: string[];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { showErrorNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
|
||||
import { TRANSCRIBER_LEFT } from './actionTypes';
|
||||
import './subscriber';
|
||||
@@ -17,7 +16,7 @@ MiddlewareRegistry.register(({ dispatch }) => next => action => {
|
||||
if (action.abruptly) {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'transcribing.failed'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { shouldShowModeratedNotification } from '../av-moderation/functions';
|
||||
import { setAudioMuted, setVideoMuted } from '../base/media/actions';
|
||||
import { MEDIA_TYPE, MediaType, VIDEO_MUTISM_AUTHORITY } from '../base/media/constants';
|
||||
import { muteRemoteParticipant } from '../base/participants/actions';
|
||||
import { getLocalParticipant, getRemoteParticipants } from '../base/participants/functions';
|
||||
import { getRemoteParticipants } from '../base/participants/functions';
|
||||
import { toggleScreensharing } from '../base/tracks/actions';
|
||||
import { isModerationNotificationDisplayed } from '../notifications/functions';
|
||||
|
||||
@@ -36,7 +36,7 @@ export function muteLocal(enable: boolean, mediaType: MediaType, stopScreenShari
|
||||
}
|
||||
|
||||
// check for A/V Moderation when trying to unmute
|
||||
if (!enable && shouldShowModeratedNotification(MEDIA_TYPE.AUDIO, getState())) {
|
||||
if (isAudio && !enable && shouldShowModeratedNotification(MEDIA_TYPE.AUDIO, getState())) {
|
||||
if (!isModerationNotificationDisplayed(MEDIA_TYPE.AUDIO, getState())) {
|
||||
dispatch(showModeratedNotification(MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
@@ -88,11 +88,6 @@ export function muteRemote(participantId: string, mediaType: MediaType) {
|
||||
export function muteAllParticipants(exclude: Array<string>, mediaType: MediaType) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const localId = getLocalParticipant(state)?.id ?? '';
|
||||
|
||||
if (!exclude.includes(localId)) {
|
||||
dispatch(muteLocal(true, mediaType, mediaType !== MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
|
||||
getRemoteParticipants(state).forEach((p, id) => {
|
||||
if (exclude.includes(id)) {
|
||||
|
||||
@@ -105,7 +105,7 @@ function _inviteRooms(rooms: ISipRoom[], conference: IJitsiConference, dispatch:
|
||||
dispatch(showErrorNotification({
|
||||
descriptionKey: 'videoSIPGW.errorInvite',
|
||||
titleKey: 'videoSIPGW.errorInviteTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -159,14 +159,14 @@ function _sessionStateChanged(
|
||||
displayName: event.displayName
|
||||
},
|
||||
descriptionKey: 'videoSIPGW.errorInviteFailed'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG);
|
||||
});
|
||||
}
|
||||
case JitsiSIPVideoGWStatus.STATE_OFF: {
|
||||
if (event.failureReason === JitsiSIPVideoGWStatus.STATUS_BUSY) {
|
||||
return showErrorNotification({
|
||||
descriptionKey: 'videoSIPGW.busy',
|
||||
titleKey: 'videoSIPGW.busyTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG);
|
||||
});
|
||||
} else if (event.failureReason) {
|
||||
logger.error(`Unknown sip videogw error ${event.newState} ${
|
||||
event.failureReason}`);
|
||||
|
||||
@@ -84,6 +84,7 @@ s2s_whitelist = {
|
||||
```
|
||||
Component "visitors.jitmeet.example.com" "visitors_component"
|
||||
auto_allow_visitor_promotion = true
|
||||
admins = { "focus@auth.jitmeet.example.com" }
|
||||
```
|
||||
- Make sure you add the correct upstreams to nginx config
|
||||
```
|
||||
|
||||
@@ -48,7 +48,7 @@ function notify_occupants_enable(jid, enable, room, actorJid, mediaType)
|
||||
body_json.type = 'av_moderation';
|
||||
body_json.enabled = enable;
|
||||
body_json.room = internal_room_jid_match_rewrite(room.jid);
|
||||
body_json.actor = actorJid;
|
||||
body_json.actor = internal_room_jid_match_rewrite(actorJid);
|
||||
body_json.mediaType = mediaType;
|
||||
local body_json_str, error = json.encode(body_json);
|
||||
|
||||
|
||||
@@ -348,8 +348,11 @@ module:hook('muc-broadcast-presence', function (event)
|
||||
is_moderator = true;
|
||||
end
|
||||
elseif session.auth_token and auto_promoted_with_token then
|
||||
-- non-vpaas and having a token is considered a moderator
|
||||
is_moderator = true;
|
||||
if not session.jitsi_meet_tenant_mismatch or session.jitsi_web_query_prefix == '' then
|
||||
-- non-vpaas and having a token is considered a moderator, and if it is not in '/' tenant
|
||||
-- the tenant from url and token should match
|
||||
is_moderator = true;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
local jid = require 'util.jid';
|
||||
local json = require 'cjson.safe';
|
||||
local queue = require "util.queue";
|
||||
local uuid_gen = require "util.uuid".generate;
|
||||
local main_util = module:require "util";
|
||||
local ends_with = main_util.ends_with;
|
||||
local get_room_from_jid = main_util.get_room_from_jid;
|
||||
local is_healthcheck_room = main_util.is_healthcheck_room;
|
||||
local internal_room_jid_match_rewrite = main_util.internal_room_jid_match_rewrite;
|
||||
local presence_check_status = main_util.presence_check_status;
|
||||
@@ -13,9 +16,15 @@ end
|
||||
|
||||
local QUEUE_MAX_SIZE = 500;
|
||||
|
||||
-- Module that generates a unique meetingId, attaches it to the room
|
||||
-- and adds it to all disco info form data (when room is queried or in the
|
||||
-- initial room owner config)
|
||||
-- Common module for all logic that can be loaded under the conference muc component.
|
||||
--
|
||||
-- This module:
|
||||
-- a) Generates a unique meetingId, attaches it to the room and adds it to all disco info form data
|
||||
-- (when room is queried or in the initial room owner config).
|
||||
-- b) Updates user region (obtain it from the incoming http headers) in the occupant's presence on pre-join.
|
||||
-- c) Avoids any participant joining the room in the interval between creating the room and jicofo entering the room.
|
||||
-- d) Removes any nick that maybe set to messages being sent to the room.
|
||||
-- e) Fires event for received endpoint messages (optimization to decode them once).
|
||||
|
||||
-- Hook to assign meetingId for new rooms
|
||||
module:hook("muc-room-created", function(event)
|
||||
@@ -149,3 +158,82 @@ module:hook('jicofo-unlock-room', handle_jicofo_unlock);
|
||||
module:hook("muc-occupant-groupchat", function(event)
|
||||
event.stanza:remove_children('nick', 'http://jabber.org/protocol/nick');
|
||||
end, 45); -- prosody check is prio 50, we want to run after it
|
||||
|
||||
module:hook('message/bare', function(event)
|
||||
local stanza = event.stanza;
|
||||
|
||||
if stanza.attr.type ~= 'groupchat' then
|
||||
return nil;
|
||||
end
|
||||
|
||||
-- we are interested in all messages without a body
|
||||
local body = stanza:get_child('body')
|
||||
if body then
|
||||
return;
|
||||
end
|
||||
|
||||
local room = get_room_from_jid(stanza.attr.to);
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s', stanza.attr.to);
|
||||
return;
|
||||
end
|
||||
|
||||
local occupant_jid = stanza.attr.from;
|
||||
local occupant = room:get_occupant_by_real_jid(occupant_jid);
|
||||
if not occupant then
|
||||
module:log("error", "Occupant sending msg %s was not found in room %s", occupant_jid, room.jid)
|
||||
return;
|
||||
end
|
||||
|
||||
local json_message = stanza:get_child_text('json-message', 'http://jitsi.org/jitmeet');
|
||||
if not json_message then
|
||||
return;
|
||||
end
|
||||
|
||||
-- TODO: add optimization by moving type and certain fields like is_interim as attribute on 'json-message'
|
||||
-- using string find is roughly 70x faster than json decode for checking the value
|
||||
if string.find(json_message, '"is_interim":true', 1, true) then
|
||||
return;
|
||||
end
|
||||
|
||||
local msg_obj, error = json.decode(json_message);
|
||||
|
||||
if error then
|
||||
module:log('error', 'Error decoding data error:%s Sender: %s to:%s', error, stanza.attr.from, stanza.attr.to);
|
||||
return true;
|
||||
end
|
||||
|
||||
if msg_obj.transcript ~= nil then
|
||||
local transcription = msg_obj;
|
||||
|
||||
-- in case of the string matching optimization above failed
|
||||
if transcription.is_interim then
|
||||
return;
|
||||
end
|
||||
|
||||
-- TODO what if we have multiple alternative transcriptions not just 1
|
||||
local text_message = transcription.transcript[1].text;
|
||||
--do not send empty messages
|
||||
if text_message == '' then
|
||||
return;
|
||||
end
|
||||
|
||||
local user_id = transcription.participant.id;
|
||||
local who = room:get_occupant_by_nick(jid.bare(room.jid)..'/'..user_id);
|
||||
|
||||
transcription.jid = who and who.jid;
|
||||
transcription.session_id = room._data.meetingId;
|
||||
|
||||
local tenant, conference_name, id = extract_subdomain(jid.node(room.jid));
|
||||
transcription.fqn = tenant..'/'..conference_name;
|
||||
transcription.customer_id = id;
|
||||
|
||||
return module:fire_event('jitsi-transcript-received', {
|
||||
room = room, occupant = occupant, transcription = transcription, stanza = stanza });
|
||||
end
|
||||
|
||||
return module:fire_event('jitsi-endpoint-message-received', {
|
||||
room = room, occupant = occupant, message = msg_obj,
|
||||
origin = event.origin,
|
||||
stanza = stanza, raw_message = json_message });
|
||||
end);
|
||||
|
||||
@@ -14,31 +14,6 @@ local is_healthcheck_room = util.is_healthcheck_room;
|
||||
local POLLS_LIMIT = 128;
|
||||
local POLL_PAYLOAD_LIMIT = 1024;
|
||||
|
||||
-- Checks if the given stanza contains a JSON message,
|
||||
-- and that the message type pertains to the polls feature.
|
||||
-- If yes, returns the parsed message. Otherwise, returns nil.
|
||||
local function get_poll_message(stanza)
|
||||
if stanza.attr.type ~= "groupchat" then
|
||||
return nil;
|
||||
end
|
||||
local json_data = stanza:get_child_text("json-message", "http://jitsi.org/jitmeet");
|
||||
if json_data == nil then
|
||||
return nil;
|
||||
end
|
||||
if string.len(json_data) >= POLL_PAYLOAD_LIMIT then
|
||||
module:log('error', 'Poll payload too large, discarding. Sender: %s to:%s', stanza.attr.from, stanza.attr.to);
|
||||
return nil;
|
||||
end
|
||||
local data, error = json.decode(json_data);
|
||||
if not data or (data.type ~= "new-poll" and data.type ~= "answer-poll") then
|
||||
if error then
|
||||
module:log('error', 'Error decoding data error:%s', error);
|
||||
end
|
||||
return nil;
|
||||
end
|
||||
return data;
|
||||
end
|
||||
|
||||
-- Logs a warning and returns true if a room does not
|
||||
-- have poll data associated with it.
|
||||
local function check_polls(room)
|
||||
@@ -87,21 +62,22 @@ end);
|
||||
-- by listening to "new-poll" and "answer-poll" messages,
|
||||
-- and updating the room poll data accordingly.
|
||||
-- This mirrors the client-side poll update logic.
|
||||
module:hook("message/bare", function(event)
|
||||
local data = get_poll_message(event.stanza);
|
||||
if data == nil then return end
|
||||
module:hook('jitsi-endpoint-message-received', function(event)
|
||||
local data, error, occupant, room, origin, stanza
|
||||
= event.message, event.error, event.occupant, event.room, event.origin, event.stanza;
|
||||
|
||||
local room = muc.get_room_from_jid(event.stanza.attr.to);
|
||||
if not data or (data.type ~= "new-poll" and data.type ~= "answer-poll") then
|
||||
return;
|
||||
end
|
||||
|
||||
if string.len(event.raw_message) >= POLL_PAYLOAD_LIMIT then
|
||||
module:log('error', 'Poll payload too large, discarding. Sender: %s to:%s', stanza.attr.from, stanza.attr.to);
|
||||
return nil;
|
||||
end
|
||||
|
||||
if data.type == "new-poll" then
|
||||
if check_polls(room) then return end
|
||||
|
||||
local occupant_jid = event.stanza.attr.from;
|
||||
local occupant = room:get_occupant_by_real_jid(occupant_jid);
|
||||
if not occupant then
|
||||
module:log("error", "Occupant %s was not found in room %s", occupant_jid, room.jid)
|
||||
return
|
||||
end
|
||||
local poll_creator = get_occupant_details(occupant)
|
||||
if not poll_creator then
|
||||
module:log("error", "Cannot retrieve poll creator id and name for %s from %s", occupant.jid, room.jid)
|
||||
@@ -115,7 +91,7 @@ module:hook("message/bare", function(event)
|
||||
|
||||
if room.polls.by_id[data.pollId] ~= nil then
|
||||
module:log("error", "Poll already exists: %s", data.pollId);
|
||||
event.origin.send(st.error_reply(event.stanza, 'cancel', 'not-allowed', 'Poll already exists'));
|
||||
origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Poll already exists'));
|
||||
return true;
|
||||
end
|
||||
|
||||
@@ -150,16 +126,9 @@ module:hook("message/bare", function(event)
|
||||
}
|
||||
}
|
||||
module:fire_event("poll-created", pollData);
|
||||
|
||||
elseif data.type == "answer-poll" then
|
||||
if check_polls(room) then return end
|
||||
|
||||
local occupant_jid = event.stanza.attr.from;
|
||||
local occupant = room:get_occupant_by_real_jid(occupant_jid);
|
||||
if not occupant then
|
||||
module:log("error", "Occupant %s does not exists for room %s", occupant_jid, room.jid)
|
||||
return
|
||||
end
|
||||
local poll = room.polls.by_id[data.pollId];
|
||||
if poll == nil then
|
||||
module:log("warn", "answering inexistent poll");
|
||||
|
||||
@@ -5,16 +5,13 @@ local update_presence_identity = module:require "util".update_presence_identity;
|
||||
-- values are set in the session, then insert them into the presence messages
|
||||
-- for that session.
|
||||
function on_message(event)
|
||||
if event and event["stanza"] then
|
||||
if event.origin and event.origin.jitsi_meet_context_user then
|
||||
|
||||
local stanza, session = event.stanza, event.origin;
|
||||
if stanza and session then
|
||||
update_presence_identity(
|
||||
event.stanza,
|
||||
event.origin.jitsi_meet_context_user,
|
||||
event.origin.jitsi_meet_context_group
|
||||
stanza,
|
||||
session.jitsi_meet_context_user,
|
||||
session.jitsi_meet_context_group
|
||||
);
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user