Compare commits

...

124 Commits

Author SHA1 Message Date
Calin-Teodor
c531c0e65c react-native-sdk(chore/deps): reset link deps to 0 2024-03-07 16:49:15 +02:00
Calinteodor
e1055ebf9b react-native-sdk(chore/overrides): update prepare_sdk script to take care of overrides (#14449)
* react-native-sdk(chore/overrides): update prepare_sdk script to take care of overrides
2024-03-07 16:20:34 +02:00
Calin-Teodor
467023f77a react-native-sdk(chore/deps): reset deps to 0 so we can update to latest 2024-03-07 15:56:09 +02:00
Calin-Teodor
1249aa2dcb react-native-sdk(android): readded react native package 2024-03-07 14:52:16 +02:00
Calinteodor
0c45d87d1a react-native-sdk(android): screen share updates (#14440)
* react-native-sdk(android): removed related modules, services to screen-share feature and updated peerDeps
2024-03-06 17:33:08 +02:00
damencho
7140a90201 fix(visitors): Fixes demoting correct participant.
Moderators receive all demote messages so they can show notification if we need to.
2024-03-05 08:22:32 -06:00
Jaya Allamsetty
0a846606fc chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1786.0.0+0129be6c...v1789.0.0+a8f8666b
2024-03-05 09:02:32 -05:00
Avram Tudor
68dc111e3c fix: decouple local recording from needing a valid jwt (#14434) 2024-03-05 12:41:44 +02:00
Saúl Ibarra Corretgé
c81184df69 fix(ios) sync SDK and Lite SDK building commands 2024-03-05 12:09:03 +02:00
damencho
9b0747a0d9 feat(visitors): Demote a visitor for mobile. 2024-03-04 13:18:04 -06:00
damencho
c8cd80a8df feat(visitors): Checks for visitors support per room. 2024-03-04 13:18:04 -06:00
damencho
f1d4332668 feat(visitors): Adds an option to demote participants to visitors. 2024-03-04 13:18:04 -06:00
damencho
55b3256dc4 fix: Changes jwt error dialog to be sticky. 2024-03-04 13:18:04 -06:00
damencho
aa8bb55f3e feat(visitors): Drops not-used messages. 2024-03-04 13:18:04 -06:00
damencho
58b73e21de fix(visitors): Fixes missing import. 2024-03-04 13:18:04 -06:00
damencho
b1c955890a feat(visitors): Admit all function. 2024-03-04 13:18:04 -06:00
damencho
6ab945c2cb fix(visitors): Fixes wrong text in notification on multiple promote requests. 2024-03-04 13:18:04 -06:00
damencho
7291e1ef00 feat(lobby): Approve multiple participants. 2024-03-04 08:13:27 -06:00
Дамян Минков
43e075d48e feat: Rate limits update (#14429)
* feat: Introduces new rate limit setting.

No can have two different values per ip that is used to limit session creation and one that is used when that stanza rate limit is exceeded.

* feat: Introduces unthrottle logic.

* fix: Bumps default iq rate limits.

* feat: Prints how many times a session hits the rates.

* Update resources/prosody-plugins/mod_rate_limit.lua

Co-authored-by: Aaron van Meerten <aaron.van.meerten@8x8.com>

---------

Co-authored-by: Aaron van Meerten <aaron.van.meerten@8x8.com>
2024-03-01 13:23:04 -06:00
Jaya Allamsetty
885e1afdaa chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1784.0.0+639ad566...v1786.0.0+0129be6c
2024-02-29 23:34:47 -05:00
Hristo Terezov
e2ec4842a1 fix(toolbarButtons): filter visitor buttons in redux.
Filters the toolbarButtons in redux depending on the visitor state instead of filtering them every time in mapStateToProps. This will prevent unnecessary rerenders of the toolbar.

Additionally:
 - Moves visitor buttons const from features/config in features/toolbox.
 - Removes dublicate functions isButtonEnabled and isToolbarButtonEnabled.
 - Adds more buttons to the visitor allowed buttons which functionality has been any way accessible trough shortcuts or somewhere else.
 - Enables customButtons to be visible for visitors.
2024-02-29 18:51:47 -06:00
Hristo Terezov
ea075d9bae fix(toolbarButtons): Store all buttons in redux.
The previous version of getToolbarButtons function was actually adding the custom buttons on every call to the config toolbarButtons array, effectively creating dublicates of every custom button. The PR fixes this issue.

Also now we will be running the getToolbarButtons calculation only when needed.
2024-02-29 16:36:52 -06:00
qnafin
68f7448624 Update build.gradle / jsRootDir = file("../")
When newArchEnabled=true. Refers to a non-existent category
2024-02-29 19:58:14 +01:00
damencho
954ef6df4f fix: Drops inspect print. 2024-02-29 12:52:19 -06:00
Calinteodor
6a3c12b316 feat(android): fix screen sharing for android 14 (#14419)
* feat(android): media projection is now done through react native webrtc
2024-02-29 16:34:24 +02:00
Calin-Teodor
5be616a224 chore(deps, rn-webrtc): updated to 118.0.2 2024-02-29 15:56:20 +02:00
Mihaela Dumitru
58d8f3be12 chore(deps) update excalidraw version (#14420) 2024-02-29 15:30:44 +02:00
Saúl Ibarra Corretgé
8ae6ba76cc feat(rn,deps) update react-native-webrtc@118.0.1
Adds UVC camera support to Android.
2024-02-29 12:02:51 +01:00
bgrozev
a06c2e056c feat: Send room_metadata updates to jicofo. (#14417)
Include the room jid in the message.
2024-02-28 15:41:55 -08:00
Saúl Ibarra Corretgé
fa0b06b859 fix(live-streaming) fix handling bogus URLs 2024-02-28 13:58:13 +01:00
Saúl Ibarra Corretgé
4c3512b375 fix(etherpad) fix handling certain URL types 2024-02-28 13:58:13 +01:00
Jaya Allamsetty
92e3a1ff99 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1783.0.0+ff4dd993...v1784.0.0+639ad566
2024-02-27 13:32:43 -05:00
Saúl Ibarra Corretgé
701e3d2b43 fix(recording) restore labels when recording locally 2024-02-27 09:17:24 -06:00
Saúl Ibarra Corretgé
2626a894e2 fix(recording) remove duplicated check 2024-02-27 09:17:24 -06:00
Saúl Ibarra Corretgé
a6f6b3a2d2 fix(subtitles) keep subtitles state when recording 2024-02-27 09:17:24 -06:00
Hristo Terezov
05aa48774a feat(recorder-transcription): Handle correctly in the UI.
Until this commit we didn't make difference between transcriptions from the recording dialog and subtitles. Now subtitles are not considered recording anymore and only the transcriptions started from recording dialog are considered recording.
2024-02-27 09:17:24 -06:00
Calinteodor
e82fc480f0 feat(tracks): disable pip in screen-share based on flag value (#14413)
* feat(tracks): disable pip in screen-share based on flag value
2024-02-27 14:59:55 +02:00
Horatiu Muresan
9f8662c0a1 chore(eslint) Update eslint (#14412) 2024-02-27 12:56:05 +02:00
Jaya Allamsetty
5b860c20b1 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1782.0.0+3ff4b0d1...v1783.0.0+ff4dd993
2024-02-26 10:25:05 -05:00
Saúl Ibarra Corretgé
a4c52b02e7 fix(recording) set transcribing metadata when stopping 2024-02-26 13:30:23 +01:00
Joseph Garrone
73952ee1f7 refactor(styles): Migrate withStyles to tss-react, remove @mui/styles dependency (#14310) 2024-02-26 14:10:07 +02:00
Hristo Terezov
0a9f6c2e4d fix(overflow-menu): disabled buttons styles 2024-02-23 17:53:29 -06:00
Jaya Allamsetty
94f9ccfe1c chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1781.0.0+98bcd4e6...v1782.0.0+3ff4b0d1
2024-02-23 17:08:34 -05:00
Hristo Terezov
ff9f767cfd fix(recording/livestreaming): labels
Display only livestreaming label when the livestreaming and the transcribtions are on.
2024-02-23 15:55:23 -06:00
JJ Asghar
5a5ec9e4d9 fix(Typo in comment) (#14077) 2024-02-23 22:16:21 +01:00
Calin-Teodor
aff671b44a feat(config/flag): enable/disable participants pane 2024-02-23 14:54:37 +02:00
Hristo Terezov
495b925fa0 fix(Livestreaming): label 2024-02-22 18:13:21 -06:00
Hristo Terezov
2f6b11a6ac fix(visitors): gum pending on promotion. 2024-02-22 14:57:51 -06:00
Jaya Allamsetty
2b7052995b feat: Enable ssrc-rewriting by default. (#14395)
* feat: Enable ssrc-rewriting by default.

* chore(deps): Update lib-jitsi-meet.
2024-02-22 15:16:50 -05:00
Calin-Teodor
5c69baefbf feat(dynamic-branding): fix label customisation 2024-02-21 14:51:37 +02:00
dependabot[bot]
8b25265f9e chore(deps): bump ip from 1.1.5 to 1.1.9
Bumps [ip](https://github.com/indutny/node-ip) from 1.1.5 to 1.1.9.
- [Commits](https://github.com/indutny/node-ip/compare/v1.1.5...v1.1.9)

---
updated-dependencies:
- dependency-name: ip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-21 09:21:37 +01:00
Hristo Terezov
f43020f650 fix(gumPending): Handle ignored muted tracks. 2024-02-20 16:54:05 -06:00
Hristo Terezov
ba95ea3ac8 feat(conference): Add init debug log 2024-02-20 09:42:07 -06:00
Besnik Bleta
7dcd9c21bc fix(lang) update for sq translation (#14387) 2024-02-20 15:23:14 +01:00
damencho
990d21038e fix: Fixes initializing cached keys. 2024-02-16 11:36:07 -06:00
Mihaela Dumitru
a4d53f271f fix(ui) center conference info (#14379) 2024-02-16 14:37:12 +02:00
bgrozev
2a1f472873 Add a second shared secret to allow rotation if necessary. (#14378) 2024-02-15 15:00:30 -08:00
damencho
8679119677 fix: Change whitelist to support only domains. 2024-02-15 16:36:37 -06:00
damencho
b02c072ba7 fix: Handles few more cases for updating cached tokens. 2024-02-15 12:20:02 -06:00
Saúl Ibarra Corretgé
e8317fccfe fix(local-recordings) fix on Chrome
WebM + VP8 seems to be the one thing that works, requesting H.264 starts
the recorder in Matroska + AV1 format (WAT) but no data is received at
all.
2024-02-15 17:49:45 +01:00
Saúl Ibarra Corretgé
bbc9c64978 fix(recording) only show advanced section for cloud recordings 2024-02-15 17:49:45 +01:00
Saúl Ibarra Corretgé
8d2b8bc772 fix(recording) fix checking if we can skip the recording dialog 2024-02-15 15:15:38 +01:00
damencho
52c2911350 feat: Adds stats around room token validation. 2024-02-15 07:13:12 -06:00
damencho
a1ebcd559b feat: Adds a print for timing out http requests after the retry. 2024-02-15 07:13:12 -06:00
damencho
5bc47ec16a feat: Adds stat when a rayo IQ is filtered. 2024-02-15 07:13:12 -06:00
damencho
e1ac7d1609 feat: Adds stats around token validation. 2024-02-15 07:13:12 -06:00
damencho
8a596f1ba2 fix: Drops unused token validation. 2024-02-15 07:13:12 -06:00
damencho
38e7c65836 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1776.0.0+f5f8a137...v1777.0.0+3898d7aa
2024-02-14 22:52:49 -05:00
Jaya Allamsetty
9602a939d8 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1775.0.0+c7495280...v1776.0.0+f5f8a137
2024-02-14 12:32:39 -05:00
Calinteodor
07b01b1371 feat(conference/recordin): Fix always on labels (#14368)
* feat(conference/recording): condition live stream label visibility and fix labels tooltip translations
2024-02-14 13:11:57 +02:00
damencho
a1549086aa chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1773.0.0+cc9b0e60...v1775.0.0+c7495280
2024-02-13 14:02:28 -06:00
Saúl Ibarra Corretgé
7e0b00ba5f feat(prejoin) default to enabled (#14236)
* feat(prejoin) default to enabled

* squash: Fixes is prejoin config check.

---------

Co-authored-by: damencho <damencho@jitsi.org>
2024-02-13 12:47:59 -06:00
Calin-Teodor
3016853d81 feat(android): replaced NotificationChannels with NotificationUtils 2024-02-13 20:03:36 +02:00
Calinteodor
ead27ace30 feat(android): update media projection notification build (#14365)
* feat(android): update media projection notification build
2024-02-13 19:25:46 +02:00
Дамян Минков
067bb653e6 feat(лоббъ): Admin set function for multiple items.
* squash: Initial copy of the function.

* squash: Invokes the admin set function.

* feat: Admin set function for multiple items.
2024-02-13 10:29:41 -06:00
Calinteodor
ba20fc71a8 feat(android): separate MediaProjection and OngoingConference notifications (#14363)
* feat(android): separate MediaProjection and OngoingConference notifications
2024-02-13 17:54:01 +02:00
Mihaela Dumitru
a7b2726ebe fix(prejoin) adjust recording warning (#14361) 2024-02-13 13:12:10 +02:00
Calinteodor
a98eef7eb3 feat(android): fixed screen sharing for Android 33 (#14359)
* feat(android): fixed screen sharing for Android 33
2024-02-13 12:33:37 +02:00
damencho
895afbab65 fix: Fixes polls parsing. 2024-02-12 13:21:06 -06:00
Saúl Ibarra Corretgé
1d6529af65 feat(transcript) add ability to dump transcript for debugging 2024-02-12 13:07:19 +01:00
Saúl Ibarra Corretgé
4d5fb719d2 fix(whiteboard) handle metadata being null 2024-02-12 13:05:53 +01:00
Jaya Allamsetty
4061a77af8 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1770.0.0+2d430a18...v1773.0.0+cc9b0e60
2024-02-09 11:20:51 -05:00
Mihaela Dumitru
7f889b2028 feat(prejoin) display recording warning (#14347) 2024-02-09 11:00:28 +02:00
bgrozev
6f49041d80 feat: Notify jicofo when room_metadata changes the config form. (#14346) 2024-02-08 14:58:02 -08:00
Avram Tudor
e73c3b6697 feat(recordings) send recordings metadata
* ref: centralise conference metadata updates

* feat(recordings) send recordings metadata
2024-02-08 21:38:06 +02:00
Avram Tudor
5a6b1d0b47 code review 2024-02-08 17:41:55 +02:00
Avram Tudor
da9cded75b fix(recording) ensure recording label is shown when transcription is on 2024-02-08 17:41:55 +02:00
Avram Tudor
9b61ad3616 fix(recording) do not show advanced options for users with local recording rights 2024-02-08 17:41:55 +02:00
Avram Tudor
583725bf31 fix(recording) do not show stop recording option in some wrong cases 2024-02-08 17:41:55 +02:00
Avram Tudor
9e2244210d ref: add selector to check if meeting is transcribed 2024-02-08 17:41:55 +02:00
Avram Tudor
361b82a1ed ref: add selector for checking cloud recording running status 2024-02-08 17:41:55 +02:00
Saúl Ibarra Corretgé
0ed25cda7e fix(ci) use the right CocoaPods version 2024-02-08 15:08:15 +01:00
Calinteodor
73ee1205eb feat(premeeting/prejoin): Control room name visibility (#14339)
feat(premeeting/prejoin): Control room name visibility (#14339)
2024-02-08 15:56:05 +02:00
damencho
21f2c60638 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1769.0.0+ff9f57c5...v1770.0.0+2d430a18
2024-02-07 14:53:46 -06:00
damencho
fd062c40fb fix: Use same format for anonymous jids as token module. 2024-02-07 14:53:36 -06:00
damencho
b87e6abc11 feat: Sends event on not allowed error.
Updates amplitude to include the update about sending events on exiting the page.
2024-02-07 13:08:35 -06:00
Avram Tudor
9f25726706 fix(recording) fix recording suggestion not being shown in some cases (#14333)
Initial implementation did not account for cases where participants become moderators
2024-02-07 15:36:43 +02:00
Calinteodor
d5ee7f3069 feat(mobile/polyfills): strophe.js for react native (#14329)
* chore(deps, abab): polyfill using dependency for strophe in react native
2024-02-06 18:28:35 +02:00
damencho
ba1102100a feat(visitors): Adds conference duration for visitors. 2024-02-06 10:16:30 -06:00
damencho
e1ce83d0c3 feat: Use room data form for conference duration.
Deprecates current component for conference duration. Will wait a bit for mobile to catch up and then we can delete the code.

chore(deps) lib-jitsi-meet@latest
jitsi/lib-jitsi-meet@v1767.0.0+178f9bbf...v1769.0.0+ff9f57c5
2024-02-06 10:16:30 -06:00
damencho
78cf510c0b feat: Moves common process_host_module to util. 2024-02-06 10:16:30 -06:00
Mihaela Dumitru
3f657c3ded feat(whiteboard) add native implementation (#14327) 2024-02-06 16:22:53 +02:00
Saúl Ibarra Corretgé
2035cd7e62 fix(recording) avoid having duplicate notification sounds 2024-02-06 11:14:12 +01:00
Saúl Ibarra Corretgé
6207e95cad fix(recording) fix default value to auto-transcribe-on-record 2024-02-06 11:14:12 +01:00
Calinteodor
40b63a187a feat(conference): remove one2one check for diplaying participants name (#14324)
* feat(conference): remove one2one check for diplaying participants name
2024-02-06 10:54:27 +02:00
Saúl Ibarra Corretgé
6c40250e18 fix(conference) avoid adding extra endpoint message listeners 2024-02-06 07:40:40 +01:00
Saúl Ibarra Corretgé
0268374b88 fix(conference) move endpointMessageReceived action
It's used for a myrias of things, not just subtitles.
2024-02-06 07:40:40 +01:00
Jaya Allamsetty
b1b60ec143 fix(config): Fix the typo in Av1 support flag. 2024-02-05 13:27:29 -05:00
Дамян Минков
82c58178b3 feat(visitors): Adds notification for promotion requests. (#14316)
* feat(visitors): Adds notification for promotion requests.

* squash: fixes lint.
2024-02-02 14:58:24 -06:00
Jaya Allamsetty
46d15a9c5c chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1764.0.0+ba21a236...v1767.0.0+178f9bbf
2024-02-02 14:58:09 -06:00
Hristo Terezov
248908f476 fix(lobby): Hide enable option if there is a visitor 2024-02-02 14:20:16 -06:00
Hristo Terezov
4a5ba0f05d fix(visitors): Disable polls. 2024-02-02 12:50:01 -06:00
damencho
b23f3b9e17 feat(visitors): Whitelist trusted domains when promoting. 2024-02-02 12:01:59 -06:00
Дамян Минков
b6b77f55f1 feat: Drops some debug prints. (#14311)
* feat: Drops some debug prints.

* squash: Fix linter errors.
2024-02-02 11:11:50 -06:00
damencho
b106c20fa2 feat: Adds indication for visitors messages to event. 2024-02-01 21:06:25 -06:00
Hristo Terezov
fab61d8c32 fix(reactions): Show when received even if UI is disabled 2024-02-01 18:08:19 -06:00
Hristo Terezov
9ac614cc4b fix(shortcut): Unregister for altKey shortcuts. 2024-02-01 18:08:19 -06:00
Hristo Terezov
a3bb1a3459 feat(visitors): Hide reactions. 2024-02-01 18:08:19 -06:00
Avram Tudor
34abd279be feat(recording) add notification to suggest recording at meeting startup (#14296)
* feat(recording) add notification to suggest recording at meeting startup

* code review changes

* update strings

* fix mobile

* fix lint
2024-02-01 17:08:41 +02:00
Saúl Ibarra Corretgé
ad6554a789 fix(transcriptions) obey skipInterimTranscriptions for chunk events 2024-02-01 10:43:10 +01:00
Saúl Ibarra Corretgé
2be8377009 fix(transcriptions) avoid generating duplicated events 2024-02-01 10:43:10 +01:00
damencho
0a0b0a760e fix: Fixes promoting the only moderator in a non vpass meeting. 2024-01-31 17:53:16 -06:00
Дамян Минков
fec6de4536 fix(visitors): Lowers visitor's hand on leave. (#14302)
* fix(visitors): Lowers visitor's hand on leave.

* squash: Drop not needed nick.

That is used only when raising hand.
2024-01-31 11:49:16 -06:00
damencho
29b2a519b3 fix(visitors): Handles promotion rejection msg.
If not handled an error is returned that the stanza is not handled (service-unavailable error) and displayed in UI.
2024-01-31 07:37:43 -06:00
270 changed files with 4828 additions and 9401 deletions

View File

@@ -73,6 +73,10 @@ jobs:
node-version: 16
cache: 'npm'
- run: npm install
- name: setup-cocoapods
uses: maxim-lobanov/setup-cocoapods@v1
with:
podfile-path: ios/Podfile.lock
- name: Install Pods
run: |
pod --version

3
.gitignore vendored
View File

@@ -99,10 +99,7 @@ tsconfig.json
#
react-native-sdk/*.tgz
react-native-sdk/android/src
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetOngoingConferenceService.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetReactNativePackage.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JMOngoingConferenceModule.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/RNOngoingNotification.java
react-native-sdk/images
react-native-sdk/ios
react-native-sdk/lang

View File

@@ -12,7 +12,6 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-feature
@@ -51,10 +50,6 @@
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
android:foregroundServiceType="mediaPlayback" />
<service
android:name="org.jitsi.meet.sdk.JitsiMeetMediaProjectionService"
android:foregroundServiceType="mediaProjection" />
<provider
android:name="com.reactnativecommunity.webview.RNCWebViewFileProvider"
android:authorities="${applicationId}.fileprovider"

View File

@@ -1,42 +0,0 @@
package org.jitsi.meet.sdk;
import android.content.Context;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = JitsiMeetMediaProjectionModule.NAME)
class JitsiMeetMediaProjectionModule
extends ReactContextBaseJavaModule {
public static final String NAME = "JitsiMeetMediaProjectionModule";
public JitsiMeetMediaProjectionModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public void launch() {
Context context = getReactApplicationContext();
JitsiMeetMediaProjectionService.launch(context);
}
@ReactMethod
public void abort() {
Context context = getReactApplicationContext();
JitsiMeetMediaProjectionService.abort(context);
}
@NonNull
@Override
public String getName() {
return NAME;
}
}

View File

@@ -1,100 +0,0 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.IBinder;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
* responsible for presenting an ongoing notification when a conference is in progress.
* The service will help keep the app running while in the background.
*
* See: https://developer.android.com/guide/components/services
*/
public class JitsiMeetMediaProjectionService extends Service {
private static final String TAG = JitsiMeetMediaProjectionService.class.getSimpleName();
public static void launch(Context context) {
OngoingNotification.createOngoingConferenceNotificationChannel();
Intent intent = new Intent(context, JitsiMeetMediaProjectionService.class);
ComponentName componentName;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
componentName = context.startForegroundService(intent);
} else {
componentName = context.startService(intent);
}
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
return;
}
if (componentName == null) {
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetMediaProjectionService.class);
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
Notification notification = OngoingNotification.buildOngoingConferenceNotification(null);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(OngoingNotification.NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -16,6 +16,7 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
@@ -33,6 +34,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.HashMap;
import java.util.Random;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
@@ -52,8 +54,12 @@ public class JitsiMeetOngoingConferenceService extends Service
private boolean isAudioMuted;
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
public static void launch(Context context, HashMap<String, Object> extraData) {
OngoingNotification.createOngoingConferenceNotificationChannel();
OngoingNotification.createNotificationChannel((Activity) context);
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
@@ -90,15 +96,15 @@ public class JitsiMeetOngoingConferenceService extends Service
public void onCreate() {
super.onCreate();
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(OngoingNotification.NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
startForeground(NOTIFICATION_ID, notification);
}
}
@@ -130,13 +136,13 @@ public class JitsiMeetOngoingConferenceService extends Service
if (isAudioMuted != null) {
this.isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
notificationManager.notify(NOTIFICATION_ID, notification);
}
}
@@ -216,13 +222,13 @@ public class JitsiMeetOngoingConferenceService extends Service
@Override
public void onReceive(Context context, Intent intent) {
isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, context);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't update service, notification is null");
} else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
notificationManager.notify(NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " audio muted changed");
}

View File

@@ -1,10 +0,0 @@
package org.jitsi.meet.sdk;
import java.util.ArrayList;
import java.util.List;
public class NotificationChannels {
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
public static List<String> allIds = new ArrayList<String>() {{ add(ONGOING_CONFERENCE_CHANNEL_ID); }};
}

View File

@@ -16,22 +16,21 @@
package org.jitsi.meet.sdk;
import static org.jitsi.meet.sdk.NotificationChannels.ONGOING_CONFERENCE_CHANNEL_ID;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import android.app.Notification;
import android.app.Activity;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import android.os.Build;
import java.util.Random;
/**
* Helper class for creating the ongoing notification which is used with
@@ -41,15 +40,15 @@ import java.util.Random;
class OngoingNotification {
private static final String TAG = OngoingNotification.class.getSimpleName();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
private static long startingTime = 0;
static void createOngoingConferenceNotificationChannel() {
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
static void createNotificationChannel(Activity context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
Context context = ReactInstanceManagerHolder.getCurrentActivity();
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;
@@ -60,12 +59,13 @@ class OngoingNotification {
NotificationChannel channel
= notificationManager.getNotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID);
if (channel != null) {
// The channel was already created, no need to do it again.
return;
}
channel = new NotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID, context.getString(R.string.ongoing_notification_action_unmute), NotificationManager.IMPORTANCE_DEFAULT);
channel = new NotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID, context.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(false);
channel.enableVibration(false);
channel.setShowBadge(false);
@@ -73,8 +73,8 @@ class OngoingNotification {
notificationManager.createNotificationChannel(channel);
}
static Notification buildOngoingConferenceNotification(Boolean isMuted) {
Context context = ReactInstanceManagerHolder.getCurrentActivity();
static Notification buildOngoingConferenceNotification(Boolean isMuted, Context context) {
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
@@ -92,7 +92,7 @@ class OngoingNotification {
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(isMuted != null ? context.getString(R.string.ongoing_notification_text) : context.getString(R.string.ongoing_notification_action_screenshare))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setOngoing(true)
@@ -103,10 +103,6 @@ class OngoingNotification {
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
if (isMuted == null) {
return builder.build();
}
NotificationCompat.Action hangupAction = createAction(context, JitsiMeetOngoingConferenceService.Action.HANGUP, R.string.ongoing_notification_action_hang_up);
JitsiMeetOngoingConferenceService.Action toggleAudioAction = isMuted

View File

@@ -37,6 +37,7 @@ import com.oney.WebRTCModule.webrtcutils.H264AndSoftwareVideoEncoderFactory;
import org.devio.rn.splashscreen.SplashScreenModule;
import org.webrtc.EglBase;
import org.webrtc.Logging;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
@@ -67,7 +68,6 @@ class ReactInstanceManagerHolder {
new DropboxModule(reactContext),
new ExternalAPIModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new JitsiMeetMediaProjectionModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),
new SplashScreenModule(reactContext),
@@ -241,6 +241,8 @@ class ReactInstanceManagerHolder {
options.videoDecoderFactory = new H264AndSoftwareVideoDecoderFactory(eglContext);
options.videoEncoderFactory = new H264AndSoftwareVideoEncoderFactory(eglContext);
options.enableMediaProjectionService = true;
// options.loggingSeverity = Logging.Severity.LS_INFO;
Log.d(TAG, "initializing RN with Activity");

View File

@@ -1,11 +1,12 @@
<resources>
<string name="app_name">Jitsi Meet SDK</string>
<string name="dropbox_app_key"></string>
<string name="media_projection_notification_title">Media projection</string>
<string name="media_projection_notification_text">You are currently sharing your screen.</string>
<string name="ongoing_notification_title">Ongoing meeting</string>
<string name="ongoing_notification_text">You are currently in a meeting. Tap to return to it.</string>
<string name="ongoing_notification_action_hang_up">Hang up</string>
<string name="ongoing_notification_action_mute">Mute</string>
<string name="ongoing_notification_action_screenshare">You are currently screen-sharing. Tap to return to the meeting.</string>
<string name="ongoing_notification_action_unmute">Unmute</string>
<string name="ongoing_notification_channel_name">Ongoing Conference Notifications</string>
</resources>

View File

@@ -38,6 +38,7 @@ import {
dataChannelClosed,
dataChannelOpened,
e2eRttChanged,
endpointMessageReceived,
kickedOut,
lockStateChanged,
nonParticipantMessageReceived,
@@ -90,7 +91,7 @@ import {
setVideoMuted,
setVideoUnmutePermissions
} from './react/features/base/media/actions';
import { MEDIA_TYPE } from './react/features/base/media/constants';
import { MEDIA_TYPE, VIDEO_TYPE } from './react/features/base/media/constants';
import {
getStartWithAudioMuted,
getStartWithVideoMuted,
@@ -162,10 +163,8 @@ import { isScreenAudioShared } from './react/features/screen-share/functions';
import { toggleScreenshotCaptureSummary } from './react/features/screenshot-capture/actions';
import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect';
import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
import { endpointMessageReceived } from './react/features/subtitles/actions.any';
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
import { muteLocal } from './react/features/video-menu/actions.any';
import { iAmVisitor } from './react/features/visitors/functions';
import UIEvents from './service/UI/UIEvents';
const logger = Logger.getLogger(__filename);
@@ -405,7 +404,13 @@ function disconnect() {
* @returns {void}
*/
function setGUMPendingStateOnFailedTracks(tracks) {
const tracksTypes = tracks.map(track => track.getType());
const tracksTypes = tracks.map(track => {
if (track.getVideoType() === VIDEO_TYPE.DESKTOP) {
return MEDIA_TYPE.SCREENSHARE;
}
return track.getType();
});
const nonPendingTracks = [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ].filter(type => !tracksTypes.includes(type));
APP.store.dispatch(gumPending(nonPendingTracks, IGUMPendingState.NONE));
@@ -679,6 +684,8 @@ export default {
startWithVideoMuted: getStartWithVideoMuted(state) || isUserInteractionRequiredForUnmute(state)
};
logger.debug(`Executed conference.init with roomName: ${roomName}`);
this.roomName = roomName;
try {
@@ -697,10 +704,6 @@ export default {
const handleInitialTracks = (options, tracks) => {
let localTracks = tracks;
// No local tracks are added when user joins as a visitor.
if (iAmVisitor(state)) {
return [];
}
if (options.startWithAudioMuted || room?.isStartAudioMuted()) {
// Always add the track on Safari because of a known issue where audio playout doesn't happen
// if the user joins audio and video muted, i.e., if there is no local media capture.
@@ -1253,7 +1256,24 @@ export default {
room = APP.connection.initJitsiConference(APP.conference.roomName, this._getConferenceOptions());
// Filter out the tracks that are muted (except on Safari).
const tracks = browser.isWebKitBased() ? localTracks : localTracks.filter(track => !track.isMuted());
let tracks = localTracks;
if (!browser.isWebKitBased()) {
const mutedTrackTypes = [];
tracks = localTracks.filter(track => {
if (!track.isMuted()) {
return true;
}
if (track.getVideoType() !== VIDEO_TYPE.DESKTOP) {
mutedTrackTypes.push(track.getType());
}
return false;
});
APP.store.dispatch(gumPending(mutedTrackTypes, IGUMPendingState.NONE));
}
this._setLocalAudioVideoStreams(tracks);
this._room = room; // FIXME do not use this
@@ -1824,28 +1844,24 @@ export default {
room.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(...args) => {
APP.store.dispatch(endpointMessageReceived(...args));
if (args && args.length >= 2) {
const [ sender, eventData ] = args;
if (eventData.name === ENDPOINT_TEXT_MESSAGE_NAME) {
APP.API.notifyEndpointTextMessageReceived({
senderInfo: {
jid: sender._jid,
id: sender._id
},
eventData
});
}
(participant, data) => {
APP.store.dispatch(endpointMessageReceived(participant, data));
if (data?.name === ENDPOINT_TEXT_MESSAGE_NAME) {
APP.API.notifyEndpointTextMessageReceived({
senderInfo: {
jid: participant.getJid(),
id: participant.getId()
},
eventData: data
});
}
});
room.on(
JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
(...args) => {
APP.store.dispatch(nonParticipantMessageReceived(...args));
APP.API.notifyNonParticipantMessageReceived(...args);
(id, data) => {
APP.store.dispatch(nonParticipantMessageReceived(id, data));
APP.API.notifyNonParticipantMessageReceived(id, data);
});
room.on(

View File

@@ -85,7 +85,7 @@ var config = {
// disableE2EE: false,
// Enables supports for AV1 codec.
// enableAv1: false,
// enableAv1Support: false,
// Enables XMPP WebSocket (as opposed to BOSH) for the given amount of users.
// mobileXmppWsThreshold: 10, // enable XMPP WebSockets on mobile for 10% of the users
@@ -103,6 +103,9 @@ var config = {
// Experiment: Whether to skip interim transcriptions.
// skipInterimTranscriptions: false,
// Dump transcripts to a <transcript> element for debugging.
// dumpTranscript: false,
},
// Disables moderator indicators.
@@ -324,6 +327,16 @@ var config = {
// 'https://jitsi-meet.example.com/subfolder/static/oauth.html',
// },
// configuration for all things recording related. Existing settings will be migrated here in the future.
// recordings: {
// // If true, shows a notification at the start of the meeting with a call to action button
// // to start recording (for users who can do so).
// // suggestRecording: true,
// // If true, shows a warning label in the prejoin screen to point out the possibility that
// // the call you're joining might be recorded.
// // showPrejoinWarning: true,
// },
// recordingService: {
// // When integrations like dropbox are enabled only that will be shown,
// // by enabling fileRecordingsServiceEnabled, we show both the integrations
@@ -637,7 +650,7 @@ var config = {
// // Whether to disable welcome page. In case it's disabled a random room
// // will be joined when no room is specified.
// disabled: false,
// // If set,landing page will redirect to this URL.
// // If set, landing page will redirect to this URL.
// customUrl: ''
// },
@@ -710,7 +723,7 @@ var config = {
// Configs for prejoin page.
// prejoinConfig: {
// // When 'true', it shows an intermediate page before joining, where the user can configure their devices.
// // This replaces `prejoinPageEnabled`.
// // This replaces `prejoinPageEnabled`. Defaults to true.
// enabled: true,
// // Hides the participant name editing field in the prejoin screen.
// // If requireDisplayName is also set as true, a name should still be provided through
@@ -1287,6 +1300,8 @@ var config = {
// remoteVideoMenu: {
// // Whether the remote video context menu to be rendered or not.
// disabled: true,
// // If set to true the 'Switch to visitor' button will be disabled.
// disableDemote: true,
// // If set to true the 'Kick out' button will be disabled.
// disableKick: true,
// // If set to true the 'Grant moderator' button will be disabled.
@@ -1375,6 +1390,8 @@ var config = {
// Options related to the participants pane.
// participantsPane: {
// // Enables feature
// enabled: true,
// // Hides the moderator settings tab.
// hideModeratorSettingsTab: false,
// // Hides the more actions button.

View File

@@ -104,7 +104,7 @@
}
}
.reactions-animations-container {
.reactions-animations-overflow-container {
position: absolute;
width: 20%;
bottom: 0;
@@ -117,6 +117,13 @@
position: relative;
}
.reactions-animations-container {
left: 50%;
bottom: 0px;
display: inline-block;
position: absolute;
}
$reactionCount: 20;
@function random($min, $max) {

View File

@@ -41,7 +41,6 @@
position: absolute;
top: 0;
height: 48px;
max-width: calc(100% - 24px);
}
@keyframes hideSubject {

View File

@@ -36,6 +36,7 @@ $flagsImagePath: "../images/";
@import 'modals/invite/info';
@import 'modals/screen-share/share-audio';
@import 'modals/screen-share/share-screen-warning';
@import 'modals/whiteboard';
@import 'videolayout_default';
@import 'subject';
@import 'popup_menu';

View File

@@ -0,0 +1,7 @@
.whiteboard {
.excalidraw-wrapper {
height: 100vh;
width: 100vw;
}
}

View File

@@ -51,7 +51,6 @@ VirtualHost "jitmeet.example.com"
}
av_moderation_component = "avmoderation.jitmeet.example.com"
speakerstats_component = "speakerstats.jitmeet.example.com"
conference_duration_component = "conferenceduration.jitmeet.example.com"
end_conference_component = "endconference.jitmeet.example.com"
-- we need bosh
modules_enabled = {
@@ -130,9 +129,6 @@ Component "focus.jitmeet.example.com" "client_proxy"
Component "speakerstats.jitmeet.example.com" "speakerstats_component"
muc_component = "conference.jitmeet.example.com"
Component "conferenceduration.jitmeet.example.com" "conference_duration_component"
muc_component = "conference.jitmeet.example.com"
Component "endconference.jitmeet.example.com" "end_conference"
muc_component = "conference.jitmeet.example.com"

1
globals.d.ts vendored
View File

@@ -21,6 +21,7 @@ declare global {
JitsiMeetElectron?: any;
PressureObserver?: any;
PressureRecord?: any;
ReactNativeWebView?: any;
// selenium tests handler
_sharedVideoPlayer: any;
alwaysOnTop: { api: any };

3
globals.native.d.ts vendored
View File

@@ -19,6 +19,9 @@ interface IWindow {
location: ILocation;
PressureObserver?: any;
PressureRecord?: any;
ReactNativeWebView?: any;
TextDecoder?: any;
TextEncoder?: any;
self: any;
top: any;

View File

@@ -453,7 +453,7 @@ PODS:
- react-native-video/Video (6.0.0-alpha.11):
- PromisesSwift
- React-Core
- react-native-webrtc (118.0.0):
- react-native-webrtc (118.0.2):
- JitsiWebRTC (~> 118.0.0)
- React-Core
- react-native-webview (13.5.1):
@@ -881,7 +881,7 @@ SPEC CHECKSUMS:
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
react-native-video: 472b7c366eaaaa0207e546d9a50410df89790bcf
react-native-webrtc: c8d9ad3c152105b2720ca2851d04b49659551992
react-native-webrtc: a0a8a1730b6cc5a5bda8a6e2166a74c9b78029e2
react-native-webview: 8baa0f5c6d336d6ba488e942bcadea5bf51f050a
React-NativeModulesApple: 4225ac31a26696c02c54b471052b3e85e74a9a0c
React-perflogger: cb433f318c6667060fc1f62e26eb58d6eb30a627

View File

@@ -629,7 +629,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\nexport NODE_ARGS=\"--max_old_space_size=4096\"\n../../node_modules/react-native/scripts/react-native-xcode.sh\n";
shellScript = "WITH_ENVIRONMENT=\"../../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
};
DE9A016B289A9A9A00E41CBB /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;

View File

@@ -1,5 +1,8 @@
{
"addPeople": {
"accessibilityLabel": {
"meetingLink": "Lidhje takimi: {{url}}"
},
"add": "Ftoni",
"addContacts": "Ftoni kontaktet tuaja",
"contacts": "kontakte",
@@ -39,6 +42,18 @@
"audioOnly": {
"audioOnly": "Gjerësi e ulët bande"
},
"bandwidthSettings": {
"assumedBandwidthBps": "p.sh. 10000000 për 10 Mbps",
"assumedBandwidthBpsWarning": "Vlera më të mëdha mund të shkaktojnë probleme rrjeti.",
"customValue": "vlerë vetjake",
"customValueEffect": "për të ujdisur vlerën e tanishme bps",
"leaveEmpty": "lëreni të zbrazët",
"leaveEmptyEffect": "për të lejuar kryerje vlerësimesh",
"possibleValues": "Vlera të mundshme",
"setAssumedBandwidthBps": "Gjerësi bande e supozuar (bps)",
"title": "Rregullime gjerësi bande",
"zeroEffect": "për të çaktivizuar videon"
},
"breakoutRooms": {
"actions": {
"add": "Shtoni aneks konsultimesh",
@@ -48,15 +63,22 @@
"leaveBreakoutRoom": "Dilni nga aneks konsultimesh",
"more": "Më tepër",
"remove": "Hiqe",
"rename": "Riemërtojeni",
"renameBreakoutRoom": "Riemërtoni aneks konsultimesh",
"sendToBreakoutRoom": "Dërgoje pjesëmarrësin te:"
},
"breakoutList": "Listë aneksesh",
"buttonLabel": "Dhoma aneks konsultimesh",
"defaultName": "Dhomë aneks konsultimesh #{{index}}",
"hideParticipantList": "Fshihe listën e pjesëmarrësve",
"mainRoom": "Dhoma kryesore",
"notifications": {
"joined": "Po hyhet te dhomë aneks konsultimesh \"{{name}}\"",
"joinedMainRoom": "Po hyhet te dhoma kryesore",
"joinedTitle": "Dhoma Aneks Konsultimesh"
}
},
"showParticipantList": "Shfaq listë pjesëmarrësish",
"title": "Dhoma Aneks Konsultimesh"
},
"calendarSync": {
"addMeetingURL": "Shtoni një lidhje takimi",
@@ -68,9 +90,9 @@
},
"join": "Merrni pjesë",
"joinTooltip": "Merrni pjesë në takim",
"nextMeeting": "takimi pasues",
"nextMeeting": "Takimi pasues",
"noEvents": "S’ka veprimtari të ardhshme të vëna në plan.",
"ongoingMeeting": "takim në zhvillim e sipër",
"ongoingMeeting": "Takim në zhvillim e sipër",
"permissionButton": "Hapni rregullimet",
"permissionMessage": "Që të shihni në aplikacion takimet tuaja, janë të domosdoshme lejet mbi Kalendarin.",
"refresh": "Rifresko kalendarin",
@@ -156,6 +178,7 @@
"localport_plural": "Porta vendore:",
"maxEnabledResolution": "maksimum dërgimi",
"more": "Shfaq më tepër",
"no": "jo",
"packetloss": "Humbje paketesh:",
"participant_id": "ID pjesëmarrësi:",
"quality": {
@@ -174,7 +197,8 @@
"status": "Lidhje:",
"transport": "Transport:",
"transport_plural": "Transporte:",
"video_ssrc": "SSRC Videoje:"
"video_ssrc": "Video SSRC:",
"yes": "po"
},
"dateUtils": {
"earlier": "Më herët",
@@ -195,7 +219,9 @@
"joinInBrowser": "Hyni që nga shfletues",
"launchMeetingLabel": "Si doni të hyhet në këtë takim?",
"launchWebButton": "Nise në web",
"noDesktopApp": "S’e keni aplikacionin?",
"noMobileApp": "S’e keni aplikacionin?",
"or": "OR",
"termsAndConditions": "Duke vazhduar, pajtoheni me <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>terms & conditions.</a> tona",
"title": "Po niset takimi juaj në {{app}}…",
"titleNew": "Po niset takimi juaj…",
@@ -220,7 +246,7 @@
"noPermission": "S’u akordua leje",
"previewUnavailable": "S’bëhet dot paraparje",
"selectADevice": "Përzgjidhni një pajisje",
"testAudio": "Luaj një tingull, për provë"
"testAudio": "Testojeni"
},
"dialIn": {
"screenTitle": "Përmbledhje rënieje numrash"
@@ -231,21 +257,28 @@
"dialog": {
"Back": "Mbrapsht",
"Cancel": "Anuloje",
"IamHost": "Jam organizatori",
"IamHost": "Hyni",
"Ok": "OK",
"Remove": "Hiqe",
"Share": "Ndaje",
"Submit": "Parashtroje",
"WaitForHostMsg": "Konferenca s’ka nisur ende. Nëse jeni organizatori, ju lutemi, bëni mirëfilltësimin. Përndryshe, ju lutemi, pritni që të mbërrijë organizatori.",
"WaitingForHostTitle": "Po pritet për organizatorin…",
"WaitForHostMsg": "Konferenca s’ka nisur, ngaqë s’ka mbërritur ende ndonjë moderator. Nëse dëshironi të bëheni një moderator, ju lutemi, bëni hyrjen. Përndryshe, ju lutemi, pritni.",
"WaitingForHostButton": "Prit për moderator",
"WaitingForHostTitle": "Po pritet për një moderator…",
"Yes": "Po",
"accessibilityLabel": {
"liveStreaming": "Transmetim i Drejtpërdrejtë"
"Cancel": "Anuloje (dilni nga dialogu)",
"Ok": "OK (ruajeni dhe dilni nga dialogu)",
"close": "Mbylle dialogun",
"liveStreaming": "Transmetim i Drejtpërdrejtë",
"sharingTabs": "Mundësi ndarjeje me të tjerë"
},
"add": "Shtoni",
"addMeetingNote": "Shtoni një shënim rreth këtij takimi",
"addOptionalNote": "Shtoni një shënim (në daçi):",
"allow": "Lejoje",
"allowToggleCameraDialog": "A e lejoni {{initiatorName}} të ndryshojë anën nga sheh kamera juaj?",
"allowToggleCameraTitle": "Të lejohet ndryshimi i kamerës?",
"alreadySharedVideoMsg": "Një tjetër pjesëmarrës po ndan me të tjerët një video. Kjo konferencë lejon vetëm një ndarje videoje në herë.",
"alreadySharedVideoTitle": "Lejohet vetëm një ndarje videoje me të tjerët në herë",
"applicationWindow": "Dritare aplikacioni",
@@ -306,7 +339,8 @@
"lockRoom": "Shtoni takim $t(lockRoomPasswordUppercase)",
"lockTitle": "Kyçja dështoi",
"login": "Hyrje",
"logoutQuestion": "Jeni i sigurt se doni të dilet dhe të ndalet konferenca?",
"loginQuestion": "Jeni i sigurt se doni të hyhet dhe të braktiset konferenca?",
"logoutQuestion": "Jeni i sigurt se doni të dilet dhe të braktiset konferenca?",
"logoutTitle": "Dalje",
"maxUsersLimitReached": "U mbërrit në kufi numri maksimum pjesëmarrësish. Konferenca është plot. Ju lutemi, lidhuni me të zotin e takimit, ose provoni më vonë!",
"maxUsersLimitReachedTitle": "U mbërrit në kufi numri maksimum pjesëmarrësish",
@@ -334,7 +368,7 @@
"muteEveryonesVideoTitle": "Të ndalet videoja e gjithkujt?",
"muteParticipantBody": "S’do jeni në gjendje të hiqni heshtimin për ta, por ata munden kurdo ta heqin për veten.",
"muteParticipantButton": "Heshtoje",
"muteParticipantsVideoBody": "S’do të jeni në gjendje të riaktivizoni kamerën e tyre, por ata munden kurdo ta riaktvizojnë për veten.",
"muteParticipantsVideoBody": "S’do të jeni në gjendje të riaktivizoni kamerën e tyre, por ata munden kurdo ta riaktivizojnë për veten.",
"muteParticipantsVideoBodyModerationOn": "S’do të jeni në gjendje të riaktivizoni kamerën e tyre dhe as ata s’do të munden.",
"muteParticipantsVideoButton": "Ndale videon",
"muteParticipantsVideoDialog": "Jeni i sigurt se doni të çaktivizoni kamerën e këtij pjesëmarrësi? S’do të jeni në gjendje të riaktivizoni kamerën, por ai mund ta riaktivizojë kurdo.",
@@ -349,8 +383,6 @@
"permissionCameraRequiredError": "Leja mbi kamerën është e domosdoshme për të marrë pjesë në konferenca me video. Ju lutemi, akordojeni që nga Rregullimet",
"permissionErrorTitle": "Leje e domosdoshme",
"permissionMicRequiredError": "Leja mbi mikrofonin është e domosdoshme për të marrë pjesë në konferenca me audio. Ju lutemi, akordojeni që nga Rregullimet",
"popupError": "Shfletuesi juaj i bllokon dritaret flluskë prej këtij sajti. Ju lutemi, aktivizoni flluskat te rregullimet e sigurisë të shfletuesit tuaj dhe riprovoni.",
"popupErrorTitle": "Flluska u bllokua",
"readMore": "më tepër",
"recentlyUsedObjects": "Së fundi përdorët objekte",
"recording": "Regjistrim",
@@ -367,6 +399,8 @@
"removePassword": "Hiqe $t(lockRoomPassword)",
"removeSharedVideoMsg": "Jeni i sigurt se doni të hiqni videon që keni ndarë me të tjerë?",
"removeSharedVideoTitle": "Hiqe videon e ndarë me të tjerë",
"renameBreakoutRoomLabel": "Emër dhome",
"renameBreakoutRoomTitle": "Riemërtoni aneks konsultimesh",
"reservationError": "Gabim sistemi rezervimi",
"reservationErrorMsg": "Kod gabimi: {{code}}, mesazh: {{msg}}",
"retry": "Riprovoni",
@@ -386,8 +420,10 @@
"sendPrivateMessageTitle": "Të dërgohet privatisht?",
"serviceUnavailable": "Shërbim jashtë funksionimi",
"sessTerminated": "Thirrja përfundoi",
"sessTerminatedReason": "Takimi u përfundua",
"sessionRestarted": "Thirrja rinisi për shkak të një problemi lidhjeje.",
"shareAudio": "Vazhdo",
"shareAudio": "Vazhdoni",
"shareAudioAltText": "që të ndani me të tjerë lëndën e dëshiruar, kaloni te \"Skedë Shfletuesi\", përzgjidhni lëndën, aktivizoni shenjën për \"ndani audio me të tjerë\" dhe mandej klikoni butonin \"ndaje me të tjerë\"",
"shareAudioTitle": "Si të ndahet audio me të tjerë",
"shareAudioWarningD1": "lypset të ndalni tregim ekrani, para se të ndani audion tuaj me të tjerë.",
"shareAudioWarningD2": "lypset të rinisni tregimin e ekranit tuaj dhe t’i vini shenjë mundësisë “ndani audio me të tjerë”.",
@@ -417,7 +453,25 @@
"thankYou": "Faleminderit që përdorni {{appName}}!",
"token": "token",
"tokenAuthFailed": "Na ndjeni, s’keni leje të merrni pjesë në këtë thirrje.",
"tokenAuthFailedReason": {
"audInvalid": "Vlerë `aud` e pavlefshme. Duhet të jetë `jitsi`.",
"contextNotFound": "Prej ngarkesës mungon objekti `context`.",
"expInvalid": "Vlerë `exp` e pavlefshme.",
"featureInvalid": "Veçori e pavlefshme: {{feature}}, gjasat janë të mos jetë sendërtuar ende.",
"featureValueInvalid": "Vlerë e pavlefshme për veçorinë: {{feature}}.",
"featuresNotFound": "Prej ngarkesës mungon objekti `features`.",
"headerNotFound": "Mungon krye.",
"issInvalid": "Vlerë `iss` e pavlefshme. Duhet të jetë `chat`.",
"kidMismatch": "ID kyçi (kid) nuk përputhet me sub.",
"kidNotFound": "Mungon ID Kyçi (kid).",
"nbfFuture": "Vlera `nbf` gjendet në të ardhmen.",
"nbfInvalid": "Vlerë `nbf` e pavlefshme.",
"payloadNotFound": "Mungon ngarkesë.",
"tokenExpired": "Token-i ka skaduar."
},
"tokenAuthFailedTitle": "Mirëfilltësimi dështoi",
"tokenAuthFailedWithReasons": "Na ndjeni, nuk keni leje të merrni pjesë në këtë thirrje. Arsye e mundshme: {{reason}}",
"tokenAuthUnsupported": "Nuk mbulohet URL token-i.",
"transcribing": "Transkriptim",
"unlockRoom": "Hiq $t(lockRoomPassword) takimi",
"user": "Përdorues",
@@ -429,8 +483,12 @@
"verifyParticipantTitle": "Verifikim përdoruesi",
"videoLink": "Lidhje videoje",
"viewUpgradeOptions": "Shihni mundësi përmirësimi",
"viewUpgradeOptionsContent": "Që të përfitoni përdorim të pakufizuar veçorish me pagesë, të tilla si regjistrimi, transcriptime, RTMP Streaming & etj, duhet të përmirësoni planin tuaj.",
"viewUpgradeOptionsContent": "Që të përfitoni përdorim të pakufizuar veçorish me pagesë, të tilla si regjistrimi, transkriptime, RTMP Streaming & etj, duhet të përmirësoni planin tuaj.",
"viewUpgradeOptionsTitle": "Zbuluat një veçori me pagesë!",
"whiteboardLimitContent": "Na ndjeni, është mbërritur te kufiri i tabelave të njëkohshme.",
"whiteboardLimitReference": "Për më tepër hollësi, ju lutemi, vizitoni",
"whiteboardLimitReferenceUrl": "sajtin tonë",
"whiteboardLimitTitle": "Kufizim përdorimi tabele",
"yourEntireScreen": "Krejt ekranin tuaj"
},
"documentSharing": {
@@ -443,6 +501,9 @@
"title": "Trupëzojeni këtë takim"
},
"feedback": {
"accessibilityLabel": {
"yourChoice": "Zgjedhja juaj: {{rating}}"
},
"average": "Çka",
"bad": "I dobët",
"detailsLabel": "Na thoni më tepër rreth tij.",
@@ -499,13 +560,16 @@
"noNumbers": "S’ka numra për përdorim.",
"noPassword": "Asnjë",
"noRoom": "S’u dha dhomë për të cilën të formësohet numri.",
"noWhiteboard": "S’u ngarkua dot tabela.",
"numbers": "Numra Për T’u Përdorur",
"password": "$t(lockRoomPasswordUppercase): ",
"reachedLimit": "Keni mbërritur në kufijtë e planit tuaj.",
"sip": "Adresë SIP",
"sipAudioOnly": "Adresë SIP vetëm për audio",
"title": "Ndani me të tjerë",
"tooltip": "Ndani me të tjerë lidhje dhe hollësi numrash për këtë takim",
"upgradeOptions": "Ju lutemi, shihni mundësitë e përmirësimit, te"
"upgradeOptions": "Ju lutemi, shihni mundësitë e përmirësimit, te",
"whiteboardError": "Gabim në ngarkimin e tabelës. Ju lutemi, riprovoni më vonë."
},
"inlineDialogFailure": {
"msg": "Ngecëm pak.",
@@ -607,13 +671,13 @@
"knockingParticipantList": "Listë pjesëmarrësish që duan të hyjnë",
"lobbyChatStartedNotification": "{{moderator}} filloi një fjalosje në holl me {{attendee}}",
"lobbyChatStartedTitle": "{{moderator}} ka filluar një fjalosje në holl me ju.",
"lobbyClosed": "Dhoma holl u mbyll.",
"nameField": "Jepni emrin tuaj",
"notificationLobbyAccessDenied": "Hyrja e {{targetParticipantName}} është hedhur poshtë nga {{originParticipantName}}",
"notificationLobbyAccessGranted": "{{targetParticipantName}} është lejuar të hyjë nga {{originParticipantName}}",
"notificationLobbyDisabled": "Holli është çaktivizuar nga {{originParticipantName}}",
"notificationLobbyEnabled": "Holli është aktivizuar nga {{originParticipantName}}",
"notificationTitle": "Holl",
"passwordField": "Jepni fjalëkalim takimi",
"passwordJoinButton": "Hyni",
"title": "Holl",
"toggleLabel": "Aktivizoni hollin"
@@ -646,6 +710,8 @@
"sessionToken": "Token Sesioni",
"start": "Nis Regjistrim",
"stop": "Ndale Regjistrimin",
"stopping": "Po ndalet Regjistrimi",
"wait": "Ju lutemi, prisni, teksa ruajmë regjistrimin tuaj",
"yes": "Po"
},
"lockRoomPassword": "fjalëkalim",
@@ -667,8 +733,11 @@
"connectedTwoMembers": "{{first}} dhe {{second}} tjetër hynë në takim",
"dataChannelClosed": "Rënie cilësie videoje",
"dataChannelClosedDescription": "Kanali urë u shkëput, kështu që cilësia e videos është kufizuar te vlera më e ulët.",
"disabledIframe": "Trupëzimi është menduar vetëm për qëllime demonstrimi, ndaj kjo thirrje do të ndërpritet pas {{timeout}} minutash.",
"disabledIframeSecondary": "Trupëzimi i {{domain}} është menduar vetëm për qëllime demonstrimi, ndaj kjo thirrje do të ndërpritet pas {{timeout}} minutash. Ju lutemi, për trupëzime të njëmendta përdorni <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a>!",
"disconnected": "u shkëput",
"displayNotifications": "Shfaq njoftime për",
"dontRemindMe": "Mos ma kujto",
"focus": "Fokusi te konferenca",
"focusFail": "{{component}} jo i passhëm - riprovoni pas {{ms}} sekondash",
"gifsMenu": "GIPHY",
@@ -677,6 +746,7 @@
"invitedOneMember": "{{name}} u ftua",
"invitedThreePlusMembers": "{{name}} dhe {{count}} të tjerë u ftuan",
"invitedTwoMembers": "{{first}} dhe {{second}} u ftuan",
"joinMeeting": "Hyni",
"kickParticipant": "{{kicked}} u përzu nga {{kicker}}",
"leftOneMember": "{{name}} doli nga takimi",
"leftThreePlusMembers": "{{name}} dhe mjaft të tjerë dolën nga takimi",
@@ -711,7 +781,6 @@
"newDeviceCameraTitle": "U pikas kamerë e re",
"noiseSuppressionDesktopAudioDescription": "Mbytja e zhurmave s’mund të aktivizohet teksa ndahet me të tjerët audioja e desktopit, ju lutemi, çaktivizojeni dhe riprovoni.",
"noiseSuppressionFailedTitle": "S’u arrit të nisej mbytja e zhurmave",
"noiseSuppressionNoTrackDescription": "Ju lutemi, së pari, çheshtoni mikrofonin tuaj.",
"noiseSuppressionStereoDescription": "Aktualisht nuk mbulohet mbytje zhurmash audioje stereo.",
"oldElectronClientDescription1": "Duket se përdorni një version të vjetër të klientit Jitsi Meet, i cili ka cenueshmëri të ditura sigurie. Ju lutemi, siguroni përditësimin me ",
"oldElectronClientDescription2": "montimin tonë më të ri",
@@ -735,13 +804,18 @@
"startSilentTitle": "Hytë pa zë në dalje!",
"suboptimalBrowserWarning": "Kemi frikë se funksionimi i takimit për ju s’do të jetë kushedi këtu. Po kërkojmë rrugë për ta përmirësuar këtë punë, por deri atëherë, ju lutemi, provoni të përdorni një nga <a href='{{recommendedBrowserPageLink}}' target='_blank'>shfletuesit e mbuluar plotësisht</a>.",
"suboptimalExperienceTitle": "Sinjalizim Mbi Shfletuesin",
"suggestRecordingAction": "Niseni",
"suggestRecordingDescription": "Do të donit të nisej një regjistrim?",
"suggestRecordingTitle": "Regjistroje këtë takim",
"unmute": "Çheshtoje",
"videoMutedRemotelyDescription": "Mundeni përherë ta rihapni.",
"videoMutedRemotelyTitle": "Videoja juaj u mbyll nga {{moderator}}",
"videoUnmuteBlockedDescription": "Heqja e heshtimit të kamerës dhe veprimi i tregimit të desktopit janë bllokuar përkohësisht për shkak kufizimesh të sistemit.",
"videoUnmuteBlockedTitle": "Heqja e heshtimit të kamerës dhe tregimi i desktopit janë bllokuar!",
"viewLobby": "Shihni hollin",
"waitingParticipants": "{{waitingParticipants}} vetë"
"waitingParticipants": "{{waitingParticipants}} vetë",
"whiteboardLimitDescription": "Ju lutemi, ruani ç’keni bërë, ngaqë së shpejti do të mbërrihet në kufi përdoruesi dhe tabela do të mbyllet.",
"whiteboardLimitTitle": "Përdorim tabele"
},
"participantsPane": {
"actions": {
@@ -752,6 +826,7 @@
"askUnmute": "Kërkoni heqje heshtimi",
"audioModeration": "Heqin heshtim të vetes",
"blockEveryoneMicCamera": "Bllokoni mikrofonin dhe kamerën e gjithkujt",
"breakoutRooms": "Dhoma anekse konsultimesh",
"invite": "Ftoni Dikë",
"moreModerationActions": "Më tepër mundësi moderimi",
"moreModerationControls": "Më tepër kontrolle moderimi",
@@ -769,7 +844,8 @@
"headings": {
"lobby": "Holli ({{count}})",
"participantsList": "Pjesëmarrës në takim ({{count}})",
"visitors": "Vizitorë ({{count}})",
"visitorRequests": " (requests {{count}})",
"visitors": "Vizitorë {{count}}",
"waitingLobby": "Duke pritur në holl ({{count}})"
},
"search": "Kërkoni te pjesëmarrësit",
@@ -859,12 +935,15 @@
"joinWithoutAudio": "Merrni pjesë pa audio",
"keyboardShortcuts": "Aktivizo shkurtore tastiere",
"linkCopied": "Lidhja u kopjua në të papastër",
"lookGood": "Mikrofoni juaj po punon si duhet",
"lookGood": "Gjithçka po punon si duhet",
"or": "ose",
"premeeting": "Para takimit",
"proceedAnyway": "Vazhdo, sido qoftë",
"recordingWarning": "Këtë thirrje pjesëmarrës të tjerë mund ta regjistrojnë",
"screenSharingError": "Gabim ndarjeje ekrani me të tjerë:",
"showScreen": "Aktivizoni skenë para takimit",
"startWithPhone": "Nise me audio telefoni",
"unsafeRoomConsent": "I kuptoj rreziqet, dëshiroj të marr pjesë te takimi",
"videoOnlyError": "Gabim video:",
"videoTrackError": "S’u krijua dot pistë video.",
"viewAllNumbers": "shihni krejt numrat"
@@ -885,9 +964,9 @@
},
"profile": {
"avatar": "avatar",
"setDisplayNameLabel": "Caktoni emrin tuaj për në ekran",
"setDisplayNameLabel": "Emër",
"setEmailInput": "Jepni email",
"setEmailLabel": "Caktoni email të gravatarit tuaj",
"setEmailLabel": "Jepni email të gravatarit tuaj",
"title": "Profil"
},
"raisedHand": "Do të donte të fliste",
@@ -919,17 +998,16 @@
"failedToStart": "S’u arrit të niset regjistrimi",
"fileSharingdescription": "Ndajeni regjistrimin me pjesëmarrësit në takim",
"highlight": "Nxjerrje në pah",
"highlightMoment": "Nxirni në pah një çast",
"highlightMoment": "Nxirrni në pah një çast",
"highlightMomentDisabled": "Mund të nxirrni në pah çaste kur fillon regjistrimi",
"highlightMomentSuccess": "Çasti u nxorr në pah",
"highlightMomentSuccess": "Çasti u nxor në pah",
"highlightMomentSucessDescription": "Çasti i nxjerrë në pah nga ju do të shtohet te përmbledhja e takimit.",
"inProgress": "Regjistrim ose transmetim drejtpërsëdrejti në ecuri e sipër",
"limitNotificationDescriptionNative": "Për shkak kërkesash të shumta, regjistrimi juaj do të kufizohet në {{limit}} min. Për regjistrime të pakufizuara provoni <3>{{app}}</3>.",
"limitNotificationDescriptionWeb": "Për shkak kërkesash të shumta, regjistrimi juaj do të kufizohet në {{limit}} min. Për regjistrime të pakufizuara provoni <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"linkGenerated": "Kemi prodhuar një lidhje për te regjistrimi juaj.",
"live": "DREJTPËRSËDREJTI",
"localRecordingNoNotificationWarning": "Regjistrimi s’do t’u njoftohet pjesëmarrësve të tjerë. Do t’ju duhet t’i vini në dijeni se takimi po regjistrohet.",
"localRecordingNoVideo": "Videoja nuk po regjistrohet",
"localRecordingNoVideo": "Videoja s’po regjistrohet",
"localRecordingStartWarning": "Ju lutemi, sigurohuni se e ndalni regjistrimin para se dilni nga takimi, që të mund ta ruani.",
"localRecordingStartWarningTitle": "Ndaleni regjistrimin që ta ruani",
"localRecordingVideoStop": "Ndalja e videos tuaj do të ndalë gjithashtu edhe regjistrimin vendor. Jeni i sigurt se doni të vazhdohet?",
@@ -944,13 +1022,15 @@
"onBy": "{{name}} nisi regjistrimin",
"onlyRecordSelf": "Regjistro vetëm rrjedhat e mia audio dhe video",
"pending": "Po përgatitet të regjistrohet takimi…",
"rec": "REC",
"recordAudioAndVideo": "Regjistro audio dhe video",
"recordTranscription": "Regjistro transkriptimin",
"saveLocalRecording": "Ruajeni lokalisht kartelën e regjistrimit (Beta)",
"serviceDescription": "Regjistrimi juaj do të ruhet nga shërbimi i regjistrimit",
"serviceDescriptionCloud": "Regjistrim në re",
"serviceDescriptionCloudInfo": "Takimet e regjistruara spastrohen automatikisht 24h pas kohës së regjistrimit të tyre.",
"serviceName": "Shërbim regjistrimi",
"sessionAlreadyActive": "Ky sesion po regjistrohet ose transmetohet drejtpërsëdrejti tashmë.",
"showAdvancedOptions": "Mundësi të mëtejshme",
"signIn": "Hyni",
"signOut": "Dilni",
"surfaceError": "Ju lutemi, përdorni skedën e tanishme.",
@@ -966,10 +1046,17 @@
"security": {
"about": "Takimit tuaj mund t’i shtoni një $t(lockRoomPassword). Pjesëmarrësve do t’u duhet të japin $t(lockRoomPassword) përpara se të lejohen të marrin pjesë në takim.",
"aboutReadOnly": "Pjesëmarrësit moderatorë mund t’i shtojnë takimit një $t(lockRoomPassword). Pjesëmarrësve do t’u duhet të japin $t(lockRoomPassword) përpara se të lejohen të marrin pjesë në takim.",
"insecureRoomNameWarning": "Emri i dhomës s’është pa rrezik. Pjesëmarrës të padëshiruar munden të marrin pjesë në konferencën tuaj. Shihni mundësinë e bërjes të sigurt të takimit tuaj, duke përdorur butonin e sigurisë.",
"title": "Mundësi Sigurie"
"insecureRoomNameWarningNative": "Emri i dhomës s’është pa rrezik. Pjesëmarrës të padëshiruar munden të hyjnë në takimin tuaj. {{recommendAction}} Mësoni më tepër rreth sigurimit të takimit tuaj ",
"insecureRoomNameWarningWeb": "Emri i dhomës s’është pa rrezik. Pjesëmarrës të padëshiruar munden të hyjnë në takimin tuaj. {{recommendAction}} Mësoni më tepër rreth sigurimit të takimit tuaj <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">këtu</a>.",
"title": "Mundësi Sigurie",
"unsafeRoomActions": {
"meeting": "Shihni mundësinë e sigurimit të takimit tuaj, duke përdorur butonin e sigurisë.",
"prejoin": "Shihni mundësinë e përdorimit për takimin të një emri më të veçantë.",
"welcome": "Shihni mundësinë e përdorimit për takimin të një emri më të veçantë, ose zgjidhni një nga sugjerimet."
}
},
"settings": {
"audio": "Audio",
"buttonLabel": "Rregullime",
"calendar": {
"about": "Integrimi me kalendar {{appName}} përdoret për të hyrë me sukses te kalendari juaj, që kështu të mund të lexohen veprimtari të ardhshme.",
@@ -990,9 +1077,11 @@
"maxStageParticipants": "Numër maksimum pjesëmarrësish që mund të fiksohen te skena kryesore (EKSPERIMENTALe)",
"microphones": "Mikrofona",
"moderator": "Moderator",
"more": "Më tepër",
"moderatorOptions": "Mundësi moderatori",
"more": "Të përgjitshme",
"name": "Emër",
"noDevice": "Asnjë",
"notifications": "Njoftime",
"participantJoined": "Hyri Pjesëmarrës",
"participantKnocking": "Pjesëmarrës hyri në holl",
"participantLeft": "Doli Pjesëmarrës",
@@ -1003,13 +1092,14 @@
"selectCamera": "Kamerë",
"selectMic": "Mikrofon",
"selfView": "Parje e vetes",
"sounds": "Tinguj",
"shortcuts": "Shkurtore",
"speakers": "Altoparlantë",
"startAudioMuted": "Gjithkush fillon i heshtuar",
"startReactionsMuted": "Heshto tinguj reagimesh për këdo",
"startVideoMuted": "Gjithkush fillon i fshehur",
"talkWhileMuted": "Flisni, teksa jeni i heshtuar",
"title": "Rregullime"
"title": "Rregullime",
"video": "Video"
},
"settingsView": {
"advanced": "Të mëtejshme",
@@ -1017,6 +1107,7 @@
"alertOk": "OK",
"alertTitle": "Kujdes",
"alertURLText": "URL-ja e dhënë për shërbyesin është e pavlefshme",
"apply": "Aplikoji",
"buildInfoSection": "Hollësi Montimi",
"conferenceSection": "Konferencë",
"disableCallIntegration": "Çaktivizo integrim thirrje me sistemin",
@@ -1027,19 +1118,21 @@
"displayNamePlaceholderText": "P.sh.: Zamir Gjoli",
"email": "Email",
"emailPlaceholderText": "email@example.com",
"gavatarMessage": "Nëse emal-i juaj është i përshoqëruar me një llogari Gravatar, do ta përdorim për të shfaqur foton e profilit tuaj.",
"goTo": "Kalo te",
"header": "Rregullime",
"help": "Ndihmë",
"links": "Lidhje",
"privacy": "Privatësi",
"profileSection": "Profil",
"sdkVersion": "Version SDK",
"serverURL": "URL Shërbyesi",
"showAdvanced": "Shfaq rregullime të mëtejshme",
"startCarModeInLowBandwidthMode": "Nën mënyrën “gjerësi e ulët bande” nis mënyrën automjet",
"startWithAudioMuted": "Fillo me audio të mbyllur",
"startWithVideoMuted": "Fillo me video të mbyllur",
"terms": "Terma",
"version": "Version"
"version": "Version aplikacioni"
},
"share": {
"dialInfoText": "\n\n=====\n\nThjesht doni t’i bini numrit në telefonin tuaj?\n\n{{defaultDialInNumber}}Klikoni mbi këtë lidhje, që të shihni numra telefoni për këtë takim\n{{dialInfoPageUrl}}",
@@ -1050,7 +1143,7 @@
"angry": "I zemëruar",
"disgusted": "I pështirosur",
"displayEmotions": "Shfaq emocione",
"fearful": "I firkësuar",
"fearful": "I frikësuar",
"happy": "I gëzuar",
"hours": "{{count}}h",
"minutes": "{{count}}m",
@@ -1058,7 +1151,7 @@
"neutral": "Asnjanës",
"sad": "I trishtuar",
"search": "Kërko",
"searchHint": "Kërkoni pjesëmarrësit",
"searchHint": "Kërkoni për pjesëmarrës",
"seconds": "{{count}}s",
"speakerStats": "Statistika Folësi",
"speakerTime": "Kohë Folësi",
@@ -1081,7 +1174,7 @@
"toolbar": {
"Settings": "Rregullime",
"accessibilityLabel": {
"Settings": "Shfaq/Fshih rregullimet",
"Settings": "Hap rregullimet",
"audioOnly": "Hap/Mbyll vetëm audion",
"audioRoute": "Përzgjidhni pajisje zëri",
"boo": "Ya",
@@ -1091,11 +1184,20 @@
"cc": "Shfaq/Fshih titra",
"chat": "Hapni / Mbyllni fjalosje",
"clap": "Duartrokitje",
"closeChat": "Mbylle fjalosjen",
"closeMoreActions": "Mbyll menunë Më Tepër Veprime",
"closeParticipantsPane": "Mbyll kuadratin pjesëmarrës",
"collapse": "Tkurre",
"document": "Shfaq/Fshih dokument të ndarë",
"documentClose": "Mbyll dokument të ndarë",
"documentOpen": "Hap dokument të ndarë",
"download": "Shkarkoni aplikacionet tona",
"embedMeeting": "Trupëzoni takimin",
"endConference": "Përfundoje takimin për të tërë",
"enterFullScreen": "Shiheni sa krejt ekrani",
"enterTileView": "Kaloni nën mënyrën me kuadrate",
"exitFullScreen": "Dil nga mënyra “Sa krejt ekrani”",
"exitTileView": "Dil nga mënyra me kuadrate",
"expand": "Zgjeroje",
"feedback": "Lini përshtypje",
"fullScreen": "Kalo nën/Dil nga mënyra “Sa krejt ekrani”",
@@ -1104,6 +1206,7 @@
"hangup": "Braktiseni takimin",
"heading": "Panel",
"help": "Ndihmë",
"hideWhiteboard": "Fshihe tabelën",
"invite": "Ftoni njerëz",
"kick": "Përzëre pjesëmarrësin",
"laugh": "E qeshur",
@@ -1113,21 +1216,25 @@
"lobbyButton": "Aktivizo/Çaktivizoni mënyrën holl",
"localRecording": "Shfaq/Fshih kontrolle regjistrimi vendor",
"lockRoom": "Aktivizo/Çaktivizo fjalëkalim takimi",
"lowerHand": "Ulni dorën",
"moreActions": "Më tepër veprime",
"moreActionsMenu": "Menu “Më tepër veprime”",
"moreOptions": "Shfaq më tepër mundësi",
"mute": "Heshtoje / Çheshtoje",
"mute": "Heshto mikrofonin",
"muteEveryone": "Heshto gjithkënd",
"muteEveryoneElse": "Heshto gjithkënd tjetër",
"muteEveryoneElsesVideoStream": "Ndal videon e gjithkujt tjetër",
"muteEveryonesVideoStream": "Ndal videon e gjithkujt",
"muteGUMPending": "Po lidhet mikrofoni juaj",
"noiseSuppression": "Mbytje zhurmash",
"participants": "Pjesëmarrës",
"openChat": "Hapni fjalosje",
"participants": "Hapni kuadrat pjesëmarrësish",
"pip": "Aktivizo/Çaktivizo mënyrën “Picture-in-Picture”",
"privateMessage": "Dërgoni mesazh privat",
"profile": "Përpunoni profilin tuaj",
"raiseHand": "Ngrini / Ulni dorën tuaj",
"reactionsMenu": "Hap / Mbyll menu reagimesh",
"raiseHand": "Ngrini dorën",
"reactions": "Reagime",
"reactionsMenu": "Menu reagimesh",
"recording": "Nis/Ndal regjistrimin",
"remoteMute": "Heshto pjesëmarrësin",
"remoteVideoMute": "Çaktivizo kamerën e pjesëmarrësit",
@@ -1135,20 +1242,25 @@
"selectBackground": "Përzgjidhni Sfond",
"selfView": "Shfaq/Fshih pamje të vetes",
"shareRoom": "Ftoni dikë",
"shareYourScreen": "Nisni / Ndalni tregimin e ekranit tuaj",
"shareYourScreen": "Nisni tregimin e ekranit tuaj",
"shareaudio": "Ndani audio me të tjerë",
"sharedvideo": "Aktivizo/Çaktivizo ndarje videoje me të tjerë",
"sharedvideo": "Ndani video me të tjerë",
"shortcuts": "Shfaq/Fshih shkurtore",
"show": "Shfaqe në skenë",
"showWhiteboard": "Shfaq tabelë",
"silence": "Heshtje",
"speakerStats": "Shfaq/Fshih statistika folësi",
"stopScreenSharing": "Ndalni tregimin e ekranit tuaj",
"stopSharedVideo": "Ndalni videon",
"surprised": "I befasuar",
"tileView": "Kalo në/Dil nga mënyra mozaik",
"toggleCamera": "Hap/Mbyll kamerën",
"toggleFilmstrip": "Shfaq/Fshih shirit filmi",
"unmute": "Hiqni heshtim mikrofoni",
"videoblur": "Aktivizo/Çaktivizo turbullim videoje",
"videomute": "Nis / Ndal kamerën",
"whiteboard": "Shfaq / Fshih tabelën"
"videomute": "Ndal kamerën",
"videomuteGUMPending": "Po lidhet kamera juaj",
"videounmute": "Nis kamerën"
},
"addPeople": "Shtoni persona te thirrja juaj",
"audioOnlyOff": "Çaktivizo mënyrën “Sasi e ulët të dhënash trafiku”",
@@ -1161,6 +1273,7 @@
"chat": "Hap / Mbyll fjalosje",
"clap": "Duartrokitje",
"closeChat": "Mbyll fjalosjen",
"closeParticipantsPane": "Mbylle kuadratin e pjesëmarrësve",
"closeReactionsMenu": "Mbyll menu reagimesh",
"disableNoiseSuppression": "Çaktivizo mbytje zhurmash",
"disableReactionSounds": "Mund të çaktivizoni tinguj reagimesh për këtë takim",
@@ -1169,6 +1282,7 @@
"download": "Shkarkoni aplikacione tonat",
"e2ee": "Fshehtëzim Skaj-Më-Skaj",
"embedMeeting": "Trupëzoni takim",
"enableNoiseSuppression": "Aktivizoni mbytje zhurmash",
"endConference": "Përfundoje takimin për të tërë",
"enterFullScreen": "Shiheni sa krejt ekrani",
"enterTileView": "Kalo te pamja me kuadrate",
@@ -1193,9 +1307,10 @@
"lowerYourHand": "Ulni dorën",
"moreActions": "Më tepër veprime",
"moreOptions": "Më tepër veprime",
"mute": "Heshtoje / Hiqi heshtimin",
"mute": "Heshto mikrofonin",
"muteEveryone": "Heshto gjithkënd",
"muteEveryonesVideo": "Çaktivizo videon e gjithkujt",
"muteGUMPending": "Po lidhet mikrofoni juaj",
"noAudioSignalDesc": "Nëse s’e keni heshtuar që nga rregullimet e sistemit, ose nga hardware-i, shihni mundësinë e ndërrimit të pajisjes.",
"noAudioSignalDescSuggestion": "Nëse s’e keni heshtuar që nga rregullimet e sistemit, ose nga hardware-i, shihni mundësinë e kalimit te pajisja e sugjeruar.",
"noAudioSignalDialInDesc": "Mund t’i bini numrit edhe duke përdorur:",
@@ -1210,7 +1325,7 @@
"pip": "Kalo nën mënyrën “Picture-in-Picture”",
"privateMessage": "Dërgo mesazh privat",
"profile": "Përpunoni profilin tuaj",
"raiseHand": "Ngrini / Ulni dorën",
"raiseHand": "Ngrini dorën",
"raiseYourHand": "Ngrini dorën",
"reactionBoo": "Dërgoni reagim me ya",
"reactionClap": "Dërgoni reagim me duartrokitje",
@@ -1218,6 +1333,7 @@
"reactionLike": "Dërgoni reagim me “thumbs up”",
"reactionSilence": "Dërgoni reagim me heshtje",
"reactionSurprised": "Dërgoni reagim të befasuari",
"reactions": "Reagime",
"security": "Mundësi sigurie",
"selectBackground": "Përzgjidhni sfond",
"shareRoom": "Ftoni dikë",
@@ -1237,17 +1353,17 @@
"talkWhileMutedPopup": "Po provoni të flisni? Jeni i heshtuar.",
"tileViewToggle": "Kalo në/Dil nga mënyra mozaik",
"toggleCamera": "Hapni/Mbyllni kamerën",
"videoSetting": "Rregullime videoje",
"videomute": "Nisni / Ndalni kamerën"
"unmute": "Hiqni heshtimin e mikrofonit",
"videoSettings": "Rregullime videoje",
"videomute": "Ndalni kamerën",
"videomuteGUMPending": "Po lidhet kamera juaj",
"videounmute": "Nisni kamerën"
},
"transcribing": {
"ccButtonTooltip": "Ndali / Nisi titrat",
"error": "Transkriptimi dështoi. Ju lutemi, riprovoni.",
"expandedLabel": "Transkriptimi aktualisht është aktiv",
"failedToStart": "S’u arrit të nisej transkriptim",
"labelToolTip": "Takimit po i bëhet transkriptim",
"off": "Transkriptimi u ndal",
"pending": "Po përgatitet të transkriptohet takimi…",
"sourceLanguageDesc": "Aktualisht si gjuhë takimi është caktuar <b>{{sourceLanguage}}</b>. <br/> Mund ta ndryshoni që nga ",
"sourceLanguageHere": "këtu",
"start": "Fillo shfaqje titrash",
@@ -1284,7 +1400,7 @@
"audioOnly": "AUD",
"audioOnlyExpanded": "Gjendeni nën mënyrën “gjerësi e ulët bande”. Nën këtë mënyrë, do të merrni vetëm audio dhe tregim ekrani.",
"bestPerformance": "Punimi më i mirë",
"callQuality": "Cilësi videoje",
"callQuality": "Cilësi Videoje (0 për punimin më të mirë, 3 për cilësinë më të lartë)",
"hd": "CL",
"hdTooltip": "Po shihet video në cilësi të lartë",
"highDefinition": "Cilësi e Lartë",
@@ -1326,6 +1442,10 @@
"videomute": "Pjesëmarrësi ka ndalur kamerën"
},
"virtualBackground": {
"accessibilityLabel": {
"currentBackground": "Sfondi i tanishëm: {{background}}",
"selectBackground": "Përzgjidhni një sfond"
},
"addBackground": "Shtoni sfond",
"apply": "Zbatoje",
"backgroundEffectError": "S’u arrit të zbatohej efekt sfondi.",
@@ -1343,13 +1463,20 @@
"none": "Asnjë",
"pleaseWait": "Ju lutemi, pritni…",
"removeBackground": "Hiqe sfondin",
"slightBlur": "Turbullim Paksa",
"slightBlur": "Gjysmë-Turbullim",
"title": "Sfonde virtualë",
"uploadedImage": "Ngarkoi figurën {{index}}",
"webAssemblyWarning": "Nuk mbulohet WebAssembly",
"webAssemblyWarningDescription": "WebAssembly e çaktivizuar ose e pambuluar nga ky shfletues"
},
"visitorsLabel": "Numër vizitorësh: {{count}}",
"visitors": {
"chatIndicator": "(vizitor)",
"labelTooltip": "Numër vizitorësh: {{count}}",
"notification": {
"description": "Që të merrni pjesë, ngrini dorën",
"title": "Jeni vizitor në takim"
}
},
"volumeSlider": "Rrëshqitës volumi",
"welcomepage": {
"accessibilityLabel": {
@@ -1382,6 +1509,7 @@
"microsoftLogo": "Stemë Microsoft-i",
"policyLogo": "Stemë Rregullash"
},
"meetingsAccessibilityLabel": "Takime",
"mobileDownLoadLinkAndroid": "Shkarkoni aplikacionin për celular me Android",
"mobileDownLoadLinkFDroid": "Shkarkoni aplikacionin për celular me F-Droid",
"mobileDownLoadLinkIos": "Shkarkoni aplikacionin për celular me iOS",
@@ -1405,6 +1533,7 @@
"whiteboard": {
"accessibilityLabel": {
"heading": "Tabelë shënimesh"
}
},
"screenTitle": "Tabelë shënimesh"
}
}

View File

@@ -305,6 +305,8 @@
"contactSupport": "Contact support",
"copied": "Copied",
"copy": "Copy",
"demoteParticipantDialog": "Are you sure you want to move this participant to visitor?",
"demoteParticipantTitle": "Move to visitor",
"dismiss": "Dismiss",
"displayNameRequired": "Hi! What’s your name?",
"done": "Done",
@@ -560,6 +562,7 @@
"noNumbers": "No dial-in numbers.",
"noPassword": "None",
"noRoom": "No room was specified to dial-in into.",
"noWhiteboard": "Could not load the whiteboard.",
"numbers": "Dial-in Numbers",
"password": "$t(lockRoomPasswordUppercase): ",
"reachedLimit": "You have reached the limit of your plan.",
@@ -567,7 +570,8 @@
"sipAudioOnly": "SIP audio only address",
"title": "Share",
"tooltip": "Share link and dial-in info for this meeting",
"upgradeOptions": "Please check the upgrade options on"
"upgradeOptions": "Please check the upgrade options on",
"whiteboardError": "Error loading the whiteboard. Please try again later."
},
"inlineDialogFailure": {
"msg": "We stumbled a bit.",
@@ -802,12 +806,16 @@
"startSilentTitle": "You joined with no audio output!",
"suboptimalBrowserWarning": "We are afraid your meeting experience isn't going to be that great here. We are looking for ways to improve this, but until then please try using one of the <a href='{{recommendedBrowserPageLink}}' target='_blank'>fully supported browsers</a>.",
"suboptimalExperienceTitle": "Browser Warning",
"suggestRecordingAction": "Start",
"suggestRecordingDescription": "Would you like to start a recording?",
"suggestRecordingTitle": "Record this meeting",
"unmute": "Unmute",
"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.",
"videoUnmuteBlockedTitle": "Camera unmute and desktop sharing blocked!",
"viewLobby": "View lobby",
"viewVisitors": "View visitors",
"waitingParticipants": "{{waitingParticipants}} people",
"whiteboardLimitDescription": "Please save your progress, as the user limit will soon be reached and the whiteboard will close.",
"whiteboardLimitTitle": "Whiteboard usage"
@@ -934,6 +942,7 @@
"or": "or",
"premeeting": "Pre meeting",
"proceedAnyway": "Proceed anyway",
"recordingWarning": "Other participants may be recording this call",
"screenSharingError": "Screen sharing error:",
"showScreen": "Enable pre meeting screen",
"startWithPhone": "Start with phone audio",
@@ -1413,6 +1422,7 @@
},
"videothumbnail": {
"connectionInfo": "Connection Info",
"demote": "Move to visitor",
"domute": "Mute",
"domuteOthers": "Mute everyone else",
"domuteVideo": "Disable camera",
@@ -1467,6 +1477,7 @@
"chatIndicator": "(visitor)",
"labelTooltip": "Number of visitors: {{count}}",
"notification": {
"demoteDescription": "Sent here by {{actor}}, raise your hand to participate",
"description": "To participate raise your hand",
"title": "You are a visitor in the meeting"
}
@@ -1527,6 +1538,7 @@
"whiteboard": {
"accessibilityLabel": {
"heading": "Whiteboard"
}
},
"screenTitle": "Whiteboard"
}
}

View File

@@ -113,7 +113,7 @@ import { isAudioMuteButtonDisabled } from '../../react/features/toolbox/function
import { setTileView, toggleTileView } from '../../react/features/video-layout/actions.any';
import { muteAllParticipants } from '../../react/features/video-menu/actions';
import { setVideoQuality } from '../../react/features/video-quality/actions';
import { toggleWhiteboard } from '../../react/features/whiteboard/actions.any';
import { toggleWhiteboard } from '../../react/features/whiteboard/actions.web';
import { getJitsiMeetTransport } from '../transport';
import {

View File

@@ -11,6 +11,7 @@ import {
} from '../../react/features/base/conference/actions';
import { isMobileBrowser } from '../../react/features/base/environment/utils';
import { setColorAlpha } from '../../react/features/base/util/helpers';
import { sanitizeUrl } from '../../react/features/base/util/uri';
import { setDocumentUrl } from '../../react/features/etherpad/actions';
import { setFilmstripVisible } from '../../react/features/filmstrip/actions.any';
import {
@@ -135,14 +136,16 @@ UI.unbindEvents = () => {
* @param {string} name etherpad id
*/
UI.initEtherpad = name => {
if (etherpadManager || !config.etherpad_base || !name) {
const etherpadBaseUrl = sanitizeUrl(config.etherpad_base);
if (etherpadManager || !etherpadBaseUrl || !name) {
return;
}
logger.log('Etherpad is enabled');
etherpadManager = new EtherpadManager(eventEmitter);
const url = new URL(name, config.etherpad_base);
const url = new URL(name, etherpadBaseUrl);
APP.store.dispatch(setDocumentUrl(url.toString()));

1087
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,12 +17,13 @@
"readmeFilename": "README.md",
"dependencies": {
"@amplitude/react-native": "2.7.0",
"@braintree/sanitize-url": "7.0.0",
"@emotion/react": "11.10.6",
"@emotion/styled": "11.10.6",
"@giphy/js-fetch-api": "4.7.1",
"@giphy/react-components": "6.8.1",
"@giphy/react-native-sdk": "2.3.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.16/jitsi-excalidraw-0.0.16.tgz",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.17/jitsi-excalidraw-0.0.17.tgz",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rnnoise-wasm": "0.1.0",
@@ -30,7 +31,6 @@
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@microsoft/microsoft-graph-client": "3.0.1",
"@mui/material": "5.12.1",
"@mui/styles": "5.12.0",
"@react-native-async-storage/async-storage": "1.19.4",
"@react-native-community/clipboard": "1.5.1",
"@react-native-community/netinfo": "11.1.0",
@@ -48,7 +48,8 @@
"@vladmandic/human": "2.6.5",
"@vladmandic/human-models": "2.5.9",
"@xmldom/xmldom": "0.8.7",
"amplitude-js": "8.2.1",
"abab": "2.0.6",
"amplitude-js": "8.21.9",
"base64-js": "1.5.1",
"bc-css-flags": "3.0.0",
"clipboard-copy": "4.0.1",
@@ -66,7 +67,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/v1764.0.0+ba21a236/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1789.0.0+a8f8666b/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -105,7 +106,7 @@
"react-native-url-polyfill": "2.0.0",
"react-native-video": "6.0.0-alpha.11",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "118.0.0",
"react-native-webrtc": "118.0.2",
"react-native-webview": "13.5.1",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
@@ -116,7 +117,8 @@
"redux-thunk": "2.4.1",
"seamless-scroll-polyfill": "2.1.8",
"semver": "7.5.4",
"tss-react": "4.4.4",
"text-encoding": "0.7.0",
"tss-react": "4.9.4",
"util": "0.12.1",
"uuid": "8.3.2",
"wasm-check": "2.0.1",
@@ -130,8 +132,8 @@
"@babel/plugin-proposal-export-default-from": "7.22.5",
"@babel/preset-env": "7.21.5",
"@babel/preset-react": "7.16.0",
"@jitsi/eslint-config": "4.1.5",
"@types/amplitude-js": "8.16.2",
"@jitsi/eslint-config": "4.1.10",
"@types/amplitude-js": "8.16.5",
"@types/audioworklet": "0.0.29",
"@types/dom-screen-wake-lock": "1.0.1",
"@types/js-md5": "0.4.3",
@@ -162,7 +164,7 @@
"css-loader": "6.8.1",
"eslint": "8.40.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsdoc": "37.0.3",
"eslint-plugin-jsdoc": "46.2.6",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-native": "4.0.0",
"eslint-plugin-typescript-sort-keys": "2.3.0",

View File

@@ -133,7 +133,7 @@ dependencies {
if (isNewArchitectureEnabled()) {
react {
jsRootDir = file("../src/")
jsRootDir = file("../")
libraryName = "JitsiMeetReactNative"
codegenJavaPackageName = "org.jitsi.meet.sdk"
}

View File

@@ -1,44 +0,0 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = JMOngoingConferenceModule.NAME)
class JMOngoingConferenceModule
extends ReactContextBaseJavaModule {
public static final String NAME = "JMOngoingConference";
public JMOngoingConferenceModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public void launch() {
Context context = getReactApplicationContext();
Activity currentActivity = getCurrentActivity();
JitsiMeetOngoingConferenceService.launch(context, currentActivity);
}
@ReactMethod
public void abort() {
Context context = getReactApplicationContext();
JitsiMeetOngoingConferenceService.abort(context);
}
@NonNull
@Override
public String getName() {
return NAME;
}
}

View File

@@ -1,101 +0,0 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.HashMap;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
* responsible for presenting an ongoing notification when a conference is in progress.
* The service will help keep the app running while in the background.
*
* See: https://developer.android.com/guide/components/services
*/
public class JitsiMeetOngoingConferenceService extends Service {
private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
public static void launch(Context context, Activity currentActivity) {
RNOngoingNotification.createOngoingConferenceNotificationChannel(currentActivity);
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
ComponentName componentName;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
componentName = context.startForegroundService(intent);
} else {
componentName = context.startService(intent);
}
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
return;
}
if (componentName == null) {
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
Notification notification = RNOngoingNotification.buildOngoingConferenceNotification(this);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
startForeground(RNOngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -21,7 +21,6 @@ public class JitsiMeetReactNativePackage implements ReactPackage {
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new JMOngoingConferenceModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),

View File

@@ -1,98 +0,0 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.Random;
/**
* Helper class for creating the ongoing notification which is used with
* {@link JitsiMeetOngoingConferenceService}. It allows the user to easily get back to the app
* and to hangup from within the notification itself.
*/
class RNOngoingNotification {
private static final String TAG = RNOngoingNotification.class.getSimpleName();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
static void createOngoingConferenceNotificationChannel(Activity currentActivity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
if (currentActivity == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;
}
NotificationManager notificationManager
= (NotificationManager) currentActivity.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel
= notificationManager.getNotificationChannel("JitsiOngoingConferenceChannel");
if (channel != null) {
// The channel was already created, no need to do it again.
return;
}
channel = new NotificationChannel("JitsiOngoingConferenceChannel", currentActivity.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(false);
channel.enableVibration(false);
channel.setShowBadge(false);
notificationManager.createNotificationChannel(channel);
}
static Notification buildOngoingConferenceNotification(Context context) {
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "JitsiOngoingConferenceChannel");
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setOngoing(true)
.setWhen(System.currentTimeMillis())
.setUsesChronometer(true)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
return builder.build();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,80 +11,83 @@
"url": "git+https://github.com/jitsi/jitsi-meet.git"
},
"dependencies": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@react-navigation/bottom-tabs": "6.5.8",
"@react-navigation/elements": "1.3.18",
"@react-navigation/material-top-tabs": "6.6.3",
"@react-navigation/native": "6.1.7",
"@react-navigation/stack": "6.3.17",
"@xmldom/xmldom": "0.8.7",
"base64-js": "1.3.1",
"grapheme-splitter": "1.0.4",
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
"js-md5": "0.6.1",
"i18next-http-backend": "^2.2.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
"optional-require": "1.0.3",
"promise.allsettled": "1.0.4",
"punycode": "2.3.0",
"react-emoji-render": "1.2.4",
"react-i18next": "10.11.4",
"react-linkify": "1.0.0-alpha",
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
"react-native-svg-transformer": "1.1.0",
"react-native-tab-view": "3.5.2",
"react-native-url-polyfill": "2.0.0",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
"redux": "4.0.4",
"redux-thunk": "2.4.1",
"unorm": "1.6.0",
"util": "0.12.1",
"uuid": "8.3.2",
"zxcvbn": "4.4.2"
"@jitsi/js-utils": "0.0.0",
"@jitsi/logger": "0.0.0",
"@jitsi/rtcstats": "0.0.0",
"@react-navigation/bottom-tabs": "0.0.0",
"@react-navigation/elements": "0.0.0",
"@react-navigation/material-top-tabs": "0.0.0",
"@react-navigation/native": "0.0.0",
"@react-navigation/stack": "0.0.0",
"@xmldom/xmldom": "0.0.0",
"base64-js": "0.0.0",
"grapheme-splitter": "0.0.0",
"i18n-iso-countries": "0.0.0",
"i18next": "0.0.0",
"js-md5": "0.0.0",
"i18next-http-backend": "0.0.0",
"js-sha512": "0.0.0",
"jwt-decode": "0.0.0",
"lib-jitsi-meet": "0.0.0",
"lodash": "0.0.0",
"moment": "0.0.0",
"moment-duration-format": "0.0.0",
"optional-require": "0.0.0",
"promise.allsettled": "0.0.0",
"punycode": "0.0.0",
"react-emoji-render": "0.0.0",
"react-i18next": "0.0.0",
"react-linkify": "0.0.0",
"react-native-dialog": "0.0.0",
"react-native-svg-transformer": "0.0.0",
"react-native-tab-view": "0.0.0",
"react-native-url-polyfill": "0.0.0",
"react-native-youtube-iframe": "0.0.0",
"react-redux": "0.0.0",
"redux": "0.0.0",
"redux-thunk": "0.0.0",
"unorm": "0.0.0",
"util": "0.0.0",
"uuid": "0.0.0",
"zxcvbn": "0.0.0"
},
"peerDependencies": {
"@amplitude/react-native": "2.7.0",
"@giphy/react-native-sdk": "2.3.0",
"@react-native-async-storage/async-storage": "1.19.3",
"@react-native-community/clipboard": "1.5.1",
"@react-native-community/netinfo": "9.4.1",
"@react-native-community/slider": "4.4.3",
"@react-native-google-signin/google-signin": "10.0.1",
"@amplitude/react-native": "0.0.0",
"@braintree/sanitize-url": "0.0.0",
"@giphy/react-native-sdk": "0.0.0",
"@react-native/metro-config": "0.0.0",
"@react-native-async-storage/async-storage": "0.0.0",
"@react-native-community/clipboard": "0.0.0",
"@react-native-community/netinfo": "0.0.0",
"@react-native-community/slider": "0.0.0",
"@react-native-google-signin/google-signin": "0.0.0",
"react-native": "*",
"react": "*",
"react-native-background-timer": "2.4.1",
"react-native-calendar-events": "2.2.0",
"react-native-default-preference": "1.4.4",
"react-native-device-info": "10.9.0",
"react-native-get-random-values": "1.9.0",
"react-native-gesture-handler": "2.9.0",
"react-native-immersive-mode": "2.0.1",
"react-native-keep-awake": "4.0.0",
"react-native-pager-view": "6.2.0",
"react-native-paper": "5.10.3",
"react-native-performance": "5.0.0",
"react-native-orientation-locker": "1.5.0",
"react-native-safe-area-context": "4.7.1",
"react-native-screens": "3.24.0",
"react-native-sound": "0.11.2",
"react-native-splash-screen": "3.3.0",
"react-native-svg": "13.13.0",
"react-native-video": "6.0.0-alpha.7",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "111.0.3",
"react-native-webview": "13.5.1"
"react-native-background-timer": "0.0.0",
"react-native-calendar-events": "0.0.0",
"react-native-default-preference": "0.0.0",
"react-native-device-info": "0.0.0",
"react-native-get-random-values": "0.0.0",
"react-native-gesture-handler": "0.0.0",
"react-native-immersive-mode": "0.0.0",
"react-native-keep-awake": "0.0.0",
"react-native-pager-view": "0.0.0",
"react-native-paper": "0.0.0",
"react-native-performance": "0.0.0",
"react-native-orientation-locker": "0.0.0",
"react-native-safe-area-context": "0.0.0",
"react-native-screens": "0.0.0",
"react-native-sound": "0.0.0",
"react-native-splash-screen": "0.0.0",
"react-native-svg": "0.0.0",
"react-native-video": "0.0.0",
"react-native-watch-connectivity": "0.0.0",
"react-native-webrtc": "0.0.0",
"react-native-webview": "0.0.0",
"text-encoding": "0.0.0"
},
"overrides": {
"@xmldom/xmldom": "0.8.7"
"@xmldom/xmldom": "0.0.0"
},
"scripts": {
"postinstall": "node sdk_instructions.js",

View File

@@ -79,6 +79,13 @@ function mergeDependencyVersions() {
}
}
// Updates SDK overrides dependencies.
for (const key in packageJSON.overrides) {
if (SDKPackageJSON.overrides.hasOwnProperty(key)) {
SDKPackageJSON.overrides[key] = packageJSON.overrides[key];
}
}
const data = JSON.stringify(SDKPackageJSON, null, 4);
fs.writeFileSync('package.json', data);

View File

@@ -328,6 +328,22 @@ export function createNetworkInfoEvent({ isOnline, networkType, details }:
};
}
/**
* Creates a "not allowed error" event.
*
* @param {string} reason - The reason for the error.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createNotAllowedErrorEvent(reason: string) {
return {
action: 'not.allowed.error',
attributes: {
reason
}
};
}
/**
* Creates an "offer/answer failure" event.
*

View File

@@ -14,7 +14,7 @@ export default class AmplitudeHandler extends AbstractHandler {
/**
* Creates new instance of the Amplitude analytics handler.
*
* @param {Object} options -
* @param {Object} options - The amplitude options.
* @param {string} options.amplitudeAPPKey - The Amplitude app key required by the Amplitude API.
* @param {boolean} options.amplitudeIncludeUTM - Whether to include UTM parameters
* in the Amplitude events.
@@ -35,6 +35,12 @@ export default class AmplitudeHandler extends AbstractHandler {
this._enabled = false;
};
// Forces sending all events on exit (flushing) via sendBeacon
const onExitPage = () => {
// @ts-ignore
amplitude.getInstance().sendEvents();
};
if (navigator.product === 'ReactNative') {
amplitude.getInstance().init(amplitudeAPPKey);
fixDeviceID(amplitude.getInstance()).then(() => {
@@ -50,7 +56,8 @@ export default class AmplitudeHandler extends AbstractHandler {
includeReferrer: true,
includeUtm,
saveParamsReferrerOncePerSession: false,
onError
onError,
onExitPage
};
// @ts-ignore

View File

@@ -14,7 +14,7 @@ class GoogleAnalyticsHandler extends AbstractHandler {
/**
* Creates new instance of the GA analytics handler.
*
* @param {Object} options -
* @param {Object} options - The Google Analytics options.
* @param {string} options.googleAnalyticsTrackingId - The GA track id
* required by the GA API.
*/
@@ -34,7 +34,7 @@ class GoogleAnalyticsHandler extends AbstractHandler {
/**
* Initializes the ga object.
*
* @param {Object} options -
* @param {Object} options - The Google Analytics options.
* @param {string} options.googleAnalyticsTrackingId - The GA track id
* required by the GA API.
* @returns {void}

View File

@@ -13,7 +13,7 @@ export default class MatomoHandler extends AbstractHandler {
/**
* Creates new instance of the Matomo handler.
*
* @param {Object} options -
* @param {Object} options - The matomo options.
* @param {string} options.matomoEndpoint - The Matomo endpoint.
* @param {string} options.matomoSiteID - The site ID.
*/
@@ -39,7 +39,7 @@ export default class MatomoHandler extends AbstractHandler {
/**
* Initializes the _paq object.
*
* @param {Object} options -
* @param {Object} options - The matomo options.
* @param {string} options.matomoEndpoint - The Matomo endpoint.
* @param {string} options.matomoSiteID - The site ID.
* @returns {void}

View File

@@ -3,6 +3,7 @@ import '../authentication/middleware';
import '../av-moderation/middleware';
import '../base/conference/middleware';
import '../base/config/middleware';
import '../base/i18n/middleware';
import '../base/jwt/middleware';
import '../base/known-domains/middleware';
import '../base/lastn/middleware';
@@ -51,5 +52,6 @@ import '../video-layout/middleware';
import '../video-quality/middleware';
import '../videosipgw/middleware';
import '../visitors/middleware';
import '../whiteboard/middleware.any';
import './middleware';

View File

@@ -13,5 +13,6 @@ import '../mobile/react-native-sdk/middleware';
import '../mobile/watchos/middleware';
import '../share-room/middleware';
import '../shared-video/middleware';
import '../whiteboard/middleware.native';
import './middlewares.any';

View File

@@ -1,6 +1,5 @@
import '../base/app/middleware';
import '../base/connection/middleware';
import '../base/i18n/middleware';
import '../base/devices/middleware';
import '../base/media/middleware';
import '../dynamic-branding/middleware';
@@ -22,6 +21,6 @@ import '../talk-while-muted/middleware';
import '../toolbox/middleware';
import '../face-landmarks/middleware';
import '../gifs/middleware';
import '../whiteboard/middleware';
import '../whiteboard/middleware.web';
import './middlewares.any';

View File

@@ -55,3 +55,4 @@ import '../video-layout/reducer';
import '../video-quality/reducer';
import '../videosipgw/reducer';
import '../visitors/reducer';
import '../whiteboard/reducer';

View File

@@ -16,7 +16,6 @@ import '../noise-suppression/reducer';
import '../screenshot-capture/reducer';
import '../talk-while-muted/reducer';
import '../virtual-background/reducer';
import '../whiteboard/reducer';
import '../web-hid/reducer';
import './reducers.any';

View File

@@ -133,7 +133,7 @@ function _upgradeRoleStarted(thenableWithCancel: Object) {
* Hides an authentication dialog where the local participant
* should authenticate.
*
* @returns {Function}.
* @returns {Function}
*/
export function hideLoginDialog() {
return hideDialog(LoginDialog);

View File

@@ -18,7 +18,7 @@ interface IProps {
* Implements the dialog that warns the user that the login will leave the conference.
*
* @param {Object} props - The props of the component.
* @returns {React$Element}.
* @returns {React$Element}
*/
const LoginQuestionDialog = ({ handler }: IProps) => {
const { t } = useTranslation();

View File

@@ -178,6 +178,18 @@ export const DATA_CHANNEL_OPENED = 'DATA_CHANNEL_OPENED';
*/
export const DATA_CHANNEL_CLOSED = 'DATA_CHANNEL_CLOSED';
/**
* The type of (redux) action which indicates that an endpoint message
* sent by another participant to the data channel is received.
*
* {
* type: ENDPOINT_MESSAGE_RECEIVED,
* participant: Object,
* data: Object
* }
*/
export const ENDPOINT_MESSAGE_RECEIVED = 'ENDPOINT_MESSAGE_RECEIVED';
/**
* The type of action which signals that the user has been kicked out from
* the conference.
@@ -333,3 +345,13 @@ export const SET_START_MUTED_POLICY = 'SET_START_MUTED_POLICY';
* }
*/
export const SET_ASSUMED_BANDWIDTH_BPS = 'SET_ASSUMED_BANDWIDTH_BPS';
/**
* The type of (redux) action which updated the conference metadata.
*
* {
* type: UPDATE_CONFERENCE_METADATA,
* metadata: Object
* }
*/
export const UPDATE_CONFERENCE_METADATA = 'UPDATE_CONFERENCE_METADATA';

View File

@@ -1,7 +1,6 @@
import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { IReduxState, IStore } from '../../app/types';
import { endpointMessageReceived } from '../../subtitles/actions.any';
import { setIAmVisitor } from '../../visitors/actions';
import { iAmVisitor } from '../../visitors/functions';
import { overwriteConfig } from '../config/actions';
@@ -9,8 +8,15 @@ import { getReplaceParticipant } from '../config/functions';
import { connect, disconnect, hangup } from '../connection/actions';
import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
import { setAudioMuted, setAudioUnmutePermissions, setVideoMuted, setVideoUnmutePermissions } from '../media/actions';
import {
gumPending,
setAudioMuted,
setAudioUnmutePermissions,
setVideoMuted,
setVideoUnmutePermissions
} from '../media/actions';
import { MEDIA_TYPE } from '../media/constants';
import { IGUMPendingState } from '../media/types';
import {
dominantSpeakerChanged,
participantKicked,
@@ -48,6 +54,7 @@ import {
DATA_CHANNEL_CLOSED,
DATA_CHANNEL_OPENED,
E2E_RTT_CHANGED,
ENDPOINT_MESSAGE_RECEIVED,
KICKED_OUT,
LOCK_STATE_CHANGED,
NON_PARTICIPANT_MESSAGE_RECEIVED,
@@ -61,7 +68,8 @@ import {
SET_PENDING_SUBJECT_CHANGE,
SET_ROOM,
SET_START_MUTED_POLICY,
SET_START_REACTIONS_MUTED
SET_START_REACTIONS_MUTED,
UPDATE_CONFERENCE_METADATA
} from './actionTypes';
import {
AVATAR_URL_COMMAND,
@@ -79,7 +87,7 @@ import {
sendLocalParticipant
} from './functions';
import logger from './logger';
import { IJitsiConference } from './reducer';
import { IConferenceMetadata, IJitsiConference } from './reducer';
/**
* Adds conference (event) listeners.
@@ -275,6 +283,21 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
})));
}
/**
* Action for updating the conference metadata.
*
* @param {IConferenceMetadata} metadata - The metadata object.
* @returns {{
* type: UPDATE_CONFERENCE_METADATA,
* metadata: IConferenceMetadata
* }}
*/
export function updateConferenceMetadata(metadata: IConferenceMetadata | null) {
return {
type: UPDATE_CONFERENCE_METADATA,
metadata
};
}
/**
* Create an action for when the end-to-end RTT against a specific remote participant has changed.
@@ -630,6 +653,25 @@ export function dataChannelClosed(code: number, reason: string) {
};
}
/**
* Signals that a participant sent an endpoint message on the data channel.
*
* @param {Object} participant - The participant details sending the message.
* @param {Object} data - The data carried by the endpoint message.
* @returns {{
* type: ENDPOINT_MESSAGE_RECEIVED,
* participant: Object,
* data: Object
* }}
*/
export function endpointMessageReceived(participant: Object, data: Object) {
return {
type: ENDPOINT_MESSAGE_RECEIVED,
participant,
data
};
}
/**
* Action to end a conference for all participants.
*
@@ -1017,6 +1059,11 @@ export function redirect(vnode: string, focusJid: string, username: string) {
.then(() => dispatch(conferenceWillInit()))
.then(() => dispatch(connect()))
.then(() => {
// Clear the gum pending state in case we have set it to pending since we are starting the
// conference without tracks.
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
if (typeof APP !== 'undefined') {
APP.conference.startConference([]);

View File

@@ -6,6 +6,7 @@ import { MIN_ASSUMED_BANDWIDTH_BPS } from '../../../../modules/API/constants';
import {
ACTION_PINNED,
ACTION_UNPINNED,
createNotAllowedErrorEvent,
createOfferAnswerFailedEvent,
createPinnedEvent
} from '../../analytics/AnalyticsEvents';
@@ -25,7 +26,7 @@ import { overwriteConfig } from '../config/actions';
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../connection/actionTypes';
import { connect, connectionDisconnected, disconnect } from '../connection/actions';
import { validateJwt } from '../jwt/functions';
import { JitsiConferenceErrors, JitsiConnectionErrors } from '../lib-jitsi-meet';
import { JitsiConferenceErrors, JitsiConferenceEvents, JitsiConnectionErrors } from '../lib-jitsi-meet';
import { PARTICIPANT_UPDATED, PIN_PARTICIPANT } from '../participants/actionTypes';
import { PARTICIPANT_ROLE } from '../participants/constants';
import {
@@ -34,6 +35,7 @@ import {
getPinnedParticipant
} from '../participants/functions';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import StateListenerRegistry from '../redux/StateListenerRegistry';
import { TRACK_ADDED, TRACK_REMOVED } from '../tracks/actionTypes';
import { getLocalTracks } from '../tracks/functions.any';
@@ -54,7 +56,8 @@ import {
conferenceWillLeave,
createConference,
setLocalSubject,
setSubject
setSubject,
updateConferenceMetadata
} from './actions';
import { CONFERENCE_LEAVE_REASONS } from './constants';
import {
@@ -65,6 +68,7 @@ import {
restoreConferenceOptions
} from './functions';
import logger from './logger';
import { IConferenceMetadata } from './reducer';
/**
* Handler for before unload event.
@@ -124,6 +128,24 @@ MiddlewareRegistry.register(store => next => action => {
return next(action);
});
/**
* Set up state change listener to perform maintenance tasks when the conference
* is left or failed.
*/
StateListenerRegistry.register(
state => getCurrentConference(state),
(conference, { dispatch }, previousConference): void => {
if (conference && !previousConference) {
conference.on(JitsiConferenceEvents.METADATA_UPDATED, (metadata: IConferenceMetadata) => {
dispatch(updateConferenceMetadata(metadata));
});
}
if (conference !== previousConference) {
dispatch(updateConferenceMetadata(null));
}
});
/**
* Makes sure to leave a failed conference in order to release any allocated
* resources like peer connections, emit participant left events, etc.
@@ -197,6 +219,12 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
break;
}
case JitsiConferenceErrors.NOT_ALLOWED_ERROR: {
const [ msg ] = error.params;
sendAnalytics(createNotAllowedErrorEvent(msg));
break;
}
case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
sendAnalytics(createOfferAnswerFailedEvent());
break;
@@ -372,7 +400,7 @@ function _connectionFailed({ dispatch, getState }: IStore, next: Function, actio
descriptionKey: errors ? 'dialog.tokenAuthFailedWithReasons' : 'dialog.tokenAuthFailed',
descriptionArguments: { reason: errors },
titleKey: 'dialog.tokenAuthFailedTitle'
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}
}

View File

@@ -27,7 +27,8 @@ import {
SET_PENDING_SUBJECT_CHANGE,
SET_ROOM,
SET_START_MUTED_POLICY,
SET_START_REACTIONS_MUTED
SET_START_REACTIONS_MUTED,
UPDATE_CONFERENCE_METADATA
} from './actionTypes';
import { isRoomValid } from './functions';
@@ -39,10 +40,23 @@ const DEFAULT_STATE = {
leaving: undefined,
locked: undefined,
membersOnly: undefined,
metadata: undefined,
password: undefined,
passwordRequired: undefined
};
export interface IConferenceMetadata {
recording?: {
isTranscribingEnabled: boolean;
};
whiteboard?: {
collabDetails: {
roomId: string;
roomKey: string;
};
};
}
export interface IJitsiConference {
addCommandListener: Function;
addLobbyMessageListener: Function;
@@ -141,6 +155,7 @@ export interface IConferenceState {
localSubject?: string;
locked?: string;
membersOnly?: IJitsiConference;
metadata?: IConferenceMetadata;
obfuscatedRoom?: string;
obfuscatedRoomSource?: string;
p2p?: Object;
@@ -247,6 +262,12 @@ ReducerRegistry.register<IConferenceState>('features/base/conference',
startAudioMutedPolicy: action.startAudioMutedPolicy,
startVideoMutedPolicy: action.startVideoMutedPolicy
};
case UPDATE_CONFERENCE_METADATA:
return {
...state,
metadata: action.metadata
};
}
return state;

View File

@@ -1,40 +1,4 @@
export type ToolbarButton = 'camera' |
'chat' |
'closedcaptions' |
'desktop' |
'download' |
'embedmeeting' |
'etherpad' |
'feedback' |
'filmstrip' |
'fullscreen' |
'hangup' |
'help' |
'highlight' |
'invite' |
'linktosalesforce' |
'livestreaming' |
'microphone' |
'mute-everyone' |
'mute-video-everyone' |
'noisesuppression' |
'participants-pane' |
'profile' |
'raisehand' |
'reactions' |
'recording' |
'security' |
'select-background' |
'settings' |
'shareaudio' |
'sharedvideo' |
'shortcuts' |
'stats' |
'tileview' |
'toggle-camera' |
'videoquality' |
'whiteboard' |
'__end';
import { ToolbarButton } from '../../toolbox/types';
type ButtonsWithNotifyClick = 'camera' |
'chat' |
@@ -503,6 +467,7 @@ export interface IConfig {
preventExecution: boolean;
}>;
participantsPane?: {
enabled?: boolean;
hideModeratorSettingsTab?: boolean;
hideMoreActionsButton?: boolean;
hideMuteAllButton?: boolean;
@@ -530,7 +495,12 @@ export interface IConfig {
sharingEnabled?: boolean;
};
recordingSharingUrl?: string;
recordings?: {
showPrejoinWarning?: boolean;
suggestRecording?: boolean;
};
remoteVideoMenu?: {
disableDemote?: boolean;
disableGrantModerator?: boolean;
disableKick?: boolean;
disablePrivateChat?: boolean;
@@ -570,6 +540,7 @@ export interface IConfig {
testing?: {
assumeBandwidth?: boolean;
disableE2EE?: boolean;
dumpTranscript?: boolean;
mobileXmppWsThreshold?: number;
noAutoPlayVideo?: boolean;
p2pTestMode?: boolean;

View File

@@ -201,6 +201,7 @@ export default [
'remoteVideoMenu',
'roomPasswordNumberOfDigits',
'readOnlyName',
'recordings',
'replaceParticipant',
'resolution',
'salesforceUrl',

View File

@@ -1,5 +1,3 @@
import { ToolbarButton } from './configType';
/**
* The prefix of the {@code localStorage} key into which {@link storeConfig}
* stores and from which {@link restoreConfig} restores.
@@ -9,50 +7,6 @@ import { ToolbarButton } from './configType';
*/
export const _CONFIG_STORE_PREFIX = 'config.js';
/**
* The list of all possible UI buttons.
*
* @protected
* @type Array<string>
*/
export const TOOLBAR_BUTTONS: ToolbarButton[] = [
'camera',
'chat',
'closedcaptions',
'desktop',
'download',
'embedmeeting',
'etherpad',
'feedback',
'filmstrip',
'fullscreen',
'hangup',
'help',
'highlight',
'invite',
'linktosalesforce',
'livestreaming',
'microphone',
'mute-everyone',
'mute-video-everyone',
'participants-pane',
'profile',
'raisehand',
'recording',
'security',
'select-background',
'settings',
'shareaudio',
'noisesuppression',
'sharedvideo',
'shortcuts',
'stats',
'tileview',
'toggle-camera',
'videoquality',
'whiteboard'
];
/**
* The toolbar buttons to show on premeeting screens.
*/
@@ -63,11 +17,6 @@ export const PREMEETING_BUTTONS = [ 'microphone', 'camera', 'select-background',
*/
export const THIRD_PARTY_PREJOIN_BUTTONS = [ 'microphone', 'camera', 'select-background' ];
/**
* The toolbar buttons to show when in visitors mode.
*/
export const VISITORS_MODE_BUTTONS = [ 'chat', 'hangup', 'raisehand', 'settings', 'tileview' ];
/**
* The set of feature flags.
*

View File

@@ -65,7 +65,7 @@ export function getMeetingRegion(state: IReduxState) {
* @returns {boolean}
*/
export function getSsrcRewritingFeatureFlag(state: IReduxState) {
return getFeatureFlag(state, FEATURE_FLAGS.SSRC_REWRITING);
return getFeatureFlag(state, FEATURE_FLAGS.SSRC_REWRITING) ?? true;
}
/**

View File

@@ -7,10 +7,8 @@ import {
IDeeplinkingConfig,
IDeeplinkingDesktopConfig,
IDeeplinkingMobileConfig,
NotifyClickButton,
ToolbarButton
NotifyClickButton
} from './configType';
import { TOOLBAR_BUTTONS } from './constants';
export * from './functions.any';
@@ -34,25 +32,6 @@ export function getReplaceParticipant(state: IReduxState): string | undefined {
return state['features/base/config'].replaceParticipant;
}
/**
* Returns the list of enabled toolbar buttons.
*
* @param {Object} state - The redux state.
* @returns {Array<string>} - The list of enabled toolbar buttons.
*/
export function getToolbarButtons(state: IReduxState): Array<string> {
const { toolbarButtons, customToolbarButtons } = state['features/base/config'];
const customButtons = customToolbarButtons?.map(({ id }) => id);
const buttons = Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
if (customButtons) {
buttons.push(...customButtons as ToolbarButton[]);
}
return buttons;
}
/**
* Returns the configuration value of web-hid feature.
*
@@ -63,20 +42,6 @@ export function getWebHIDFeatureConfig(state: IReduxState): boolean {
return state['features/base/config'].enableWebHIDFeature || false;
}
/**
* Checks if the specified button is enabled.
*
* @param {string} buttonName - The name of the button.
* {@link interfaceConfig}.
* @param {Object|Array<string>} state - The redux state or the array with the enabled buttons.
* @returns {boolean} - True if the button is enabled and false otherwise.
*/
export function isToolbarButtonEnabled(buttonName: string, state: IReduxState | Array<string>) {
const buttons = Array.isArray(state) ? state : getToolbarButtons(state);
return buttons.includes(buttonName);
}
/**
* Returns whether audio level measurement is enabled or not.
*

View File

@@ -1,6 +1,8 @@
import _ from 'lodash';
import { CONFERENCE_INFO } from '../../conference/components/constants';
import { TOOLBAR_BUTTONS } from '../../toolbox/constants';
import { ToolbarButton } from '../../toolbox/types';
import ReducerRegistry from '../redux/ReducerRegistry';
import { equals } from '../redux/functions';
@@ -16,10 +18,8 @@ import {
IDeeplinkingConfig,
IDeeplinkingDesktopConfig,
IDeeplinkingMobileConfig,
IMobileDynamicLink,
ToolbarButton
IMobileDynamicLink
} from './configType';
import { TOOLBAR_BUTTONS } from './constants';
import { _cleanupConfig, _setDeeplinkingDefaults } from './functions';
/**

View File

@@ -52,6 +52,16 @@ export const CONNECTION_WILL_CONNECT = 'CONNECTION_WILL_CONNECT';
*/
export const SET_LOCATION_URL = 'SET_LOCATION_URL';
/**
* The type of (redux) action which sets the preferVisitor in store.
*
* {
* type: SET_PREFER_VISITOR,
* preferVisitor: ?boolean
* }
*/
export const SET_PREFER_VISITOR = 'SET_PREFER_VISITOR';
/**
* The type of (redux) action which tells whether connection info should be displayed
* on context menu.

View File

@@ -16,7 +16,8 @@ import {
CONNECTION_ESTABLISHED,
CONNECTION_FAILED,
CONNECTION_WILL_CONNECT,
SET_LOCATION_URL
SET_LOCATION_URL,
SET_PREFER_VISITOR
} from './actionTypes';
import { JITSI_CONNECTION_URL_KEY } from './constants';
import logger from './logger';
@@ -180,6 +181,22 @@ export function setLocationURL(locationURL?: URL) {
};
}
/**
* To change prefer visitor in the store. Used later to decide what to request from jicofo on connection.
*
* @param {boolean} preferVisitor - The value to set.
* @returns {{
* type: SET_PREFER_VISITOR,
* preferVisitor: boolean
* }}
*/
export function setPreferVisitor(preferVisitor: boolean) {
return {
type: SET_PREFER_VISITOR,
preferVisitor
};
}
/**
* Opens new connection.
*

View File

@@ -10,6 +10,7 @@ import {
CONNECTION_FAILED,
CONNECTION_WILL_CONNECT,
SET_LOCATION_URL,
SET_PREFER_VISITOR,
SHOW_CONNECTION_INFO
} from './actionTypes';
import { ConnectionFailedError } from './types';
@@ -57,6 +58,11 @@ ReducerRegistry.register<IConnectionState>(
case SET_LOCATION_URL:
return _setLocationURL(state, action);
case SET_PREFER_VISITOR:
return assign(state, {
preferVisitor: action.preferVisitor
});
case SET_ROOM:
return _setRoom(state);

View File

@@ -164,6 +164,12 @@ export const NOTIFICATIONS_ENABLED = 'notifications.enabled';
*/
export const OVERFLOW_MENU_ENABLED = 'overflow-menu.enabled';
/**
* Flag indicating if participants should be enabled.
* Default: enabled (true).
*/
export const PARTICIPANTS_ENABLED = 'participants.enabled';
/**
* Flag indicating if Picture-in-Picture should be enabled.
* Default: auto-detected.

View File

@@ -8,7 +8,7 @@ import { toState } from '../../redux/functions';
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state
* features/base/config.
* @returns {number}.
* @returns {number}
*/
export function getClientWidth(stateful: IStateful) {
const state = toState(stateful)['features/base/responsive-ui'];
@@ -23,7 +23,7 @@ export function getClientWidth(stateful: IStateful) {
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state
* features/base/config.
* @returns {number}.
* @returns {number}
*/
export function getClientHeight(stateful: IStateful) {
const state = toState(stateful)['features/base/responsive-ui'];

View File

@@ -5,14 +5,16 @@ 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 Toolbox from '../../../../toolbox/components/web/Toolbox';
import { isButtonEnabled } from '../../../../toolbox/functions.web';
import { getConferenceName } from '../../../conference/functions';
import { PREMEETING_BUTTONS, THIRD_PARTY_PREJOIN_BUTTONS } from '../../../config/constants';
import { getToolbarButtons, isToolbarButtonEnabled } from '../../../config/functions.web';
import { withPixelLineHeight } from '../../../styles/functions.web';
import ConnectionStatus from './ConnectionStatus';
import Preview from './Preview';
import RecordingWarning from './RecordingWarning';
import UnsafeRoomWarning from './UnsafeRoomWarning';
interface IProps {
@@ -57,6 +59,11 @@ interface IProps {
*/
showDeviceStatus: boolean;
/**
* Indicates whether to display the recording warning.
*/
showRecordingWarning?: boolean;
/**
* If should show unsafe room warning when joining.
*/
@@ -167,6 +174,7 @@ const PreMeetingScreen = ({
children,
className,
showDeviceStatus,
showRecordingWarning,
showUnsafeRoomWarning,
skipPrejoinButton,
title,
@@ -200,6 +208,7 @@ const PreMeetingScreen = ({
{skipPrejoinButton}
{showUnsafeRoomWarning && <UnsafeRoomWarning />}
{showDeviceStatus && <DeviceStatus />}
{showRecordingWarning && <RecordingWarning />}
</div>
</div>
</div>
@@ -219,8 +228,8 @@ const PreMeetingScreen = ({
* @returns {Object}
*/
function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
const { hiddenPremeetingButtons, hideConferenceSubject } = state['features/base/config'];
const toolbarButtons = getToolbarButtons(state);
const { hiddenPremeetingButtons } = state['features/base/config'];
const { toolbarButtons } = state['features/toolbox'];
const premeetingButtons = (ownProps.thirdParty
? THIRD_PARTY_PREJOIN_BUTTONS
: PREMEETING_BUTTONS).filter((b: any) => !(hiddenPremeetingButtons || []).includes(b));
@@ -235,9 +244,9 @@ function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
// toolbarButtons config overwrite.
_buttons: hiddenPremeetingButtons
? premeetingButtons
: premeetingButtons.filter(b => isToolbarButtonEnabled(b, toolbarButtons)),
: premeetingButtons.filter(b => isButtonEnabled(b, toolbarButtons)),
_premeetingBackground: premeetingBackground,
_roomName: (hideConferenceSubject ? undefined : getConferenceName(state)) ?? ''
_roomName: isRoomNameEnabled(state) ? getConferenceName(state) : ''
};
}

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { makeStyles } from 'tss-react/mui';
import { withPixelLineHeight } from '../../../styles/functions.web';
const useStyles = makeStyles()(theme => {
return {
warning: {
bottom: 0,
color: theme.palette.text03,
display: 'flex',
justifyContent: 'center',
...withPixelLineHeight(theme.typography.bodyShortRegular),
marginBottom: theme.spacing(3),
marginTop: theme.spacing(2),
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3),
position: 'absolute',
width: '100%',
'@media (max-width: 720px)': {
position: 'relative'
}
}
};
});
const RecordingWarning = () => {
const { t } = useTranslation();
const { classes } = useStyles();
return (
<div className = { classes.warning }>
{t('prejoin.recordingWarning')}
</div>
);
};
export default RecordingWarning;

View File

@@ -1,9 +1,9 @@
import { NativeModules, Platform } from 'react-native';
import { IReduxState, IStore } from '../../app/types';
import { setPictureInPictureEnabled } from '../../mobile/picture-in-picture/functions';
import { showNotification } from '../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
import { PIP_WHILE_SCREEN_SHARING_ENABLED } from '../flags/constants';
import { getFeatureFlag } from '../flags/functions';
import JitsiMeetJS from '../lib-jitsi-meet';
import {
setScreenshareMuted,
@@ -14,7 +14,6 @@ import { VIDEO_MUTISM_AUTHORITY } from '../media/constants';
import { addLocalTrack, replaceLocalTrack } from './actions.any';
import { getLocalDesktopTrack, getTrackState, isLocalVideoTrackDesktop } from './functions.native';
const { JitsiMeetMediaProjectionModule } = NativeModules;
export * from './actions.any';
@@ -35,10 +34,7 @@ export function toggleScreensharing(enabled: boolean, _ignore1?: boolean, _ignor
if (!isSharing) {
_startScreenSharing(dispatch, state);
Platform.OS === 'android' && JitsiMeetMediaProjectionModule.launch();
}
Platform.OS === 'android' && JitsiMeetMediaProjectionModule.abort();
} else {
dispatch(setScreenshareMuted(true));
dispatch(setVideoMuted(false, VIDEO_MUTISM_AUTHORITY.SCREEN_SHARE));
@@ -56,7 +52,11 @@ export function toggleScreensharing(enabled: boolean, _ignore1?: boolean, _ignor
* @returns {void}
*/
async function _startScreenSharing(dispatch: IStore['dispatch'], state: IReduxState) {
setPictureInPictureEnabled(false);
const pipWhileScreenSharingEnabled = getFeatureFlag(state, PIP_WHILE_SCREEN_SHARING_ENABLED, false);
if (!pipWhileScreenSharingEnabled) {
setPictureInPictureEnabled(false);
}
try {
const tracks: any[] = await JitsiMeetJS.createLocalTracks({ devices: [ 'desktop' ] });

View File

@@ -145,6 +145,24 @@ const useStyles = makeStyles()(theme => {
pointerEvents: 'none'
},
contextMenuItemIconDisabled: {
'& svg': {
fill: `${theme.palette.text03} !important`
}
},
contextMenuItemLabelDisabled: {
color: theme.palette.text03,
'&:hover': {
background: 'none'
},
'& svg': {
fill: theme.palette.text03
}
},
contextMenuItemDrawer: {
padding: '13px 16px'
},
@@ -233,13 +251,15 @@ const ContextMenuItem = ({
tabIndex = { onClick ? tabIndex : undefined }>
{customIcon ? customIcon
: icon && <Icon
className = { styles.contextMenuItemIcon }
className = { cx(styles.contextMenuItemIcon,
disabled && styles.contextMenuItemIconDisabled) }
size = { 20 }
src = { icon } />}
{text && (
<TextWithOverflow
className = { cx(styles.text,
_overflowDrawer && styles.drawerText,
disabled && styles.contextMenuItemLabelDisabled,
textClassName) }
overflowType = { overflowType } >
{text}

View File

@@ -145,7 +145,7 @@ export function setColorAlpha(color: string, opacity: number) {
/**
* Gets the hexa rgb values for a shorthand css color.
*
* @param {string} color -
* @param {string} color - The shorthand css color.
* @returns {Array<number>} - Array containing parsed r, g, b values of the color.
*/
function parseShorthandColor(color: string) {

View File

@@ -1,3 +1,5 @@
import base64js from 'base64-js';
import { timeoutPromise } from './timeoutPromise';
/**
@@ -43,3 +45,37 @@ export function doGetJSON(url: string, retry?: boolean, options?: Object) {
return fetchPromise;
}
/**
* Encodes strings to Base64URL.
*
* @param {any} data - The byte array to encode.
* @returns {string}
*/
export const encodeToBase64URL = (data: string): string => base64js
.fromByteArray(new window.TextEncoder().encode(data))
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
/**
* Decodes strings from Base64URL.
*
* @param {string} data - The byte array to decode.
* @returns {string}
*/
export const decodeFromBase64URL = (data: string): string => {
let s = data;
// Convert from Base64URL to Base64.
if (s.length % 4 === 2) {
s += '==';
} else if (s.length % 4 === 3) {
s += '=';
}
s = s.replace(/-/g, '+').replace(/_/g, '/');
// Convert Base64 to a byte array.
return new window.TextDecoder().decode(base64js.toByteArray(s));
};

View File

@@ -1,3 +1,5 @@
import { sanitizeUrl as _sanitizeUrl } from '@braintree/sanitize-url';
import { parseURLParams } from './parseURLParams';
import { normalizeNFKC } from './strings';
@@ -657,3 +659,25 @@ export function appendURLHashParam(url: string, name: string, value: string) {
return newUrl.toString();
}
/**
* Sanitizes the given URL so that it's safe to use. If it's unsafe, null is returned.
*
* @param {string|URL} url - The URL that needs to be sanitized.
*
* @returns {URL?} - The sanitized URL, or null otherwise.
*/
export function sanitizeUrl(url?: string | URL): URL | null {
if (!url) {
return null;
}
const urlStr = url.toString();
const result = _sanitizeUrl(urlStr);
if (result === 'about:blank') {
return null;
}
return new URL(result);
}

View File

@@ -6,6 +6,7 @@ import { getFeatureFlag } from '../../../base/flags/functions';
import { translate } from '../../../base/i18n/functions';
import { IconChatUnread, IconMessage } from '../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { arePollsDisabled } from '../../../conference/functions.any';
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { getUnreadPollCount } from '../../../polls/functions';
@@ -65,11 +66,10 @@ class ChatButton extends AbstractButton<IProps> {
*/
function _mapStateToProps(state: IReduxState, ownProps: any) {
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { disablePolls } = state['features/base/config'];
const { visible = enabled } = ownProps;
return {
_isPollsDisabled: disablePolls,
_isPollsDisabled: arePollsDisabled(state),
// The toggled icon should also be available for new polls
_unreadMessageCount: getUnreadCount(state) || getUnreadPollCount(state),

View File

@@ -8,6 +8,7 @@ import { IconMessage, IconReply } from '../../../base/icons/svg';
import { getParticipantById } from '../../../base/participants/functions';
import { IParticipant } from '../../../base/participants/types';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { arePollsDisabled } from '../../../conference/functions.any';
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { handleLobbyChatInitialized, openChat } from '../../actions.native';
@@ -96,11 +97,10 @@ class PrivateMessageButton extends AbstractButton<IProps, any> {
*/
export function _mapStateToProps(state: IReduxState, ownProps: any) {
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { disablePolls } = state['features/base/config'];
const { visible = enabled, isLobbyMessage, participantID } = ownProps;
return {
_isPollsDisabled: disablePolls,
_isPollsDisabled: arePollsDisabled(state),
_participant: getParticipantById(state, participantID),
_isLobbyMessage: isLobbyMessage,
visible

View File

@@ -7,6 +7,7 @@ import { translate } from '../../../base/i18n/functions';
import { getLocalParticipant } from '../../../base/participants/functions';
import { withPixelLineHeight } from '../../../base/styles/functions.web';
import Tabs from '../../../base/ui/components/web/Tabs';
import { arePollsDisabled } from '../../../conference/functions.any';
import PollsPane from '../../../polls/components/web/PollsPane';
import { sendMessage, setIsPollsTabFocused, toggleChat } from '../../actions.web';
import { CHAT_SIZE, CHAT_TABS, SMALL_WIDTH_THRESHOLD } from '../../constants';
@@ -316,12 +317,11 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { isOpen, isPollsTabFocused, messages, nbUnreadMessages } = state['features/chat'];
const { nbUnreadPolls } = state['features/polls'];
const _localParticipant = getLocalParticipant(state);
const { disablePolls } = state['features/base/config'];
return {
_isModal: window.innerWidth <= SMALL_WIDTH_THRESHOLD,
_isOpen: isOpen,
_isPollsEnabled: !disablePolls,
_isPollsEnabled: !arePollsDisabled(state),
_isPollsTabFocused: isPollsTabFocused,
_messages: messages,
_nbUnreadMessages: nbUnreadMessages,

View File

@@ -295,7 +295,7 @@ export default class MessageContainer extends AbstractMessageContainer<IProps, I
/**
* Check if a message is visible in view.
*
* @param {Element} message -
* @param {Element} message - The message.
*
* @returns {boolean}
*/

View File

@@ -2,7 +2,7 @@ import { AnyAction } from 'redux';
import { IReduxState, IStore } from '../app/types';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
import { CONFERENCE_JOINED, ENDPOINT_MESSAGE_RECEIVED } from '../base/conference/actionTypes';
import { getCurrentConference } from '../base/conference/functions';
import { IJitsiConference } from '../base/conference/reducer';
import { openDialog } from '../base/dialog/actions';
@@ -29,7 +29,6 @@ import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes';
import { pushReactions } from '../reactions/actions.any';
import { ENDPOINT_REACTION_NAME } from '../reactions/constants';
import { getReactionMessageFromBuffer, isReactionsEnabled } from '../reactions/functions.any';
import { endpointMessageReceived } from '../subtitles/actions.any';
import { showToolbox } from '../toolbox/actions';
@@ -93,14 +92,6 @@ MiddlewareRegistry.register(store => next => action => {
_addChatMsgListener(action.conference, store);
break;
case OPEN_CHAT:
unreadCount = 0;
if (typeof APP !== 'undefined') {
APP.API.notifyChatUpdated(unreadCount, true);
}
break;
case CLOSE_CHAT: {
const isPollTabOpen = getState()['features/chat'].isPollsTabFocused;
@@ -116,6 +107,38 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case ENDPOINT_MESSAGE_RECEIVED: {
const state = store.getState();
if (!isReactionsEnabled(state)) {
return;
}
const { participant, data } = action;
if (data?.name === ENDPOINT_REACTION_NAME) {
store.dispatch(pushReactions(data.reactions));
_handleReceivedMessage(store, {
id: participant.getId(),
message: getReactionMessageFromBuffer(data.reactions),
privateMessage: false,
lobbyChat: false,
timestamp: data.timestamp
}, false, true);
}
break;
}
case OPEN_CHAT:
unreadCount = 0;
if (typeof APP !== 'undefined') {
APP.API.notifyChatUpdated(unreadCount, true);
}
break;
case SET_IS_POLL_TAB_FOCUSED: {
dispatch(resetNbUnreadPollsMessages());
break;
@@ -253,35 +276,6 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
}
);
conference.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(...args: any) => {
const state = store.getState();
if (!isReactionsEnabled(state)) {
return;
}
// @ts-ignore
store.dispatch(endpointMessageReceived(...args));
if (args && args.length >= 2) {
const [ { _id }, eventData ] = args;
if (eventData.name === ENDPOINT_REACTION_NAME) {
store.dispatch(pushReactions(eventData.reactions));
_handleReceivedMessage(store, {
id: _id,
message: getReactionMessageFromBuffer(eventData.reactions),
privateMessage: false,
lobbyChat: false,
timestamp: eventData.timestamp
}, false, true);
}
}
});
conference.on(
JitsiConferenceEvents.CONFERENCE_ERROR, (errorType: string, error: Error) => {
errorType === JitsiConferenceErrors.CHAT_ERROR && _handleChatError(store, error);

View File

@@ -1,11 +1,13 @@
import React, { useCallback } from 'react';
import { TouchableOpacity } from 'react-native';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { openHighlightDialog } from '../../../recording/actions.native';
import HighlightButton from '../../../recording/components/Recording/native/HighlightButton';
import RecordingLabel from '../../../recording/components/native/RecordingLabel';
import { getActiveSession } from '../../../recording/functions';
import VisitorsCountLabel from '../../../visitors/components/native/VisitorsCountLabel';
import RaisedHandsCountLabel from './RaisedHandsCountLabel';
@@ -28,7 +30,10 @@ interface IProps {
const AlwaysOnLabels = ({ createOnPress }: IProps) => {
const dispatch = useDispatch();
const openHighlightDialogCallback = useCallback(() => dispatch(openHighlightDialog()), [ dispatch ]);
const isStreaming = useSelector((state: IReduxState) =>
Boolean(getActiveSession(state, JitsiRecordingConstants.mode.STREAM)));
const openHighlightDialogCallback = useCallback(() =>
dispatch(openHighlightDialog()), [ dispatch ]);
return (<>
<TouchableOpacity
@@ -36,11 +41,14 @@ const AlwaysOnLabels = ({ createOnPress }: IProps) => {
onPress = { createOnPress(LABEL_ID_RECORDING) } >
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />
</TouchableOpacity>
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { createOnPress(LABEL_ID_STREAMING) } >
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
</TouchableOpacity>
{
isStreaming
&& <TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { createOnPress(LABEL_ID_STREAMING) } >
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
</TouchableOpacity>
}
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { openHighlightDialogCallback }>

View File

@@ -17,7 +17,6 @@ import { IReduxState, IStore } from '../../../app/types';
import { CONFERENCE_BLURRED, CONFERENCE_FOCUSED } from '../../../base/conference/actionTypes';
import { FULLSCREEN_ENABLED, PIP_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import { getParticipantCount } from '../../../base/participants/functions';
import Container from '../../../base/react/components/native/Container';
import LoadingIndicator from '../../../base/react/components/native/LoadingIndicator';
import TintedView from '../../../base/react/components/native/TintedView';
@@ -101,11 +100,6 @@ interface IProps extends AbstractProps {
*/
_fullscreenEnabled: boolean;
/**
* The indicator which determines if the conference type is one to one.
*/
_isOneToOneConference: boolean;
/**
* The indicator which determines if the participants pane is open.
*/
@@ -370,7 +364,6 @@ class Conference extends AbstractConference<IProps, State> {
_aspectRatio,
_connecting,
_filmstripVisible,
_isOneToOneConference,
_largeVideoParticipantId,
_reducedUI,
_shouldDisplayTileView,
@@ -426,13 +419,11 @@ class Conference extends AbstractConference<IProps, State> {
<Captions onPress = { this._onClick } />
{
_shouldDisplayTileView || (
!_isOneToOneConference
&& <Container style = { styles.displayNameContainer }>
<DisplayNameLabel
participantId = { _largeVideoParticipantId } />
</Container>
)
_shouldDisplayTileView
|| <Container style = { styles.displayNameContainer }>
<DisplayNameLabel
participantId = { _largeVideoParticipantId } />
</Container>
}
{ !_shouldDisplayTileView && <LonelyMeetingExperience /> }
@@ -573,7 +564,6 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { backgroundColor } = state['features/dynamic-branding'];
const { startCarMode } = state['features/base/settings'];
const { enabled: audioOnlyEnabled } = state['features/base/audio-only'];
const participantCount = getParticipantCount(state);
const brandingStyles = backgroundColor ? {
backgroundColor
} : undefined;
@@ -587,7 +577,6 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
_connecting: isConnecting(state),
_filmstripVisible: isFilmstripVisible(state),
_fullscreenEnabled: getFeatureFlag(state, FULLSCREEN_ENABLED, true),
_isOneToOneConference: Boolean(participantCount === 2),
_isParticipantsPaneOpen: isOpen,
_largeVideoParticipantId: state['features/large-video'].participantId,
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),

View File

@@ -4,11 +4,13 @@ import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { getConferenceName, getConferenceTimestamp } from '../../../base/conference/functions';
import { CONFERENCE_TIMER_ENABLED, MEETING_NAME_ENABLED } from '../../../base/flags/constants';
import { CONFERENCE_TIMER_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import AudioDeviceToggleButton from '../../../mobile/audio-mode/components/AudioDeviceToggleButton';
import PictureInPictureButton from '../../../mobile/picture-in-picture/components/PictureInPictureButton';
import ParticipantsPaneButton from '../../../participants-pane/components/native/ParticipantsPaneButton';
import { isParticipantsPaneEnabled } from '../../../participants-pane/functions';
import { isRoomNameEnabled } from '../../../prejoin/functions';
import ToggleCameraButton from '../../../toolbox/components/native/ToggleCameraButton';
import { isToolboxVisible } from '../../../toolbox/functions.native';
import ConferenceTimer from '../ConferenceTimer';
@@ -16,6 +18,7 @@ import ConferenceTimer from '../ConferenceTimer';
import Labels from './Labels';
import styles from './styles';
interface IProps {
/**
@@ -29,15 +32,20 @@ interface IProps {
*/
_createOnPress: Function;
/**
* Whether participants feature is enabled or not.
*/
_isParticipantsPaneEnabled: boolean;
/**
* Name of the meeting we're currently in.
*/
_meetingName: string;
/**
* Whether displaying the current meeting name is enabled or not.
* Whether displaying the current room name is enabled or not.
*/
_meetingNameEnabled: boolean;
_roomNameEnabled: boolean;
/**
* True if the navigation bar should be visible.
@@ -53,7 +61,7 @@ interface IProps {
* @returns {JSX.Element}
*/
const TitleBar = (props: IProps) => {
const { _visible } = props;
const { _isParticipantsPaneEnabled, _visible } = props;
if (!_visible) {
return null;
@@ -75,7 +83,7 @@ const TitleBar = (props: IProps) => {
</View>
}
{
props._meetingNameEnabled
props._roomNameEnabled
&& <View style = { styles.roomNameView as ViewStyle }>
<Text
numberOfLines = { 1 }
@@ -93,9 +101,13 @@ const TitleBar = (props: IProps) => {
<View style = { styles.titleBarButtonContainer }>
<AudioDeviceToggleButton styles = { styles.titleBarButton } />
</View>
<View style = { styles.titleBarButtonContainer }>
<ParticipantsPaneButton styles = { styles.titleBarButton } />
</View>
{
_isParticipantsPaneEnabled
&& <View style = { styles.titleBarButtonContainer }>
<ParticipantsPaneButton
styles = { styles.titleBarButton } />
</View>
}
</View>
);
};
@@ -107,15 +119,15 @@ const TitleBar = (props: IProps) => {
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState) {
const { hideConferenceTimer, hideConferenceSubject } = state['features/base/config'];
const { hideConferenceTimer } = state['features/base/config'];
const startTimestamp = getConferenceTimestamp(state);
return {
_conferenceTimerEnabled:
Boolean(getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true) && !hideConferenceTimer && startTimestamp),
_isParticipantsPaneEnabled: isParticipantsPaneEnabled(state),
_meetingName: getConferenceName(state),
_meetingNameEnabled:
getFeatureFlag(state, MEETING_NAME_ENABLED, true) && !hideConferenceSubject,
_roomNameEnabled: isRoomNameEnabled(state),
_visible: isToolboxVisible(state)
};
}

View File

@@ -8,7 +8,6 @@ import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
* React component for Audio icon.
*
* @returns {JSX.Element} - The Audio icon.
*
*/
const AudioIcon = (): JSX.Element => (<Icon
color = { BaseTheme.palette.ui02 }

View File

@@ -23,6 +23,7 @@ import { getOverlayToRender } from '../../../overlay/functions.web';
import ParticipantsPane from '../../../participants-pane/components/web/ParticipantsPane';
import Prejoin from '../../../prejoin/components/web/Prejoin';
import { isPrejoinPageVisible } from '../../../prejoin/functions';
import ReactionAnimations from '../../../reactions/components/web/ReactionsAnimations';
import { toggleToolboxVisible } from '../../../toolbox/actions.any';
import { fullScreenChanged, showToolbox } from '../../../toolbox/actions.web';
import JitsiPortal from '../../../toolbox/components/web/JitsiPortal';
@@ -260,6 +261,7 @@ class Conference extends AbstractConference<IProps, any> {
{ _showLobby && <LobbyScreen />}
</div>
<ParticipantsPane />
<ReactionAnimations />
</div>
);
}

View File

@@ -1,5 +1,6 @@
import { IStateful } from '../base/app/types';
import { toState } from '../base/redux/functions';
import { iAmVisitor } from '../visitors/functions';
/**
@@ -15,3 +16,19 @@ export function shouldDisplayNotifications(stateful: IStateful) {
return !calleeInfoVisible;
}
/**
*
* Returns true if polls feature is disabled.
*
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state
* features/base/config.
* @returns {boolean}
*/
export function arePollsDisabled(stateful: IStateful) {
const state = toState(stateful);
return state['features/base/config']?.disablePolls || iAmVisitor(state);
}

View File

@@ -1,21 +1 @@
import { IStateful } from '../base/app/types';
import { toState } from '../base/redux/functions';
export * from './functions.any';
/**
*
* Returns true if polls feature is disabled.
*
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state
* features/base/config.
* @returns {boolean}.
*/
export function getDisablePolls(stateful: IStateful) {
const state = toState(stateful)['features/base/config'];
return state.disablePolls;
}

View File

@@ -35,6 +35,7 @@ import {
NOTIFICATION_ICON,
NOTIFICATION_TIMEOUT_TYPE
} from '../notifications/constants';
import { showStartRecordingNotification } from '../recording/actions';
import { showSalesforceNotification } from '../salesforce/actions';
import { setToolboxEnabled } from '../toolbox/actions.any';
@@ -149,6 +150,8 @@ function _conferenceJoined({ dispatch, getState }: IStore) {
}
dispatch(showSalesforceNotification());
dispatch(showStartRecordingNotification());
_checkIframe(getState(), dispatch);
}

View File

@@ -1,10 +1,7 @@
import i18next from 'i18next';
import { CONFERENCE_JOINED, KICKED_OUT } from '../base/conference/actionTypes';
import { IJitsiConference } from '../base/conference/reducer';
import { ENDPOINT_MESSAGE_RECEIVED, KICKED_OUT } from '../base/conference/actionTypes';
import { hangup } from '../base/connection/actions.web';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { getParticipantDisplayName } from '../base/participants/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { openAllowToggleCameraDialog, setCameraFacingMode } from '../base/tracks/actions.web';
@@ -14,8 +11,15 @@ import './middleware.any';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONFERENCE_JOINED: {
_addSetCameraFacingModeListener(action.conference);
case ENDPOINT_MESSAGE_RECEIVED: {
const { participant, data } = action;
if (data?.name === CAMERA_FACING_MODE_MESSAGE) {
APP.store.dispatch(openAllowToggleCameraDialog(
/* onAllow */ () => APP.store.dispatch(setCameraFacingMode(data.facingMode)),
/* initiatorId */ participant.getId()
));
}
break;
}
@@ -34,28 +38,3 @@ MiddlewareRegistry.register(store => next => action => {
return next(action);
});
/**
* Registers listener for {@link JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED} that
* will perform various chat related activities.
*
* @param {IJitsiConference} conference - The conference.
* @returns {void}
*/
function _addSetCameraFacingModeListener(conference: IJitsiConference) {
conference.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(...args: any) => {
if (args && args.length >= 2) {
const [ sender, eventData ] = args;
if (eventData.name === CAMERA_FACING_MODE_MESSAGE) {
APP.store.dispatch(openAllowToggleCameraDialog(
/* onAllow */ () => APP.store.dispatch(setCameraFacingMode(eventData.facingMode)),
/* initiatorId */ sender._id
));
}
}
}
);
}

View File

@@ -1,9 +1,9 @@
import { Theme } from '@mui/material';
import { withStyles } from '@mui/styles';
import clsx from 'clsx';
import React from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { withStyles } from 'tss-react/mui';
import { IReduxState, IStore } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
@@ -114,7 +114,7 @@ interface IProps extends AbstractProps, WithTranslation {
/**
* An object containing the CSS classes.
*/
classes: any;
classes?: Partial<Record<keyof ReturnType<typeof styles>, string>>;
/**
* The Redux dispatch function.
@@ -225,7 +225,8 @@ class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
* @returns {ReactElement}
*/
render() {
const { enableStatsDisplay, participantId, statsPopoverPosition, classes, t } = this.props;
const { enableStatsDisplay, participantId, statsPopoverPosition, t } = this.props;
const classes = withStyles.getClasses(this.props);
const visibilityClass = this._getVisibilityClass();
if (this.props._popoverDisabled) {
@@ -305,7 +306,8 @@ class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
* @returns {string}
*/
_getVisibilityClass() {
const { _isConnectionStatusInactive, _isConnectionStatusInterrupted, classes } = this.props;
const { _isConnectionStatusInactive, _isConnectionStatusInterrupted } = this.props;
const classes = withStyles.getClasses(this.props);
return this.state.showIndicator
|| this.props.alwaysVisible
@@ -399,4 +401,4 @@ export function _mapStateToProps(state: IReduxState, ownProps: any) {
};
}
export default connect(_mapStateToProps)(translate(withStyles(styles)(ConnectionIndicator)));
export default connect(_mapStateToProps)(translate(withStyles(ConnectionIndicator, styles)));

View File

@@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { useStyles } from 'tss-react/mui';
import Icon from '../../../base/icons/components/Icon';
import { IconConnection, IconConnectionInactive } from '../../../base/icons/svg';
@@ -13,7 +13,7 @@ interface IProps {
/**
* An object containing the CSS classes.
*/
classes: any;
classes?: Partial<Record<'icon' | 'inactiveIcon', string>>;
/**
* A CSS class that interprets the current connection status as a color.
@@ -41,10 +41,6 @@ interface IProps {
track?: ITrack;
}
const useStyles = makeStyles()(() => {
return {};
});
export const ConnectionIndicatorIcon = ({
classes,
colorClass,
@@ -90,7 +86,7 @@ export const ConnectionIndicatorIcon = ({
return (
<span className = 'connection_ninja'>
<Icon
className = { cx(classes.icon, classes.inactiveIcon, colorClass) }
className = { cx(classes?.icon, classes?.inactiveIcon, colorClass) }
size = { 24 }
src = { IconConnectionInactive } />
</span>
@@ -107,7 +103,7 @@ export const ConnectionIndicatorIcon = ({
return (
<span className = { emptyIconWrapperClassName }>
<Icon
className = { cx(classes.icon, colorClass) }
className = { cx(classes?.icon, colorClass) }
size = { 16 }
src = { IconConnection } />
</span>

View File

@@ -8,9 +8,11 @@ import {
/**
* Contains all the callbacks to be notified when stats are updated.
*
* ```
* {
* userId: Function[]
* }.
* }
* ```
*/
const subscribers: any = {};

View File

@@ -1,8 +1,8 @@
import { Theme } from '@mui/material';
import { withStyles } from '@mui/styles';
import React from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { withStyles } from 'tss-react/mui';
import { IReduxState, IStore } from '../../app/types';
import { getAvailableDevices } from '../../base/devices/actions.web';
@@ -37,7 +37,7 @@ interface IProps extends AbstractDialogTabProps, WithTranslation {
/**
* CSS classes object.
*/
classes: any;
classes?: Partial<Record<keyof ReturnType<typeof styles>, string>>;
/**
* Whether or not the audio selector can be interacted with. If true,
@@ -234,7 +234,6 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
*/
render() {
const {
classes,
hasAudioPermission,
hideAudioInputPreview,
hideAudioOutputPreview,
@@ -247,6 +246,8 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
} = this.props;
const { audioInput, audioOutput } = this._getSelectors();
const classes = withStyles.getClasses(this.props);
return (
<div className = { classes.container }>
{!iAmVisitor && <div
@@ -392,4 +393,4 @@ const mapStateToProps = (state: IReduxState) => {
};
};
export default connect(mapStateToProps)(withStyles(styles)(translate(AudioDevicesSelection)));
export default connect(mapStateToProps)(withStyles(translate(AudioDevicesSelection), styles));

View File

@@ -1,8 +1,8 @@
import { Theme } from '@mui/material';
import { withStyles } from '@mui/styles';
import React from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { withStyles } from 'tss-react/mui';
import { IReduxState, IStore } from '../../app/types';
import { getAvailableDevices } from '../../base/devices/actions.web';
@@ -33,7 +33,7 @@ export interface IProps extends AbstractDialogTabProps, WithTranslation {
/**
* CSS classes object.
*/
classes: any;
classes?: Partial<Record<keyof ReturnType<typeof styles>, string>>;
/**
* The currently selected desktop share frame rate in the frame rate select dropdown.
@@ -203,13 +203,14 @@ class VideoDeviceSelection extends AbstractDialogTab<IProps, IState> {
*/
render() {
const {
classes,
hideAdditionalSettings,
hideVideoInputPreview,
localFlipX,
t
} = this.props;
const classes = withStyles.getClasses(this.props);
return (
<div className = { classes.container }>
{ !hideVideoInputPreview
@@ -366,4 +367,4 @@ const mapStateToProps = (state: IReduxState) => {
};
};
export default connect(mapStateToProps)(withStyles(styles)(translate(VideoDeviceSelection)));
export default connect(mapStateToProps)(withStyles(translate(VideoDeviceSelection), styles));

View File

@@ -23,7 +23,8 @@ MiddlewareRegistry.register(store => next => action => {
backgroundImageUrl,
brandedIcons,
didPageUrl,
inviteDomain
inviteDomain,
labels
} = action.value;
action.value = {
@@ -32,7 +33,8 @@ MiddlewareRegistry.register(store => next => action => {
backgroundImageUrl,
brandedIcons,
didPageUrl,
inviteDomain
inviteDomain,
labels
};
// The backend may send an empty string, make sure we skip that.

View File

@@ -230,7 +230,7 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
* Transforms the branding images into an array of Images objects ready
* to be used as virtual backgrounds.
*
* @param {Array<string>} images -
* @param {Array<string>} images - The branding images.
* @private
* @returns {{Props}}
*/

View File

@@ -50,7 +50,7 @@ export function doesEveryoneSupportE2EE(stateful: IStateful) {
*
* @param {Function|Object} stateful - The redux store or {@code getState}
* function.
* @returns {boolean}.
* @returns {boolean}
*/
export function isMaxModeReached(stateful: IStateful) {
const participantCount = getParticipantCount(toState(stateful));
@@ -63,7 +63,7 @@ export function isMaxModeReached(stateful: IStateful) {
*
* @param {Function|Object} stateful - The redux store or {@code getState}
* function.
* @returns {boolean}.
* @returns {boolean}
*/
export function isMaxModeThresholdReached(stateful: IStateful) {
const participantCount = getParticipantCount(toState(stateful));

View File

@@ -56,6 +56,7 @@ class SharedDocument extends PureComponent<IProps> {
style = { styles.sharedDocContainer }>
<WebView
hideKeyboardAccessoryView = { true }
incognito = { true }
renderLoading = { this._renderLoading }
source = {{ uri: _documentUrl ?? '' }}
startInLoadingState = { true }

View File

@@ -4,6 +4,7 @@ import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
import { getCurrentConference } from '../base/conference/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { sanitizeUrl } from '../base/util/uri';
import { TOGGLE_DOCUMENT_EDITING } from './actionTypes';
import { setDocumentUrl } from './actions';
@@ -27,9 +28,10 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
({ value }: { value: string; }) => {
let url;
const { etherpad_base: etherpadBase } = getState()['features/base/config'];
const etherpadBaseUrl = sanitizeUrl(etherpadBase);
if (etherpadBase) {
url = new URL(value, etherpadBase).toString();
if (etherpadBaseUrl) {
url = new URL(value, etherpadBaseUrl.toString()).toString();
}
dispatch(setDocumentUrl(url));

View File

@@ -3,12 +3,11 @@ import { AnyAction } from 'redux';
import { IStore } from '../app/types';
import {
CONFERENCE_JOINED,
CONFERENCE_WILL_LEAVE
CONFERENCE_WILL_LEAVE,
ENDPOINT_MESSAGE_RECEIVED
} from '../base/conference/actionTypes';
import { getCurrentConference } from '../base/conference/functions';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { getLocalParticipant, getParticipantCount } from '../base/participants/functions';
import { IParticipant } from '../base/participants/types';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from '../base/tracks/actionTypes';
@@ -28,27 +27,22 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
FaceLandmarksDetector.init(store);
}
// allow using remote face centering data when local face centering is not enabled
action.conference.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(participant: IParticipant | undefined, eventData: any) => {
if (!participant || !eventData || !participant.getId) {
return;
}
return next(action);
} else if (action.type === ENDPOINT_MESSAGE_RECEIVED) {
// Allow using remote face centering data when local face centering is not enabled.
const { participant, data } = action;
if (eventData.type === FACE_BOX_EVENT_TYPE) {
dispatch({
type: UPDATE_FACE_COORDINATES,
faceBox: eventData.faceBox,
id: participant.getId()
});
}
if (data?.type === FACE_BOX_EVENT_TYPE) {
dispatch({
type: UPDATE_FACE_COORDINATES,
faceBox: data.faceBox,
id: participant.getId()
});
}
return next(action);
}
if (!isEnabled) {
return next(action);
}
@@ -57,7 +51,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
case CONFERENCE_WILL_LEAVE : {
FaceLandmarksDetector.stopDetection(store);
return next(action);
break;
}
case TRACK_ADDED: {
const { jitsiTrack: { isLocal, videoType }, muted } = action.track;
@@ -67,18 +61,18 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
FaceLandmarksDetector.startDetection(store, action.track);
}
return next(action);
break;
}
case TRACK_UPDATED: {
const { jitsiTrack: { isLocal, videoType } } = action.track;
if (videoType !== 'camera' || !isLocal()) {
return next(action);
break;
}
const { muted } = action.track;
if (muted !== undefined) {
if (typeof muted !== 'undefined') {
// addresses video mute state changes
if (muted) {
FaceLandmarksDetector.stopDetection(store);
@@ -87,7 +81,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
}
}
return next(action);
break;
}
case TRACK_REMOVED: {
const { jitsiTrack: { isLocal, videoType } } = action.track;
@@ -96,7 +90,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
FaceLandmarksDetector.stopDetection(store);
}
return next(action);
break;
}
case ADD_FACE_LANDMARKS: {
const state = getState();
@@ -110,7 +104,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
// Disabling for now as there is no value of having the data in speakerstats at the server
// sendFaceExpressionToServer(conference, faceLandmarks);
return next(action);
break;
}
case NEW_FACE_COORDINATES: {
const state = getState();
@@ -127,6 +121,8 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
faceBox,
id: localParticipant?.id
});
break;
}
}

View File

@@ -81,7 +81,7 @@ interface IProps {
class Filmstrip extends PureComponent<IProps> {
/**
* Whether the local participant should be rendered separately from the
* remote participants ie outside of their {@link ScrollView}.
* remote participants i.e. outside of their {@link ScrollView}.
*/
_separateLocalThumbnail: boolean;

View File

@@ -1,15 +1,14 @@
import { withStyles } from '@mui/styles';
import clsx from 'clsx';
import _ from 'lodash';
import React, { PureComponent } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { FixedSizeGrid, FixedSizeList } from 'react-window';
import { withStyles } from 'tss-react/mui';
import { ACTION_SHORTCUT_TRIGGERED, createShortcutEvent, createToolbarEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState, IStore } from '../../../app/types';
import { getToolbarButtons } from '../../../base/config/functions.web';
import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n/functions';
import Icon from '../../../base/icons/components/Icon';
@@ -213,7 +212,7 @@ interface IProps extends WithTranslation {
/**
* An object containing the CSS classes.
*/
classes: any;
classes?: Partial<Record<keyof ReturnType<typeof styles>, string>>;
/**
* The redux {@code dispatch} function.
@@ -349,10 +348,10 @@ class Filmstrip extends PureComponent <IProps, IState> {
_verticalViewBackground,
_verticalViewGrid,
_verticalViewMaxWidth,
classes,
filmstripType,
t
} = this.props;
const classes = withStyles.getClasses(this.props);
const { isMouseDown } = this.state;
const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
@@ -838,12 +837,12 @@ class Filmstrip extends PureComponent <IProps, IState> {
_renderToggleButton() {
const {
t,
classes,
_isVerticalFilmstrip,
_mainFilmstripVisible,
_topPanelFilmstrip,
_topPanelVisible
} = this.props;
const classes = withStyles.getClasses(this.props);
const icon = (_topPanelFilmstrip ? _topPanelVisible : _mainFilmstripVisible) ? IconArrowDown : IconArrowUp;
const actions = isMobileBrowser()
? { onTouchStart: this._onToggleButtonTouch }
@@ -884,11 +883,11 @@ class Filmstrip extends PureComponent <IProps, IState> {
*/
function _mapStateToProps(state: IReduxState, ownProps: any) {
const { _hasScroll = false, filmstripType, _topPanelFilmstrip, _remoteParticipants } = ownProps;
const toolbarButtons = getToolbarButtons(state);
const { toolbarButtons } = state['features/toolbox'];
const { iAmRecorder } = state['features/base/config'];
const { topPanelHeight, topPanelVisible, visible, width: verticalFilmstripWidth } = state['features/filmstrip'];
const { localScreenShare } = state['features/base/participants'];
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
const reduceHeight = state['features/toolbox'].visible && toolbarButtons?.length;
const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
const { isOpen: shiftRight } = state['features/chat'];
const disableSelfView = getHideSelfView(state);
@@ -942,4 +941,4 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
};
}
export default withStyles(styles)(translate(connect(_mapStateToProps)(Filmstrip)));
export default withStyles(translate(connect(_mapStateToProps)(Filmstrip)), styles);

View File

@@ -2,7 +2,6 @@ import React from 'react';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { getToolbarButtons } from '../../../base/config/functions.web';
import { isMobileBrowser } from '../../../base/environment/utils';
import { LAYOUTS } from '../../../video-layout/constants';
import { getCurrentLayout } from '../../../video-layout/functions.web';
@@ -107,9 +106,9 @@ const MainFilmstrip = (props: IProps) => (
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState, _ownProps: any) {
const toolbarButtons = getToolbarButtons(state);
const { toolbarButtons } = state['features/toolbox'];
const { remoteParticipants, width: verticalFilmstripWidth } = state['features/filmstrip'];
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
const reduceHeight = state['features/toolbox'].visible && toolbarButtons?.length;
const {
gridDimensions: dimensions = { columns: undefined,
rows: undefined },

View File

@@ -2,7 +2,6 @@ import React from 'react';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { getToolbarButtons } from '../../../base/config/functions.web';
import { isMobileBrowser } from '../../../base/environment/utils';
import { LAYOUTS, LAYOUT_CLASSNAMES } from '../../../video-layout/constants';
import { getCurrentLayout } from '../../../video-layout/functions.web';
@@ -108,9 +107,9 @@ const StageFilmstrip = (props: IProps) =>
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState, _ownProps: any) {
const toolbarButtons = getToolbarButtons(state);
const { toolbarButtons } = state['features/toolbox'];
const activeParticipants = getActiveParticipantsIds(state);
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
const reduceHeight = state['features/toolbox'].visible && toolbarButtons?.length;
const {
gridDimensions: dimensions = { columns: undefined,
rows: undefined },

View File

@@ -1,10 +1,10 @@
import { Theme } from '@mui/material';
import { withStyles } from '@mui/styles';
import clsx from 'clsx';
import debounce from 'lodash/debounce';
import React, { Component, KeyboardEvent, RefObject, createRef } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { withStyles } from 'tss-react/mui';
import { createScreenSharingIssueEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
@@ -227,7 +227,7 @@ export interface IProps extends WithTranslation {
/**
* An object containing CSS classes.
*/
classes: any;
classes?: Partial<Record<keyof ReturnType<typeof defaultStyles>, string>>;
/**
* The redux dispatch function.
@@ -351,8 +351,8 @@ const defaultStyles = (theme: Theme) => {
'& img': {
maxWidth: '100%',
maxHeight: '100%',
objectFit: 'contain',
flexGrow: '1'
objectFit: 'contain' as const,
flexGrow: 1
}
},
@@ -946,9 +946,9 @@ class Thumbnail extends Component<IProps, IState> {
_isDominantSpeakerDisabled,
_participant,
_raisedHand,
_thumbnailType,
classes
_thumbnailType
} = this.props;
const classes = withStyles.getClasses(this.props);
className += ` ${DISPLAY_MODE_TO_CLASS_NAME[displayMode]}`;
@@ -994,7 +994,8 @@ class Thumbnail extends Component<IProps, IState> {
* @returns {Component}
*/
_renderGif() {
const { _gifSrc, classes } = this.props;
const { _gifSrc } = this.props;
const classes = withStyles.getClasses(this.props);
return _gifSrc && (
<div className = { classes.gif }>
@@ -1033,10 +1034,10 @@ class Thumbnail extends Component<IProps, IState> {
_shouldDisplayTintBackground,
_thumbnailType,
_videoTrack,
classes,
filmstripType,
t
} = this.props;
const classes = withStyles.getClasses(this.props);
const { id, name, pinned } = _participant || {};
const { isHovered, popoverVisible } = this.state;
const styles = this._getStyles();
@@ -1197,7 +1198,8 @@ class Thumbnail extends Component<IProps, IState> {
if (_isVirtualScreenshareParticipant) {
const { isHovered } = this.state;
const { _videoTrack, _isMobile, classes, _thumbnailType } = this.props;
const { _videoTrack, _isMobile, _thumbnailType } = this.props;
const classes = withStyles.getClasses(this.props);
return (
<VirtualScreenshareParticipant
@@ -1399,4 +1401,4 @@ function _mapStateToProps(state: IReduxState, ownProps: any): Object {
};
}
export default connect(_mapStateToProps)(withStyles(defaultStyles)(translate(Thumbnail)));
export default connect(_mapStateToProps)(withStyles(translate(Thumbnail), defaultStyles));

View File

@@ -17,7 +17,7 @@ interface IProps {
/**
* Class name for indicators container.
*/
className: string;
className?: string;
/**
* Whether or not the indicators are for the local participant.

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