Compare commits

...

113 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
39c1542286 fix(recording) skip consent dialog on Spot TV 2025-04-17 22:39:34 -05:00
Saúl Ibarra Corretgé
bf39fff501 fix(polls) halt processing of malformed polls
We need to return something other than nil in order to halt the
processing of the event.

https://prosody.im/doc/developers/moduleapi#modulehook_event_name_handler_priority
2025-04-17 22:39:19 -05:00
damencho
08d5466e5e fix(prosody): Fixes extracting domain from rooms without a domain. 2025-04-11 10:59:21 -05:00
Calin-Teodor
6966fa9eca fix(base/ui): fallback to og value if not found in allTokens 2025-04-09 10:59:47 +03:00
Calinteodor
1f2d999459 feat(base/ui): update tokens (#15688)
* Make createColorTokens generate values based on jitsiTokens and tokens
2025-04-09 10:59:40 +03:00
Mihaela Dumitru
c84f92075d fix(recording) disable option to dismiss consent dialog (#15876) 2025-04-08 16:56:16 +03:00
Дамян Минков
f506f85864 fix(jwt): Fix tenant matching for features when tenant is missing. 2025-03-28 16:37:12 -05:00
damencho
4bae526610 fix(tests): Tests improvements for validate shards.
fix(tests): Wait for the join button to disappear if it was there.

fix(tests): Make sure we give time dialog to submit password.

Give time for the dialog to disappear before entering the new password when retrying or checking for wrong password.

fix(tests): Adds some debug prints for a case reporting failure to leave.

fix(tests): When checking audio levels make sure testMode and debugAudioLevels are present.
2025-03-21 13:40:13 -05:00
Mihaela Dumitru
c13fdf7913 fix(recordings) skip consent for jibri participant (#15825) 2025-03-21 13:48:52 +02:00
Horatiu Muresan
cdcdd1b1bb feat(groupchat-polls-permissions) Implement groupchat and polls creation for web (#15806) 2025-03-20 10:07:28 -05:00
damencho
9017b24fee fix: Fixes dialout dialog and undefined error. 2025-03-12 11:41:19 -05:00
damencho
88cb4d4e00 fix(av-moderation): Skip hiding AV moderation menu. 2025-03-10 21:57:50 -05:00
Saúl Ibarra Corretgé
a88203cf03 fix(tracks) fix toggleCamera on mobile web browsers
Mobile web browsers may not allow opening multiple cameras at the same
time, this is a limitation coming from the underlying paltform. As such,
avoid createing a new track before we have disposed the previous one.

This does introduce a _slight_ fade to back effect, but the alternative
is an exception, so I'll take it! To make matters worse, this is not
necessarily a problem with all devices. It has been observed in a
Samsung S24, at least.
2025-03-10 20:07:35 -05:00
Дамян Минков
7455fe97fa feat(jwt): Adds an option to match tenant. (#15727)
* feat(jwt): Adds an option to match tenant.

There are cases where if tenant doesn't match features are ignore in the backend via prosody and session.jitsi_meet_tenant_mismatch.

* squash: add a comment explaining the change.

* squash: fix comments.

* squash: fix comments2.
2025-03-07 13:00:05 -06:00
Дамян Минков
0520e01194 fix(tests): Fixes the checks when to use token. (#15706)
* feat(tests): Fixes the checks when to use token.

We have few options:
- iframeAPI tests generating tokens via jwtPrivateKeyPath
- tests that just use provided JWT_ACCESS_TOKEN for the first participant to avoid deployments where initial authentication is required
- tests that does not use iframeAPI, but want to use the jwtPrivateKeyPath for a meeting (invite test as JWT_ACCESS_TOKEN does not satisfy some services)

* squash: Bump hangup wait, redirects may take a little bit more.

* squash: Rename forceGenerateToken to preferGenerateToken and fix logic when private key is missing.
2025-03-05 11:42:16 -06:00
Saúl Ibarra Corretgé
5524745099 fix(rn,recording) fix closing the consent dialog (#15699)
* fix(rn,recording) fix closing the consent dialog

* fix(recording) avoid using private properties
2025-03-05 14:56:56 +02:00
Mihaela Dumitru
1d1c7e2e31 feat(recordings) add consent dialog (#15673) 2025-03-05 14:56:45 +02:00
damencho
c9dd4d0f4d feat(tests): Validate shard tests. 2025-03-04 18:59:55 -06:00
Saúl Ibarra Corretgé
dbc06bd75a fix(breakout-rooms) fix processing commands (#15695)
* fix(breakout-rooms) fix processing commands

* squash: fix wrong var name.

* squash: fix move to breakout room.

It can be from breakout to main or from main to breakout.

---------

Co-authored-by: damencho <damencho@jitsi.org>
2025-03-04 12:30:30 -06:00
Hristo Terezov
7554dcad97 chore(package.json): use LJM from release-8443 branch 2025-02-28 12:05:54 -06:00
Jaya Allamsetty
a6f6235dd0 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1915.0.0+6e9b9c01...v1916.0.0+f5964f9b
2025-02-25 14:53:28 -05:00
Hristo Terezov
ee6bf011e9 feat(config): Add logger.warn for depricated params.
This includes interfaceConfig.SUPPORT_URL, interfaceConfig.LIVE_STREAMING_HELP_LINK, config.defaultLogoUrl, config.deploymentUrls, config.liveStreaming and config.customToolbarButtons.
2025-02-25 07:41:53 -06:00
Hristo Terezov
bea8a7f984 fix(configWhitelist): Remove customToolbarButtons. 2025-02-25 07:41:53 -06:00
Hristo Terezov
2edca5dacb fix(analytics): overwritesCustomButtonsWithURL metric
Count all customButtons overrides not only the ones that are not including data URLs.
2025-02-25 07:41:53 -06:00
Hristo Terezov
69ac73c556 feat(dynamic-branding): Add customToolbarButtons. 2025-02-25 07:41:53 -06:00
Hristo Terezov
89556ecd66 feat(dynamic-branding): Add customParticipantMenuButtons 2025-02-25 07:41:53 -06:00
Hristo Terezov
462f91f070 feat(dynamic-branding): Add etherpadBase 2025-02-25 07:41:53 -06:00
Hristo Terezov
d29a77b15f feat(dynamic-branding): Add peopleSearchUrl 2025-02-25 07:41:53 -06:00
Hristo Terezov
c31fe521c4 feat(analytics): remove overwritesPrejoinConfigICEUrl 2025-02-25 07:41:53 -06:00
Hristo Terezov
8f6f542e9c feat(inIframe-whitelists): Implement.
Now we are able to have a whitelist for config and interface config that will be used only for the case where jitsi-meet is loaded in an IFrame.
2025-02-25 07:41:53 -06:00
Hristo Terezov
69d9e7d405 ref(analytics): remove overwritesHosts 2025-02-25 07:41:53 -06:00
Hristo Terezov
5e6748a88a ref(analytics): remove overwritesIceServers 2025-02-25 07:41:53 -06:00
Hristo Terezov
8bc70f9c87 fix(iceServers): Restrict iceServers url param to iframe only. 2025-02-25 07:41:53 -06:00
Avram Tudor
357d226987 feat: allow specifying actions in custom notifications (#15666)
Co-authored-by: Avram Tudor <tudor.avram@8x8.com>
2025-02-25 12:43:18 +02:00
bgrozev
6b1f7138c6 fix: Check for ICE connected as part of ensureXParticipants. (#15664)
* fix: Check for ICE connected as part of ensureXParticipants.

* squash: Move waitForIceConnected and waitForSendReceiveData to ensure methods.

* squash: Check ICE first, then "send receive data", then remote streams. Report the correct failure.

---------

Co-authored-by: damencho <damencho@jitsi.org>
2025-02-24 21:08:33 -06:00
damencho
55219dc51b fix(tests): Fix test name in FF excludes. 2025-02-24 10:29:02 -06:00
damencho
0eb3a9a43c fix(tests): Temporary disable one check when FF is involved. 2025-02-21 15:28:57 -06:00
damencho
4d7136b7a7 fix(tests): AV moderation UI changes. 2025-02-21 15:28:57 -06:00
damencho
b7d9e1d85d fix(tests): Fix avatar test adding FF condition. 2025-02-21 15:28:57 -06:00
damencho
a714058328 fix(tests): Fixes Lobby disabled wait. 2025-02-21 15:28:57 -06:00
damencho
02ff4a1bac feat(tests): Drops unused field for setting password.
We require digit input and do not have a custom validation.
2025-02-21 15:28:57 -06:00
damencho
7833e1337e feat(tests): Adds keep-alive to newly created sessions.
Tests that take time (desktopSharing) before they use one of the browsers (the 4th one), by the time we use it backend may have timed out  the websocket (60 seconds). Add every 20 second and execute a print to keep it alive.
2025-02-21 15:28:57 -06:00
damencho
18e0e64ca0 fix(tests): Disable lastN test for FF. 2025-02-21 15:28:57 -06:00
damencho
80a3d88359 fix(tests): Disable AV moderation for FF. 2025-02-21 15:28:57 -06:00
damencho
5d72028872 feat(tests): Adds debug logs on failure. 2025-02-21 15:28:57 -06:00
damencho
e89776848c fix(tests): Use worker id to create console log files.
Avoid accumulating large files and keeping them per test.
2025-02-21 15:28:57 -06:00
damencho
70bc78e765 fix(tests): Disable startMuted on FF. 2025-02-21 15:28:57 -06:00
damencho
4fceae7733 fix(tests): Bumps global timeout for tests.
Desktop sharing is a long one.
2025-02-21 15:28:57 -06:00
damencho
23b7dd4abf fix(tests): Adds undefined checks. 2025-02-21 15:28:57 -06:00
damencho
0216bbd1d9 feat(tests): Adds an option to specify max instances. 2025-02-21 15:28:57 -06:00
damencho
15a4fa45e0 feat(tests): Adds target for grid ff tests. 2025-02-21 15:28:57 -06:00
damencho
f2d9ffd5f6 feat(tests): Handle checking for grid by updating merged config. 2025-02-21 15:28:57 -06:00
Rahul Vishwakarma
b0ba7c8671 lang: Update Italian. 2025-02-21 15:28:39 -06:00
damencho
e5fa25892e fix(logging): Keeps the log storage ready when there is conference error.
LogCollector stops saving logs the moment we leave the room, although we take care to stop statistics from ljm and throw events so we can flush the logs.
Flush on conference failed.
2025-02-21 12:35:50 -06:00
Hristo Terezov
ae5fe24556 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1914.0.0+c040dee9...v1915.0.0+6e9b9c01
2025-02-21 08:55:22 -06:00
Rahul Vishwakarma
b9ef0aa27a lang: Update hindi translation 2025-02-20 16:03:51 -06:00
Christoph Settgast
f30625acf0 lang: update German translation (#15650) 2025-02-20 21:49:52 +01:00
damencho
66d70305a0 fix(docs): Updates the extra large conf docs. 2025-02-20 13:37:46 -06:00
damencho
9108b7ebec fix(tests): Adopts tests to the AV moderation UI changes. 2025-02-19 21:39:43 -06:00
damencho
9454049220 fix(av-moderation): When we are allowed to unmute make the notification sticky.
If the notification disappears, we don't have any other indication about this.
We were not showing any notification if only video is allowed.
Adds option to unmute audio or video, depend on what was allowed.
2025-02-19 21:39:43 -06:00
damencho
2ce2e01803 fix(participants): Offer audio,video choice to allow a participant.
We were showing only one option in the notification that was allowing both at the same time.
We add not 3 option, allow audio, allow video or both.
2025-02-19 21:39:43 -06:00
damencho
ab25d6c5ab fix(participants-pan): Move the audio allow to be default.
When both audio and video is to be allowed, make the audio the first one to show nad video to stay in the 3-dots menu.
2025-02-19 21:39:43 -06:00
damencho
1b0dc0cfb0 fix(video-menu): When muting all skip local.
When muting multiple participants always skip the local one for audio and for video.
2025-02-19 21:39:43 -06:00
damencho
33e484a847 fix(fmuc): Updates auto-promote case checks. 2025-02-19 18:18:52 -06:00
Jaya Allamsetty
67bebc0491 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1912.0.0+522577a4...v1914.0.0+c040dee9
2025-02-19 17:09:28 -05:00
Mihaela Dumitru
9186a74ae3 fix(recordings) increase duration for recording prompt notification (#15632) 2025-02-19 19:44:24 +02:00
sargamgayatri0803@gmail.com
67d9a9819e fix:Auto-Scroll Issue in Poll Screen After Adding an Option 2025-02-19 13:17:54 +02:00
damencho
16b88a29db fix(avmoderation): Fix actor jid. 2025-02-19 04:37:38 -06:00
Дамян Минков
9783793514 fix(iframeAPI): Fix setSubtitles command language param.
setRequestingSubtitles requires the last parameter in certain format.
2025-02-19 08:04:43 +01:00
Axel Prola
93de398a09 feat : Add config to disable camera tint foreground (#15619)
Co-authored-by: Axel Prola <axel.prola@equasens.com>
2025-02-18 13:16:47 -06:00
Kevin Vikström
23e97a4284 lang: added language norwegian bokmal (#15594)
* added language norwegian bokmal

* added norwegian bokmål to languages.json
2025-02-18 07:13:07 -06:00
sargamgayatri0803@gmail.com
9bb906551e fix:(profile): ensure apply button remains visible when keyboard appears 2025-02-18 14:35:09 +02:00
Saúl Ibarra Corretgé
b1ad82cef9 fix(build) add .bundle to ignore files 2025-02-17 17:24:54 +01:00
Saúl Ibarra Corretgé
09c9f2930c fix(ios,build) add missing dependencies for fastlane 2025-02-17 17:24:54 +01:00
Saúl Ibarra Corretgé
74efbd7a61 feat(ios) introduce gemfile to make builds more reproducible
With it we can control what Ruby version, cocoapods version and fastlane
version is being used.
2025-02-17 16:17:34 +01:00
Saúl Ibarra Corretgé
1b1e7d9bce fix(ios,ci) use Xcode 16.2 for making iOS builds 2025-02-17 16:17:34 +01:00
damencho
dc98fc4839 feat(tests): Adds video layout test. 2025-02-14 12:00:49 -06:00
damencho
a815f97c7e feat(tests): Adds udp test. 2025-02-14 12:00:49 -06:00
damencho
8261cf2811 feat(tests): Adds tile view test. 2025-02-14 12:00:49 -06:00
damencho
f2238935b5 feat(tests): Adds switch video test. 2025-02-14 12:00:49 -06:00
damencho
5f12f76ada feat(tests): Adds subject test. 2025-02-14 12:00:49 -06:00
damencho
5a9464697f feat(tests): Adds stop video test. 2025-02-14 12:00:49 -06:00
damencho
f44601a82b feat(tests): Adds singlePort test. 2025-02-14 12:00:49 -06:00
damencho
3d3de4a884 feat(tests): Adds preJoin test. 2025-02-14 12:00:49 -06:00
damencho
c02ad56b6d feat(tests): Adds oneOnOne test. 2025-02-14 12:00:49 -06:00
damencho
ea7c5ccd58 fix(tests): Uses utility methods for mute/unmute. 2025-02-14 12:00:49 -06:00
Hristo Terezov
7ec3eae72b feat(test): Implement hangupAllParticipants 2025-02-14 11:07:00 -06:00
Hristo Terezov
edf7d18308 feat(tests): Print error on execute failure. 2025-02-14 11:07:00 -06:00
Hristo Terezov
6bf4a4e91d fix(tests): ensureTwoParticipants.
Now we are waiting for the second participant to join before starting waitForRemoteStreams.
2025-02-14 11:07:00 -06:00
damencho
5fd966f042 fix(tests): Adds mute test. 2025-02-13 14:40:28 -06:00
damencho
e275f20055 fix(tests): Moves muteAudio to ParticipantsPane. 2025-02-13 14:40:28 -06:00
damencho
ff624a34d8 feat(tests): Adds grant moderator test. 2025-02-13 14:40:28 -06:00
damencho
c98050224c feat(tests): Adds lock room with digits only test. 2025-02-13 14:40:28 -06:00
damencho
5bee373091 feat(tests): Adds lock room test. 2025-02-13 14:40:28 -06:00
Jaya Allamsetty
db4ab34ddf fix(tracks) Replace the tracks directly on camera toggle.
Fixes an issue where p2p peer stops rendering remote video when the mobile client toggles camera. This happens only when the peer starts video muted.
2025-02-13 11:37:22 -05:00
Calinteodor
ef138fb5aa feat(android/ios): start/stop recording events for native (#15598)
Added native android and ios events for start and stop recording.
2025-02-13 18:36:11 +02:00
Saúl Ibarra Corretgé
13bfdaed68 feat(external_api) facilitate gDM Electron
In order to use gDM in Electron the flow is somewhat reversed. It starts
from the Electron main process, so we need an API in the external_api
that can trigger the builtin picker. The picker is still necessary.
2025-02-13 12:12:19 +01:00
Saúl Ibarra Corretgé
ff656f4e6b fix(tracks) don't throw if creating a desktop track fails
There is nobody to catch it and we already show the error as a
notification.
2025-02-13 12:12:19 +01:00
Saúl Ibarra Corretgé
a27b78cef0 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1910.0.0+31897f9b...v1912.0.0+522577a4
2025-02-13 11:15:19 +01:00
damencho
4fa426ace0 fix: Fixes wrong state in password dialog.
Fixes the following: Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components

Detected due to failure while moving locked room test.
2025-02-12 18:13:32 -06:00
Дамян Минков
ac34f524fa feat(tests): Small improvements to dial-in test. (#15600)
* feat(tests): Small improvements to dial-in test.

* squash: Fix lint.
2025-02-12 15:32:14 -06:00
Joshua Irmer
31a4f2a4ec fix(prejoin): do not show conference info in prejoin or lobby (#15591)
* do not show conference info in prejoin or lobby

Signed-off-by: Joshua Irmer <irmer@gonicus.de>

* fix typo

Signed-off-by: Joshua Irmer <irmer@gonicus.de>

---------

Signed-off-by: Joshua Irmer <irmer@gonicus.de>
2025-02-12 09:53:35 -06:00
damencho
dc908512f9 feat(prosody): Updates checks in presence_identity avoids setting missing user. 2025-02-11 13:51:16 -06:00
Hristo Terezov
ae983645d1 fix(tests): add more time for getNotificationText.
The lobby tests were failing.
2025-02-11 10:22:10 -06:00
Mihaela Dumitru
3514b22191 fix(recordings) dismiss notification when recording in progress (#15588) 2025-02-11 17:06:23 +02:00
Calinteodor
405af3af5f feat(toolbox/native): reorganizing buttons in the toolbox and overflow menu (#15543)
Configures what buttons can be visible inside Toolbox and OverflowMenu, based on priority and config overrides, just like web does.
2025-02-11 16:17:13 +02:00
Mihaela Dumitru
a6d333c07a fix(recordings) improve label to clearly reflect current status (#15570) 2025-02-10 15:39:00 +02:00
damencho
0387cdc888 feat(notifications): Make all error notifications sticky.
There are many cases where the error disappears and users easily miss the information.
2025-02-10 06:17:50 -06:00
Calinteodor
f670f39dd2 feat(android/ios): Native API events for show/hide notification (#15577)
Added show/hide notification events for native Android/iOS
2025-02-10 11:34:50 +02:00
damencho
7262465777 feat(prosody): Introduces events for json messages and transcripts.
Optimizes json parsing of incoming messages. Now we do it in centralized place and firing an event.
2025-02-07 22:10:26 -06:00
Calin-Teodor
75b4049529 feat(android): use fresco 3.2.0 in order to fix animation for gifs 2025-02-07 17:08:19 +02:00
Calin-Teodor
ac6185424c dep(react-native): update to 0.75.5 2025-02-07 16:57:12 +02:00
Hristo Terezov
9e15df8e3d fix(analytics): remove overwritesWatchRTC* props 2025-02-06 17:02:13 -06:00
183 changed files with 6543 additions and 1791 deletions

View File

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

@@ -62,6 +62,7 @@ buck-out/
# fastlane
#
.bundle/
**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/test_output

16
Gemfile Normal file
View 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
View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -393,6 +393,9 @@ var config = {
// // showPrejoinWarning: true,
// // If true, the notification for recording start will display a link to download the cloud recording.
// // showRecordingLink: true,
// // If true, mutes audio and video when a recording begins and displays a dialog
// // explaining the effect of unmuting.
// // requireConsent: true,
// },
// recordingService: {
@@ -600,6 +603,7 @@ var config = {
// short: 2500,
// medium: 5000,
// long: 10000,
// extraLong: 60000,
// },
// // Options for the recording limit notification.
@@ -1371,8 +1375,12 @@ var config = {
The config file should be in JSON.
None of the fields are mandatory and the response must have the shape:
{
// Whether participant can only send group chat message if `send-groupchat` feature is enabled in jwt.
groupChatRequiresPermission: false,
// Whether participant can only create polls if `create-polls` feature is enabled in jwt.
pollCreationRequiresPermission: false,
// The domain url to apply (will replace the domain in the sharing conference link/embed section)
inviteDomain: 'example-company.org,
inviteDomain: 'example-company.org',
// The hex value for the colour used as background
backgroundColor: '#fff',
// The url for the image used as background
@@ -1560,6 +1568,8 @@ var config = {
// You can enable tokenAuthUrlAutoRedirect which will detect that you have logged in successfully before
// and will automatically redirect to the token service to get the token for the meeting.
// tokenAuthUrlAutoRedirect: false
// An option to respect the context.tenant jwt field compared to the current tenant from the url
// tokenRespectTenant: false,
// You can put an array of values to target different entity types in the invite dialog.
// Valid values are "phone", "room", "sip", "user", "videosipgw" and "email"
@@ -1857,6 +1867,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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -37,6 +37,7 @@
"ml": "മലയാളം",
"mn": "Монгол",
"mr": "मराठी",
"nb": "Norsk bokmål",
"nl": "Nederlands",
"oc": "Occitan",
"pl": "Polski",

View File

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

View File

@@ -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 werent 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": "Youre 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": "कनेक्शन जानकारी",

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -263,6 +263,7 @@
"Remove": "Remove",
"Share": "Share",
"Submit": "Submit",
"Understand": "I understand",
"WaitForHostMsg": "The conference has not yet started because no moderators have yet arrived. If you'd like to become a moderator please log-in. Otherwise, please wait.",
"WaitForHostNoAuthMsg": "The conference has not yet started because no moderators have yet arrived. Please wait.",
"WaitingForHostButton": "Wait for moderator",
@@ -393,6 +394,8 @@
"recentlyUsedObjects": "Your recently used objects",
"recording": "Recording",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Not possible while a live stream is active",
"recordingInProgressDescription": "This meeting is being recorded. Your audio and video have been muted. If you choose to unmute, you consent to being recorded.",
"recordingInProgressTitle": "Recording in progress",
"rejoinNow": "Rejoin now",
"remoteControlAllowedMessage": "{{user}} accepted your remote control request!",
"remoteControlDeniedMessage": "{{user}} rejected your remote control request!",
@@ -733,7 +736,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 +760,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 +812,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 +829,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.",
@@ -842,7 +848,7 @@
"actions": {
"admit": "Admit",
"admitAll": "Admit all",
"allow": "Allow attendees to:",
"allow": "Allow non-moderators to:",
"allowVideo": "Allow video",
"askUnmute": "Ask to unmute",
"audioModeration": "Unmute themselves",
@@ -1405,7 +1411,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 +1451,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",

View File

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

View File

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

View File

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

241
package-lock.json generated
View File

@@ -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/v1910.0.0+31897f9b/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#release-8443",
"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,7 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1910.0.0+31897f9b/lib-jitsi-meet.tgz",
"integrity": "sha512-SOG0BWijsUoowGQJnrmQYcR1sfV4bR5El/D3bqSD63PXlX2uZ+fYaMYzjGkyEKyN7EgHvorPdV57p5QHbQsQ5Q==",
"resolved": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#8847230f2dbbb63d17077e5804862ddbc50402df",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -20457,22 +20456,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 +29741,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 +29800,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 +29813,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 +29843,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 +29906,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 +29945,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 +29981,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 +37636,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1910.0.0+31897f9b/lib-jitsi-meet.tgz",
"integrity": "sha512-SOG0BWijsUoowGQJnrmQYcR1sfV4bR5El/D3bqSD63PXlX2uZ+fYaMYzjGkyEKyN7EgHvorPdV57p5QHbQsQ5Q==",
"version": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#8847230f2dbbb63d17077e5804862ddbc50402df",
"from": "lib-jitsi-meet@https://github.com/jitsi/lib-jitsi-meet#release-8443",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
@@ -40182,21 +40181,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",

View File

@@ -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/v1910.0.0+31897f9b/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#release-8443",
"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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
@@ -540,6 +542,7 @@ export interface IConfig {
recordingSharingUrl?: string;
recordings?: {
recordAudioAndVideo?: boolean;
requireConsent?: boolean;
showPrejoinWarning?: boolean;
showRecordingLink?: boolean;
suggestRecording?: boolean;
@@ -600,6 +603,7 @@ export interface IConfig {
tokenAuthUrl?: string;
tokenAuthUrlAutoRedirect?: string;
tokenLogoutUrl?: string;
tokenRespectTenant?: string;
toolbarButtons?: Array<ToolbarButton>;
toolbarConfig?: {
alwaysVisible?: boolean;

View File

@@ -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',
@@ -202,7 +205,10 @@ export default [
'remoteVideoMenu',
'roomPasswordNumberOfDigits',
'readOnlyName',
'recordings',
'recordings.recordAudioAndVideo',
'recordings.showPrejoinWarning',
'recordings.showRecordingLink',
'recordings.suggestRecording',
'replaceParticipant',
'resolution',
'screenshotCapture',
@@ -242,4 +248,4 @@ export default [
'webrtcIceTcpDisable',
'webrtcIceUdpDisable',
'whiteboard.enabled'
].concat(extraConfigWhitelist);
].concat(extraConfigWhitelist).concat(inIframe() ? inIframeConfigWhitelist : []);

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -36,6 +36,11 @@ interface IProps extends AbstractProps, WithTranslation {
*/
descriptionKey?: string | { key: string; params: string; };
/**
* Whether the cancel button is hidden.
*/
isCancelHidden?: Boolean;
/**
* Whether or not the nature of the confirm button is destructive.
*/
@@ -100,6 +105,7 @@ class ConfirmDialog extends AbstractDialog<IProps> {
cancelLabel,
children,
confirmLabel,
isCancelHidden,
isConfirmDestructive,
isConfirmHidden,
t,
@@ -121,10 +127,12 @@ class ConfirmDialog extends AbstractDialog<IProps> {
}
{ this._renderDescription() }
{ children }
<Dialog.Button
label = { t(cancelLabel || 'dialog.confirmNo') }
onPress = { this._onCancel }
style = { styles.dialogButton } />
{
!isCancelHidden && <Dialog.Button
label = { t(cancelLabel || 'dialog.confirmNo') }
onPress = { this._onCancel }
style = { styles.dialogButton } />
}
{
!isConfirmHidden && <Dialog.Button
label = { t(confirmLabel || 'dialog.confirmYes') }

View File

@@ -1,9 +1,12 @@
import { ParticipantFeaturesKey } from '../participants/types';
/**
* The list of supported meeting features to enable/disable through jwt.
*/
export const MEET_FEATURES = {
export const MEET_FEATURES: Record<string, ParticipantFeaturesKey> = {
BRANDING: 'branding',
CALENDAR: 'calendar',
CREATE_POLLS: 'create-polls',
FLIP: 'flip',
INBOUND_CALL: 'inbound-call',
LIVESTREAMING: 'livestreaming',
@@ -13,6 +16,7 @@ export const MEET_FEATURES = {
RECORDING: 'recording',
ROOM: 'room',
SCREEN_SHARING: 'screen-sharing',
SEND_GROUPCHAT: 'send-groupchat',
SIP_INBOUND_CALL: 'sip-inbound-call',
SIP_OUTBOUND_CALL: 'sip-outbound-call',
TRANSCRIPTION: 'transcription'

View File

@@ -4,7 +4,7 @@ import jwtDecode from 'jwt-decode';
import { IReduxState } from '../../app/types';
import { isVpaasMeeting } from '../../jaas/functions';
import { getLocalParticipant } from '../participants/functions';
import { IParticipantFeatures } from '../participants/types';
import { IParticipantFeatures, ParticipantFeaturesKey } from '../participants/types';
import { parseURLParams } from '../util/parseURLParams';
import { JWT_VALIDATION_ERRORS, MEET_FEATURES } from './constants';
@@ -49,7 +49,12 @@ export function getJwtName(state: IReduxState) {
* @param {boolean} ifNotInFeatures - Default value if features prop exists but does not have the {@code feature}.
* @returns {boolean}
*/
export function isJwtFeatureEnabled(state: IReduxState, feature: string, ifNoToken: boolean, ifNotInFeatures: boolean) {
export function isJwtFeatureEnabled(
state: IReduxState,
feature: ParticipantFeaturesKey,
ifNoToken: boolean,
ifNotInFeatures: boolean
) {
const { jwt } = state['features/base/jwt'];
let { features } = getLocalParticipant(state) || {};
@@ -68,7 +73,7 @@ export function isJwtFeatureEnabled(state: IReduxState, feature: string, ifNoTok
}
interface IIsJwtFeatureEnabledStatelessParams {
feature: string;
feature: ParticipantFeaturesKey;
ifNoToken: boolean;
ifNotInFeatures: boolean;
jwt?: string;
@@ -101,11 +106,11 @@ export function isJwtFeatureEnabledStateless({
return ifNoToken;
}
if (typeof features[feature as keyof typeof features] === 'undefined') {
if (typeof features[feature] === 'undefined') {
return ifNotInFeatures;
}
return String(features[feature as keyof typeof features]) === 'true';
return String(features[feature]) === 'true';
}
/**
@@ -202,7 +207,7 @@ export function validateJwt(jwt: string) {
const { features } = context;
const meetFeatures = Object.values(MEET_FEATURES);
Object.keys(features).forEach(feature => {
(Object.keys(features) as ParticipantFeaturesKey[]).forEach(feature => {
if (meetFeatures.includes(feature)) {
const featureValue = features[feature];

View File

@@ -3,12 +3,14 @@ import jwtDecode from 'jwt-decode';
import { AnyAction } from 'redux';
import { IStore } from '../../app/types';
import { isVpaasMeeting } from '../../jaas/functions';
import { SET_CONFIG } from '../config/actionTypes';
import { SET_LOCATION_URL } from '../connection/actionTypes';
import { participantUpdated } from '../participants/actions';
import { getLocalParticipant } from '../participants/functions';
import { IParticipant } from '../participants/types';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { parseURIString } from '../util/uri';
import { SET_JWT } from './actionTypes';
import { setJWT } from './actions';
@@ -125,6 +127,8 @@ function _setJWT(store: IStore, next: Function, action: AnyAction) {
const { jwt, type, ...actionPayload } = action;
if (!Object.keys(actionPayload).length) {
const state = store.getState();
if (jwt) {
let jwtPayload;
@@ -150,9 +154,22 @@ function _setJWT(store: IStore, next: Function, action: AnyAction) {
const newUser = user ? { ...user } : {};
let features = context.features;
const { tokenRespectTenant } = state['features/base/config'];
// eslint-disable-next-line max-depth
if (!isVpaasMeeting(state) && tokenRespectTenant && context.tenant) {
// we skip checking vpaas meetings as there are other backend rules in place
// this way vpaas users can still use this field if needed
const { locationURL = { href: '' } as URL } = state['features/base/connection'];
const { tenant = '' } = parseURIString(locationURL.href) || {};
features = context.tenant === tenant || tenant === '' ? features : {};
}
_overwriteLocalParticipant(
store, { ...newUser,
features: context.features });
features });
// eslint-disable-next-line max-depth
if (context.user && context.user.role === 'visitor') {
@@ -172,7 +189,7 @@ function _setJWT(store: IStore, next: Function, action: AnyAction) {
// On Web it should eventually be restored from storage, but there's
// no such use case yet.
const { user } = store.getState()['features/base/jwt'];
const { user } = state['features/base/jwt'];
user && _undoOverwriteLocalParticipant(store, user);
}

View File

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

View File

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

View File

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

View File

@@ -57,6 +57,7 @@ export interface ILocalParticipant extends IParticipant {
export interface IParticipantFeatures {
'branding'?: boolean | string;
'calendar'?: boolean | string;
'create-polls'?: boolean | string;
'flip'?: boolean | string;
'inbound-call'?: boolean | string;
'livestreaming'?: boolean | string;
@@ -66,6 +67,7 @@ export interface IParticipantFeatures {
'recording'?: boolean | string;
'room'?: boolean | string;
'screen-sharing'?: boolean | string;
'send-groupchat'?: boolean | string;
'sip-inbound-call'?: boolean | string;
'sip-outbound-call'?: boolean | string;
'transcription'?: boolean | string;
@@ -84,3 +86,5 @@ export interface IJitsiParticipant {
getSources: () => Map<string, Map<string, ISourceInfo>>;
isHidden: () => boolean;
}
export type ParticipantFeaturesKey = keyof IParticipantFeatures;

View File

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

View File

@@ -6,7 +6,6 @@ import { NOTIFICATION_TIMEOUT, NOTIFICATION_TIMEOUT_TYPE } from '../../notificat
import { getCurrentConference } from '../conference/functions';
import { IJitsiConference } from '../conference/reducer';
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
import { createLocalTrack } from '../lib-jitsi-meet/functions.any';
import { setAudioMuted, setScreenshareMuted, setVideoMuted } from '../media/actions';
import {
CAMERA_FACING_MODE,
@@ -17,7 +16,6 @@ import {
VideoType
} from '../media/constants';
import { getLocalParticipant } from '../participants/functions';
import { updateSettings } from '../settings/actions';
import {
SET_NO_SRC_DATA_NOTIFICATION_UID,
@@ -291,7 +289,7 @@ export function showNoDataFromSourceVideoError(jitsiTrack: any) {
const notificationAction = dispatch(showErrorNotification({
descriptionKey: 'dialog.cameraNotSendingData',
titleKey: 'dialog.cameraNotSendingDataTitle'
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}));
notificationInfo = {
uid: notificationAction?.uid
@@ -825,39 +823,3 @@ export function setNoSrcDataNotificationUid(uid?: string) {
uid
};
}
/**
* Toggles the facingMode constraint on the video stream.
*
* @returns {Function}
*/
export function toggleCamera() {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const tracks = state['features/base/tracks'];
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;
// Update the flipX value so the environment facing camera is not flipped, before the new track is created.
dispatch(updateSettings({ localFlipX: targetFacingMode === CAMERA_FACING_MODE.USER ? localFlipX : false }));
const newVideoTrack = await createLocalTrack('video', null, null, { facingMode: targetFacingMode });
// FIXME: See above.
await APP.conference.useVideoStream(newVideoTrack);
};
}

View File

@@ -16,15 +16,18 @@ import { getCurrentConference } from '../conference/functions';
import { notifyCameraError, notifyMicError } from '../devices/actions.web';
import { openDialog } from '../dialog/actions';
import { JitsiTrackErrors, JitsiTrackEvents, browser } from '../lib-jitsi-meet';
import { createLocalTrack } from '../lib-jitsi-meet/functions.any';
import { gumPending, setScreenshareMuted } from '../media/actions';
import { MEDIA_TYPE, MediaType, VIDEO_TYPE } from '../media/constants';
import { IGUMPendingState } from '../media/types';
import {
addLocalTrack,
replaceLocalTrack,
toggleCamera
} from './actions.any';
CAMERA_FACING_MODE,
MEDIA_TYPE,
MediaType,
VIDEO_TYPE,
} from '../media/constants';
import { IGUMPendingState } from '../media/types';
import { updateSettings } from '../settings/actions';
import { addLocalTrack, replaceLocalTrack } from './actions.any';
import AllowToggleCameraDialog from './components/web/AllowToggleCameraDialog';
import {
createLocalTracksF,
@@ -157,9 +160,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 +174,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 +460,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 +479,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 +509,34 @@ export function handleScreenSharingError(
dispatch(showErrorNotification({
descriptionKey,
titleKey
}, timeout));
}));
};
}
/**
* Toggles the facingMode constraint on the video stream.
*
* @returns {Function}
*/
export function toggleCamera() {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const tracks = state['features/base/tracks'];
const localVideoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
const currentFacingMode = localVideoTrack.getCameraFacingMode();
const { localFlipX } = state['features/base/settings'];
const targetFacingMode = currentFacingMode === CAMERA_FACING_MODE.USER
? CAMERA_FACING_MODE.ENVIRONMENT
: CAMERA_FACING_MODE.USER;
// Update the flipX value so the environment facing camera is not flipped, before the new track is created.
dispatch(updateSettings({ localFlipX: targetFacingMode === CAMERA_FACING_MODE.USER ? localFlipX : false }));
// On mobile only one camera can be open at a time, so first stop the current camera track.
await dispatch(replaceLocalTrack(localVideoTrack, null));
const newVideoTrack = await createLocalTrack('video', null, null, { facingMode: targetFacingMode });
await dispatch(replaceLocalTrack(null, newVideoTrack));
};
}

View File

@@ -1,49 +1,3 @@
// Default color palette
export const colors = {
error03: '#7A141F',
error04: '#A21B29',
error05: '#CB2233',
error06: '#D83848',
error08: '#F24D5F',
primary01: '#00112D',
primary02: '#00225A',
primary03: '#003486',
primary04: '#0045B3',
primary05: '#0056E0',
primary06: '#246FE5',
primary07: '#4687ED',
primary08: '#99BBF3',
primary09: '#CCDDF9',
surface01: '#040404',
surface02: '#141414',
surface03: '#292929',
surface04: '#3D3D3D',
surface05: '#525252',
surface06: '#666',
surface07: '#858585',
surface08: '#A3A3A3',
surface09: '#C2C2C2',
surface10: '#E0E0E0',
surface11: '#FFF',
success04: '#189B55',
success05: '#1EC26A',
warning05: '#F8AE1A',
warning06: '#FFD600',
support01: '#FF9B42',
support02: '#F96E57',
support03: '#DF486F',
support04: '#B23683',
support05: '#73348C',
support06: '#6A50D3',
support07: '#4380E2',
support08: '#00A8B3',
support09: '#2AA076'
};
// Mapping between the token used and the color
export const colorMap = {
@@ -55,108 +9,110 @@ export const colorMap = {
// Container backgrounds
ui01: 'surface02',
ui02: 'surface03',
ui03: 'surface04',
ui03: 'ui02',
ui04: 'surface05',
ui05: 'surface06',
ui06: 'surface07',
ui05: 'ui01',
ui06: 'ui03',
ui07: 'surface08',
ui08: 'surface09',
ui09: 'surface10',
ui10: 'surface11',
ui08: 'ui21',
ui09: 'ui08',
ui10: 'ui04',
// ----- Actions -----
// Primary
action01: 'primary06',
action01Hover: 'primary07',
action01Active: 'primary04',
action01: 'action01',
action01Hover: 'hover01',
action01Active: 'active01',
// Secondary
action02: 'surface10',
action02Hover: 'surface11',
action02Active: 'surface09',
action02: 'action02',
action02Hover: 'hover02',
action02Active: 'active02',
// Destructive
actionDanger: 'error05',
actionDangerHover: 'error06',
actionDangerActive: 'error04',
actionDanger: 'action03',
actionDangerHover: 'hover03',
actionDangerActive: 'active03',
// Tertiary
action03: 'transparent',
action03Hover: 'surface04',
action03Hover: 'hover05',
action03Active: 'surface03',
// Disabled
disabled01: 'surface09',
disabled01: 'disabled01',
// Focus
focus01: 'primary07',
focus01: 'focus01',
// ----- Links -----
link01: 'primary07',
link01Hover: 'primary08',
link01Active: 'primary06',
link01: 'action01',
link01Hover: 'hover07',
link01Active: 'action04',
// ----- Text -----
// Primary
text01: 'surface11',
text01: 'textColor01',
// Secondary
text02: 'surface09',
text02: 'textColor02',
// Tertiary
text03: 'surface07',
text03: 'ui03',
// High-contrast
text04: 'surface01',
// Error
textError: 'error08',
textError: 'alertRed',
// ----- Icons -----
// Primary
icon01: 'surface11',
icon01: 'icon01',
// Secondary
icon02: 'surface09',
icon02: 'ui21',
// Tertiary
icon03: 'surface07',
icon03: 'icon07',
// High-contrast
icon04: 'surface01',
// Error
iconError: 'error06',
iconError: 'action03',
// Normal
iconNormal: 'action04',
// Success
iconSuccess: 'alertGreen',
// Warning
iconWarning: 'warning01',
// ----- Forms -----
field01: 'surface04',
field01: 'ui02',
// ----- Feedback -----
// Success
success01: 'success05',
success02: 'success04',
success02: 'success01',
// Warning
warning01: 'warning05',
warning01: 'warning01',
warning02: 'warning06',
// ----- Support -----
support01: 'support01',
support02: 'support02',
support03: 'support03',
support04: 'support04',
support05: 'support05',
support06: 'support06',
support07: 'support07',
support08: 'support08',
support09: 'support09'
support06: 'support06'
};
@@ -175,19 +131,9 @@ export const spacing
= [ 0, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128 ];
export const typography = {
labelRegular: {
fontSize: 12,
lineHeight: 16,
fontWeight: font.weightRegular,
letterSpacing: 0.16
},
labelRegular: 'label01',
labelBold: {
fontSize: 12,
lineHeight: 16,
fontWeight: font.weightSemiBold,
letterSpacing: 0.16
},
labelBold: 'labelBold01',
bodyShortRegularSmall: {
fontSize: 10,
@@ -252,19 +198,9 @@ export const typography = {
letterSpacing: 0
},
heading1: {
fontSize: 54,
lineHeight: 64,
fontWeight: font.weightSemiBold,
letterSpacing: 0
},
heading1: 'heading01',
heading2: {
fontSize: 42,
lineHeight: 50,
fontWeight: font.weightSemiBold,
letterSpacing: 0
},
heading2: 'heading02',
heading3: {
fontSize: 32,

View File

@@ -1,11 +1,10 @@
import { colorMap, colors, font, shape, spacing, typography } from '../Tokens';
import { colorMap, font, shape, spacing, typography } from '../Tokens';
import { createNativeTheme } from '../functions.native';
import updateTheme from './updateTheme.native';
export default createNativeTheme(updateTheme({
font,
colors,
colorMap,
spacing,
shape,

View File

@@ -1,9 +1,8 @@
import { breakpoints, colorMap, colors, font, shape, spacing, typography } from '../Tokens';
import { breakpoints, colorMap, font, shape, spacing, typography } from '../Tokens';
import { createWebTheme } from '../functions.web';
export default createWebTheme({
font,
colors,
colorMap,
spacing,
shape,

View File

@@ -8,10 +8,10 @@ import { createColorTokens } from './utils';
* @param {Object} arg - The ui tokens.
* @returns {Object}
*/
export function createNativeTheme({ font, colors, colorMap, shape, spacing, typography }: any): any {
export function createNativeTheme({ font, colorMap, shape, spacing, typography }: any): any {
return {
...DefaultTheme,
palette: createColorTokens(colorMap, colors),
palette: createColorTokens(colorMap),
shape,
spacing,
typography: {

View File

@@ -16,7 +16,6 @@ declare module '@mui/material/styles' {
interface ThemeProps {
breakpoints: Object;
colorMap: Object;
colors: Object;
font: Object;
shape: Object;
spacing: Array<number>;
@@ -29,10 +28,10 @@ interface ThemeProps {
* @param {Object} arg - The ui tokens.
* @returns {Object}
*/
export function createWebTheme({ font, colors, colorMap, shape, spacing, typography, breakpoints }: ThemeProps) {
export function createWebTheme({ font, colorMap, shape, spacing, typography, breakpoints }: ThemeProps) {
return createTheme(adaptV4Theme({
spacing,
palette: createColorTokens(colorMap, colors),
palette: createColorTokens(colorMap),
shape,
typography: {
// @ts-ignore

View File

@@ -0,0 +1,14 @@
{
"success05": "#1EC26A",
"support05": "#73348C",
"support06": "#6A50D3",
"surface01": "#040404",
"surface02": "#141414",
"surface03": "#292929",
"surface05": "#525252",
"surface08": "#A3A3A3",
"warning06": "#FFD600"
}

View File

@@ -0,0 +1,211 @@
{
"action01": "#4687ED",
"action02": "#E0E0E0",
"action03": "#D83848",
"action04": "#246FE5",
"action05": "#4687ED",
"action06": "#0056E0",
"action07": "#D7E3F9",
"action08": "#99BBF3",
"action09": "#4687ED",
"active01": "#0056E0",
"active02": "#C2C2C2",
"active03": "#CB2233",
"active04": "#189B55",
"active05": "#666666",
"active07": "#0056E0",
"active08": "#525252",
"active09": "#3D3D3D",
"active10": "#3D3D3D",
"active11": "#CCDDF9",
"active12": "#666666",
"alertGreen": "#189B55",
"alertRed": "#F24D5F",
"alertYellow": "#F8AE1A",
"body01": {
"fontFamily": "Inter, sans-serif",
"fontSize": "0.875rem",
"lineHeight": "1.25rem",
"fontWeight": 400,
"letterSpacing": "-0.006rem"
},
"body02": {
"fontFamily": "Inter, sans-serif",
"fontSize": "1rem",
"lineHeight": "1.5rem",
"fontWeight": 400,
"letterSpacing": "-0.011rem"
},
"bodyBold01": {
"fontFamily": "Inter, sans-serif",
"fontSize": "0.875rem",
"lineHeight": "1.25rem",
"fontWeight": 600,
"letterSpacing": "-0.006rem"
},
"bodyBold02": {
"fontFamily": "Inter, sans-serif",
"fontSize": "1rem",
"lineHeight": "1.5rem",
"fontWeight": 600,
"letterSpacing": "-0.011rem"
},
"brand01": "#FFFFFF",
"bulletList01": {
"fontFamily": "Inter, sans-serif",
"fontSize": "0.875rem",
"lineHeight": "1.5rem",
"fontWeight": 400,
"letterSpacing": "-0.006rem"
},
"data01": "#C15C97",
"data02": "#4079D3",
"data03": "#A276B2",
"data04": "#4CAC9C",
"data05": "#E76782",
"data06": "#8CA7D1",
"data07": "#B23683",
"data08": "#FFA95E",
"data09": "#8B559F",
"data10": "#009B89",
"data11": "#858585",
"dataText01": "#292929",
"dataText02": "#292929",
"dataText03": "#292929",
"dataText04": "#000000",
"dataText05": "#000000",
"dataText06": "#000000",
"dataText07": "#FFFFFF",
"dataText08": "#000000",
"dataText09": "#FFFFFF",
"dataText10": "#FFFFFF",
"dataText11": "#000000",
"disabled01": "#C2C2C2",
"disabled02": "#666666",
"disabled03": "#C2C2C2",
"disabled04": "#858585",
"error01": "#F24D5F",
"focus01": "#D7E3F9",
"heading01": {
"fontFamily": "Inter, sans-serif",
"fontSize": "1.25rem",
"lineHeight": "1.75rem",
"fontWeight": 600,
"letterSpacing": "-0.017rem"
},
"heading02": {
"fontFamily": "Inter, sans-serif",
"fontSize": "1.75rem",
"lineHeight": "2.5rem",
"fontWeight": 600,
"letterSpacing": "-0.020rem"
},
"highlight01": "#F8AE1A",
"hover01": "#4687ED",
"hover02": "#F1F1F1",
"hover03": "#F24D5F",
"hover04": "#4BCE88",
"hover05": "#3D3D3D",
"hover06": "#666666",
"hover07": "#99BBF3",
"hover08": "#2F2E32",
"hover09": "#666666",
"hover10": "#292929",
"hover11": "#99BBF3",
"hover12": "#3D3D3D",
"hover13": "#525252",
"icon01": "#FFFFFF",
"icon02": "#FFFFFF",
"icon03": "#666666",
"icon04": "#0056E0",
"icon05": "#292929",
"icon06": "#666666",
"icon07": "#858585",
"icon08": "#292929",
"info01": "#666666",
"label01": {
"fontFamily": "Inter, sans-serif",
"fontSize": "0.75rem",
"lineHeight": "1rem",
"fontWeight": 400,
"letterSpacing": "normal"
},
"labelBold01": {
"fontFamily": "Inter, sans-serif",
"fontSize": "0.75rem",
"lineHeight": "1rem",
"fontWeight": 600,
"letterSpacing": "normal"
},
"overlay01": "#292929BF",
"shadow01": "#141414",
"shadowHigh": "0px 2px 20px 9px #141414",
"shadowLow": "0px 1px 2px 1px #141414",
"shadowMedium": "0px 2px 8px 2px #141414",
"statusAvailable01": "#189B55",
"statusAway01": "#DD7011",
"statusBusy01": "#F24D5F",
"statusOffline01": "#666666",
"statusWrapup01": "#8B559F",
"success01": "#189B55",
"textColor01": "#FFFFFF",
"textColor02": "#C2C2C2",
"textColor04": "#292929",
"textColor05": "#C2C2C2",
"textColor06": "#FFFFFF",
"textColor07": "#292929",
"textColor08": "#FFFFFF",
"textColor09": "#000000",
"textColor10": "#E7E3FF",
"ui01": "#666666",
"ui02": "#3D3D3D",
"ui03": "#858585",
"ui04": "#FFFFFF",
"ui05": "#2F2E32",
"ui06": "#171719",
"ui07": "#F1F1F1",
"ui08": "#E0E0E0",
"ui09": "#CCDDF9",
"ui10": "#666666",
"ui11": "#0C0C0D",
"ui12": "#212124",
"ui13": "#003486",
"ui14": "#127440",
"ui15": "#00225A",
"ui16": "#F5D3D6",
"ui17": "#FEEFD1",
"ui18": "#D2F3E1",
"ui19": "#E0E0E0",
"ui20": "#E8DCEC",
"ui21": "#C2C2C2",
"ui22": "#CCDDF9",
"ui23": "#3C2F8E",
"ui24": "#5F50BE",
"ui25": "#171719",
"warning01": "#F8AE1A",
"warning02": "#F8AE1A",
"warning03": "#9F701C"
}

View File

@@ -1,13 +1,21 @@
import { merge } from 'lodash';
import * as jitsiTokens from './jitsiTokens.json';
import * as tokens from './tokens.json';
/**
* Creates the color tokens based on the color theme and the association map.
* If a key is not found in the association map it defaults to the current value.
*
* @param {Object} colorMap - A map between the token name and the actual color value.
* @param {Object} colors - An object containing all the theme colors.
* @returns {Object}
*/
export function createColorTokens(colorMap: Object, colors: Object): any {
export function createColorTokens(colorMap: Object): any {
const allTokens = merge({}, tokens, jitsiTokens);
return Object.entries(colorMap)
.reduce((result, [ token, value ]: [any, keyof Object]) =>
Object.assign(result, { [token]: colors[value] || value }), {});
.reduce((result, [ token, value ]: [any, string]) => {
const color = allTokens[value as keyof typeof allTokens] || value;
return Object.assign(result, { [token]: color });
}, {});
}

View File

@@ -0,0 +1,9 @@
/**
* Checks if Jitsi Meet is running on Spot TV.
*
* @returns {boolean} Whether or not Jitsi Meet is running on Spot TV.
*/
export function isSpotTV(): boolean {
return navigator.userAgent.includes('SpotElectron/');
}

View File

@@ -8,7 +8,7 @@ import { translate } from '../../../base/i18n/functions';
import { IconFaceSmile, IconSend } from '../../../base/icons/svg';
import Button from '../../../base/ui/components/web/Button';
import Input from '../../../base/ui/components/web/Input';
import { areSmileysDisabled } from '../../functions';
import { areSmileysDisabled, isSendGroupChatDisabled } from '../../functions';
import SmileysPanel from './SmileysPanel';
@@ -22,6 +22,8 @@ interface IProps extends WithTranslation {
*/
_areSmileysDisabled: boolean;
_isSendGroupChatDisabled: boolean;
/**
* The id of the message recipient, if any.
*/
@@ -145,7 +147,8 @@ class ChatInput extends Component<IProps, IState> {
value = { this.state.message } />
<Button
accessibilityLabel = { this.props.t('chat.sendButton') }
disabled = { !this.state.message.trim() }
disabled = { !this.state.message.trim()
|| (this.props._isSendGroupChatDisabled && !this.props._privateMessageRecipientId) }
icon = { IconSend }
onClick = { this._onSubmitMessage }
size = { isMobileBrowser() ? 'large' : 'medium' } />
@@ -170,10 +173,20 @@ class ChatInput extends Component<IProps, IState> {
* @returns {void}
*/
_onSubmitMessage() {
const {
_isSendGroupChatDisabled,
_privateMessageRecipientId,
onSend
} = this.props;
if (_isSendGroupChatDisabled && !_privateMessageRecipientId) {
return;
}
const trimmed = this.state.message.trim();
if (trimmed) {
this.props.onSend(trimmed);
onSend(trimmed);
this.setState({ message: '' });
@@ -273,10 +286,12 @@ class ChatInput extends Component<IProps, IState> {
*/
const mapStateToProps = (state: IReduxState) => {
const { privateMessageRecipient } = state['features/chat'];
const isGroupChatDisabled = isSendGroupChatDisabled(state);
return {
_areSmileysDisabled: areSmileysDisabled(state),
_privateMessageRecipientId: privateMessageRecipient?.id
_privateMessageRecipientId: privateMessageRecipient?.id,
_isSendGroupChatDisabled: isGroupChatDisabled
};
};

View File

@@ -23,7 +23,7 @@ interface IState {
/**
* The id of the last read message.
*/
lastReadMessageId: string;
lastReadMessageId: string | null;
}
/**

View File

@@ -7,6 +7,7 @@ import emojiAsciiAliases from 'react-emoji-render/data/asciiAliases';
import { IReduxState } from '../app/types';
import { getLocalizedDateFormatter } from '../base/i18n/dateUtil';
import i18next from '../base/i18n/i18next';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import { getParticipantById } from '../base/participants/functions';
import { escapeRegexp } from '../base/util/helpers';
@@ -190,3 +191,20 @@ export function getPrivateNoticeMessage(message: IMessage) {
recipient: message.messageType === MESSAGE_TYPE_LOCAL ? message.recipient : i18next.t('chat.you')
});
}
/**
* Check if participant is not allowed to send group messages.
*
* @param {IReduxState} state - The redux state.
* @returns {boolean} - Returns true if the participant is not allowed to send group messages.
*/
export function isSendGroupChatDisabled(state: IReduxState) {
const { groupChatRequiresPermission } = state['features/dynamic-branding'];
if (!groupChatRequiresPermission) {
return false;
}
return !isJwtFeatureEnabled(state, 'send-groupchat', false, false);
}

View File

@@ -42,7 +42,7 @@ import {
SEND_REACTION,
SET_IS_POLL_TAB_FOCUSED
} from './actionTypes';
import { addMessage, addMessageReaction, clearMessages, closeChat } from './actions.any';
import { addMessage, addMessageReaction, clearMessages, closeChat, setPrivateMessageRecipient } from './actions.any';
import { ChatPrivacyDialog } from './components';
import {
INCOMING_MSG_SOUND_ID,
@@ -52,7 +52,7 @@ import {
MESSAGE_TYPE_REMOTE,
MESSAGE_TYPE_SYSTEM
} from './constants';
import { getUnreadCount } from './functions';
import { getUnreadCount, isSendGroupChatDisabled } from './functions';
import { INCOMING_MSG_SOUND_FILE } from './sounds';
/**
@@ -161,13 +161,29 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case OPEN_CHAT:
case OPEN_CHAT: {
unreadCount = 0;
if (typeof APP !== 'undefined') {
APP.API.notifyChatUpdated(unreadCount, true);
}
const { privateMessageRecipient } = store.getState()['features/chat'];
if (
isSendGroupChatDisabled(store.getState())
&& privateMessageRecipient
&& !action.participant
) {
const participant = getParticipantById(store.getState(), privateMessageRecipient.id);
if (participant) {
action.participant = participant;
}
}
break;
}
case SET_IS_POLL_TAB_FOCUSED: {
dispatch(resetNbUnreadPollsMessages());
@@ -307,6 +323,12 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
isGuest,
messageId,
privateMessage: false });
if (isSendGroupChatDisabled(store.getState()) && participantId) {
const participant = getParticipantById(store, participantId);
store.dispatch(setPrivateMessageRecipient(participant));
}
}
);

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { Theme } from '@mui/material';
import { adaptV4Theme, createTheme } from '@mui/material/styles';
import { breakpoints, colorMap, colors, font, shape, spacing, typography } from '../base/ui/Tokens';
import { breakpoints, colorMap, font, shape, spacing, typography } from '../base/ui/Tokens';
import { createColorTokens } from '../base/ui/utils';
/**
@@ -19,7 +19,7 @@ export function createMuiBrandingTheme(customTheme: Theme) {
spacing: customSpacing
} = customTheme;
const newPalette = createColorTokens(colorMap, colors);
const newPalette = createColorTokens(colorMap);
if (customPalette) {
overwriteRecurrsive(newPalette, customPalette);

View File

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

View File

@@ -76,6 +76,15 @@ const DEFAULT_STATE = {
*/
didPageUrl: '',
/**
* Whether participant can only send group chat message if `send-groupchat`
* feature is enabled in jwt.
*
* @public
* @type {boolean}
*/
groupChatRequiresPermission: false,
/**
* The custom invite domain.
*
@@ -117,6 +126,14 @@ const DEFAULT_STATE = {
*/
muiBrandedTheme: undefined,
/**
* Whether participant can only create polls if `create-polls` feature is enabled in jwt.
*
* @public
* @type {boolean}
*/
pollCreationRequiresPermission: false,
/**
* The lobby/prejoin background.
*
@@ -152,12 +169,15 @@ export interface IDynamicBrandingState {
defaultBranding: boolean;
defaultTranscriptionLanguage?: boolean;
didPageUrl: string;
groupChatRequiresPermission: boolean;
inviteDomain: string;
labels: Object | null;
logoClickUrl: string;
logoImageUrl: string;
muiBrandedTheme?: boolean;
pollCreationRequiresPermission: boolean;
premeetingBackground: string;
requireRecordingConsent?: boolean;
sharedVideoAllowedURLDomains?: Array<string>;
showGiphyIntegration?: boolean;
supportUrl?: string;
@@ -178,14 +198,17 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
brandedIcons,
defaultBranding,
didPageUrl,
groupChatRequiresPermission,
inviteDomain,
labels,
logoClickUrl,
logoImageUrl,
muiBrandedTheme,
pollCreationRequiresPermission,
premeetingBackground,
sharedVideoAllowedURLDomains,
showGiphyIntegration,
requireRecordingConsent,
supportUrl,
virtualBackgrounds
} = action.value;
@@ -197,14 +220,17 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
brandedIcons,
defaultBranding,
didPageUrl,
groupChatRequiresPermission,
inviteDomain,
labels,
logoClickUrl,
logoImageUrl,
muiBrandedTheme,
pollCreationRequiresPermission,
premeetingBackground,
sharedVideoAllowedURLDomains,
showGiphyIntegration,
requireRecordingConsent,
supportUrl,
customizationFailed: false,
customizationReady: true,

View File

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

View File

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

View File

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

View File

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

View File

@@ -203,8 +203,6 @@ class InviteContactsForm extends AbstractAddPeopleDialog<IProps, IState> {
);
}
_isAddDisabled: () => boolean;
/**
* Callback invoked when a selection has been made but before it has been
* set as selected.

View File

@@ -1,5 +1,6 @@
import { IStore } from '../app/types';
import { openDialog } from '../base/dialog/actions';
import { ParticipantFeaturesKey } from '../base/participants/types';
import PremiumFeatureDialog from './components/web/PremiumFeatureDialog';
import { isFeatureDisabled } from './functions';
@@ -7,11 +8,11 @@ import { isFeatureDisabled } from './functions';
/**
* Shows a dialog prompting users to upgrade, if requested feature is disabled.
*
* @param {string} feature - The feature to check availability for.
* @param {ParticipantFeaturesKey} feature - The feature to check availability for.
*
* @returns {Function}
*/
export function maybeShowPremiumFeatureDialog(feature: string) {
export function maybeShowPremiumFeatureDialog(feature: ParticipantFeaturesKey) {
return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
if (isFeatureDisabled(getState(), feature)) {
dispatch(openDialog(PremiumFeatureDialog));

View File

@@ -1,5 +1,6 @@
import { IReduxState } from '../app/types';
import { IJitsiConference } from '../base/conference/reducer';
import { ParticipantFeaturesKey } from '../base/participants/types';
import { VPAAS_TENANT_PREFIX } from './constants';
import logger from './logger';
@@ -105,10 +106,10 @@ export async function sendGetDetailsRequest({ appId, baseUrl }: {
* Returns the billing id for vpaas meetings.
*
* @param {IReduxState} state - The state of the app.
* @param {string} feature - Feature to be looked up for disable state.
* @param {ParticipantFeaturesKey} feature - Feature to be looked up for disable state.
* @returns {boolean}
*/
export function isFeatureDisabled(state: IReduxState, feature: string) {
export function isFeatureDisabled(state: IReduxState, feature: ParticipantFeaturesKey) {
return state['features/jaas'].disabledFeatures.includes(feature);
}

View File

@@ -10,6 +10,7 @@ import Watermarks from '../../base/react/components/web/Watermarks';
import { getHideSelfView } from '../../base/settings/functions.any';
import { getVideoTrackByParticipant } from '../../base/tracks/functions.web';
import { setColorAlpha } from '../../base/util/helpers';
import { isSpotTV } from '../../base/util/spot';
import StageParticipantNameLabel from '../../display-name/components/web/StageParticipantNameLabel';
import { FILMSTRIP_BREAKPOINT } from '../../filmstrip/constants';
import { getVerticalViewMaxWidth, isFilmstripResizable } from '../../filmstrip/functions.web';
@@ -23,8 +24,6 @@ import { getLargeVideoParticipant } from '../functions';
import ScreenSharePlaceholder from './ScreenSharePlaceholder.web';
// Hack to detect Spot.
const SPOT_DISPLAY_NAME = 'Meeting Room';
interface IProps {
@@ -352,20 +351,20 @@ function _mapStateToProps(state: IReduxState) {
const { backgroundColor, backgroundImageUrl } = state['features/dynamic-branding'];
const { isOpen: isChatOpen } = state['features/chat'];
const { width: verticalFilmstripWidth, visible } = state['features/filmstrip'];
const { defaultLocalDisplayName, hideDominantSpeakerBadge } = state['features/base/config'];
const { hideDominantSpeakerBadge } = state['features/base/config'];
const { seeWhatIsBeingShared } = state['features/large-video'];
const localParticipantId = getLocalParticipant(state)?.id;
const largeVideoParticipant = getLargeVideoParticipant(state);
const videoTrack = getVideoTrackByParticipant(state, largeVideoParticipant);
const isLocalScreenshareOnLargeVideo = largeVideoParticipant?.id?.includes(localParticipantId ?? '')
&& videoTrack?.videoType === VIDEO_TYPE.DESKTOP;
const isOnSpot = defaultLocalDisplayName === SPOT_DISPLAY_NAME;
return {
_backgroundAlpha: state['features/base/config'].backgroundAlpha,
_customBackgroundColor: backgroundColor,
_customBackgroundImageUrl: backgroundImageUrl,
_displayScreenSharingPlaceholder: Boolean(isLocalScreenshareOnLargeVideo && !seeWhatIsBeingShared && !isOnSpot),
_displayScreenSharingPlaceholder:
Boolean(isLocalScreenshareOnLargeVideo && !seeWhatIsBeingShared && !isSpotTV()),
_hideSelfView: getHideSelfView(state),
_isChatOpen: isChatOpen,
_isScreenSharing: Boolean(isLocalScreenshareOnLargeVideo),

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,6 @@ import {
IconUsers,
IconWarning
} from '../../../base/icons/svg';
import { colors } from '../../../base/ui/Tokens';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
import Button from '../../../base/ui/components/native/Button';
import IconButton from '../../../base/ui/components/native/IconButton';
@@ -30,10 +29,10 @@ import styles from './styles';
*/
const ICON_COLOR = {
error: colors.error06,
normal: colors.primary06,
success: colors.success05,
warning: colors.warning05
error: BaseTheme.palette.iconError,
normal: BaseTheme.palette.iconNormal,
success: BaseTheme.palette.iconSuccess,
warning: BaseTheme.palette.iconWarning
};

View File

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

View File

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

View File

@@ -22,8 +22,7 @@ import Icon from '../../../base/icons/components/Icon';
import { IconCheck, IconRaiseHand, IconVideoOff } from '../../../base/icons/svg';
import { MEDIA_TYPE } from '../../../base/media/constants';
import { raiseHand } from '../../../base/participants/actions';
import { getParticipantCount, getRaiseHandsQueue, isEveryoneModerator, isLocalParticipantModerator }
from '../../../base/participants/functions';
import { getRaiseHandsQueue, isLocalParticipantModerator } from '../../../base/participants/functions';
import { LOWER_HAND_MESSAGE } from '../../../base/tracks/constants';
import MuteEveryonesVideoDialog
from '../../../video-menu/components/native/MuteEveryonesVideoDialog';
@@ -47,8 +46,6 @@ export const ContextMenuMore = () => {
const { t } = useTranslation();
const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
const allModerators = useSelector(isEveryoneModerator);
const participantCount = useSelector(getParticipantCount);
const isAudioModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.AUDIO));
const isVideoModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.VIDEO));
@@ -79,7 +76,7 @@ export const ContextMenuMore = () => {
src = { IconRaiseHand } />
<Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.lowerAllHands')}</Text>
</TouchableOpacity> }
{isModerationSupported && ((participantCount === 1 || !allModerators)) && <>
{isModerationSupported && <>
{/* @ts-ignore */}
<Divider style = { styles.divider } />
<View style = { styles.contextMenuItem as ViewStyle }>

View File

@@ -21,11 +21,7 @@ import {
IconVideoOff
} from '../../../base/icons/svg';
import { MEDIA_TYPE } from '../../../base/media/constants';
import {
getParticipantCount,
getRaiseHandsQueue,
isEveryoneModerator
} from '../../../base/participants/functions';
import { getRaiseHandsQueue } from '../../../base/participants/functions';
import { withPixelLineHeight } from '../../../base/styles/functions.web';
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup';
@@ -88,9 +84,7 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
const dispatch = useDispatch();
const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
const raisedHandsQueue = useSelector(getRaiseHandsQueue);
const allModerators = useSelector(isEveryoneModerator);
const isModeratorSettingsTabEnabled = useSelector(shouldShowModeratorSettings);
const participantCount = useSelector(getParticipantCount);
const isAudioModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.AUDIO));
const isVideoModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.VIDEO));
const isBreakoutRoom = useSelector(isInBreakoutRoom);
@@ -151,7 +145,7 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
text: t('participantsPane.actions.stopEveryonesVideo')
} ] } />
{raisedHandsQueue.length !== 0 && <LowerHandButton />}
{!isBreakoutRoom && isModerationSupported && (participantCount === 1 || !allModerators) && (
{!isBreakoutRoom && isModerationSupported && (
<ContextMenuItemGroup actions = { actions }>
<div className = { classes.text }>
<span>{t('participantsPane.actions.allow')}</span>

View File

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

View File

@@ -1,5 +1,8 @@
import React, { ComponentType, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { isCreatePollDisabled } from '../functions';
/*
* Props that will be passed by the AbstractPollsPane to its
@@ -7,6 +10,7 @@ import { useTranslation } from 'react-i18next';
**/
export type AbstractProps = {
createMode: boolean;
isCreatePollsDisabled: boolean;
onCreate: () => void;
setCreateMode: (mode: boolean) => void;
t: Function;
@@ -21,6 +25,7 @@ export type AbstractProps = {
*/
const AbstractPollsPane = (Component: ComponentType<AbstractProps>) => () => {
const isCreatePollsDisabled = useSelector(isCreatePollDisabled);
const [ createMode, setCreateMode ] = useState(false);
const onCreate = () => {
@@ -31,6 +36,7 @@ const AbstractPollsPane = (Component: ComponentType<AbstractProps>) => () => {
return (<Component
createMode = { createMode }
isCreatePollsDisabled = { isCreatePollsDisabled }
/* eslint-disable react/jsx-no-bind */
onCreate = { onCreate }
setCreateMode = { setCreateMode }

View File

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

View File

@@ -28,7 +28,7 @@ const useStyles = makeStyles()(() => {
};
});
const PollsPane = ({ createMode, onCreate, setCreateMode, t }: AbstractProps) => {
const PollsPane = ({ createMode, isCreatePollsDisabled, onCreate, setCreateMode, t }: AbstractProps) => {
const { classes } = useStyles();
return createMode
@@ -40,6 +40,7 @@ const PollsPane = ({ createMode, onCreate, setCreateMode, t }: AbstractProps) =>
<div className = { classes.footer }>
<Button
accessibilityLabel = { t('polls.create.create') }
disabled = { isCreatePollsDisabled }
fullWidth = { true }
labelKey = { 'polls.create.create' }
onClick = { onCreate } />

View File

@@ -1,4 +1,5 @@
import { IReduxState } from '../app/types';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import { IAnswerData } from './types';
@@ -62,3 +63,19 @@ export function hasIdenticalAnswers(currentAnswers: Array<IAnswerData>): boolean
return currentAnswersSet.size !== nonEmptyCurrentAnswers.length;
}
/**
* Check if participant is not allowed to create polls.
*
* @param {IReduxState} state - The redux state.
* @returns {boolean} - Returns true if the participant is not allowed to create polls.
*/
export function isCreatePollDisabled(state: IReduxState) {
const { pollCreationRequiresPermission } = state['features/dynamic-branding'];
if (!pollCreationRequiresPermission) {
return false;
}
return !isJwtFeatureEnabled(state, 'create-polls', false, false);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1 +1,2 @@
export { default as StartRecordingDialog } from './native/StartRecordingDialog';
export { default as RecordingConsentDialog } from './native/RecordingConsentDialog';

View File

@@ -1 +1,2 @@
export { default as StartRecordingDialog } from './web/StartRecordingDialog';
export { default as RecordingConsentDialog } from './web/RecordingConsentDialog';

View File

@@ -0,0 +1,30 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import ConfirmDialog from '../../../../base/dialog/components/native/ConfirmDialog';
import { setAudioUnmutePermissions, setVideoUnmutePermissions } from '../../../../base/media/actions';
/**
* Component that renders the dialog for explicit consent for recordings.
*
* @returns {JSX.Element}
*/
export default function RecordingConsentDialog() {
const dispatch = useDispatch();
const consent = useCallback(() => {
dispatch(setAudioUnmutePermissions(false, true));
dispatch(setVideoUnmutePermissions(false, true));
return true;
}, []);
return (
<ConfirmDialog
confirmLabel = { 'dialog.Understand' }
descriptionKey = { 'dialog.recordingInProgressDescription' }
isCancelHidden = { true }
onSubmit = { consent }
title = { 'dialog.recordingInProgressTitle' } />
);
}

View File

@@ -0,0 +1,36 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { setAudioUnmutePermissions, setVideoUnmutePermissions } from '../../../../base/media/actions';
import Dialog from '../../../../base/ui/components/web/Dialog';
/**
* Component that renders the dialog for explicit consent for recordings.
*
* @returns {JSX.Element}
*/
export default function RecordingConsentDialog() {
const { t } = useTranslation();
const dispatch = useDispatch();
const consent = useCallback(() => {
dispatch(setAudioUnmutePermissions(false, true));
dispatch(setVideoUnmutePermissions(false, true));
}, []);
return (
<Dialog
cancel = {{ hidden: true }}
disableBackdropClose = { true }
disableEscape = { true }
hideCloseButton = { true }
ok = {{ translationKey: 'dialog.Understand' }}
onSubmit = { consent }
titleKey = 'dialog.recordingInProgressTitle'>
<div>
{t('dialog.recordingInProgressDescription')}
</div>
</Dialog>
);
}

Some files were not shown because too many files have changed in this diff Show More