Compare commits

..

23 Commits

Author SHA1 Message Date
Calin-Teodor
392c8e9aa8 feat(toolbox): fixed undefined for previous layout type 2023-12-19 16:36:23 +02:00
Avram Tudor
7f87d4eada feat(transcript) add recording settings for recording transcriptions (#14158) 2023-12-19 11:25:06 +02:00
Mihaela Dumitru
6d11aa8049 fix(ui) style prejoin drawer (#14165) 2023-12-18 18:30:48 +02:00
Saúl Ibarra Corretgé
0c1ce152fe feat(error-handling) refactor global error and unhandledrejection event handling
Conceptually related: https://github.com/jitsi/lib-jitsi-meet/pull/2411
2023-12-15 23:56:19 +01:00
goblin
82ae6a8456 fix(doc) update README
Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org>
2023-12-15 12:03:56 +01:00
Дамян Минков
9ebab2c7d0 feat: Visitors promotion (#14119)
* fix: Fixes wrong warning message.

* fix: Detect enables/disables visitors for a room.

* fix: We need customusername in all cases of auto-allow setting.

* feat: Sends promotion-request to all moderators.

* feat(visitors): Implements request promotion.

* feat(visitors): Implements single moderator and vpass cases for moderators.

* fix: Fixes clearing request instances from UI.

* feat: Implements visitors approval for mobile.

* squash: Drops unused and wrong report for auto allow promotion.

* squash: Returns early based on count.

* squash: Moves translation to common key.

* squash: Adds dependencies for useCallback.

* squash: comments.

* squash: Refactor 1 to unify with native.

* squash: Rename some styles.

* squash: Fixes error dew to fewer hooks error.

* squash: Renames VISITOR_PROMOTION_REQUEST_DENIED.

* squash: Fix renaming component.

* squash: Suggestions.
2023-12-14 08:31:58 -06:00
Horatiu Muresan
af4488d1e9 fix(toolbox) prevent toolbox shift up on stage view (#14155) 2023-12-14 12:32:19 +02:00
bgrozev
d9599d31f1 fix: Do not log unknown commands. (#14153)
Events such as "mouse-move", "mouse-leave" and "face-landmark-detected"
reach this code and pollute the logs. It's probably worth investigating
why this is the case and fixing it if necessary, but for now just remove
the log message.
2023-12-13 10:04:48 -08:00
Mihaela Dumitru
d094ac0034 fix(external-api) extend captureLargeVideoScreenshot for screenshare (#14149) 2023-12-13 17:31:44 +02:00
Avram Tudor
c6b7ec7c9c fix(transcript) duplicated namespace (#14151) 2023-12-13 16:15:09 +02:00
Calinteodor
6e35e5b310 feat(call-integration): revert changes related to visitors (#14150)
* feat(mobile/call-integration): removed undefined checks
2023-12-13 15:38:44 +02:00
Jaya Allamsetty
429787f9c8 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1734.0.0+34ceebd2...v1736.0.0+8bee4514
2023-12-12 16:04:22 -05:00
Дамян Минков
f7995b395f feat: Adds detection of occupants with no connection. (#14146)
* feat: Adds detection of occupants with no connection.

We saw recently two occasions with rooms with participants but no prosody.full_sessions for those participants and when everyone leaves the meeting it never ends.

* squash: Updates counting.
2023-12-12 11:47:43 -06:00
Avram Tudor
72b4c8123a ref(transcriptions): refactor transcriptions api (#14144)
* ref(transcriptions): refactor transcriptions api

* ref(transcriptions): refactor usage of translation label

Extend IFrame API to allow adding a transcriber in the room without the subtitles needing to be visible.
Allow transcription chunk messages to be passed through the IFrame API if a transcriber is present.
Clean-up transcription messages sent through the IFrame API to not include timeout field and possible conflicting states (stable / unstable /final)

* fix linting

* code review: extend api message to match webhook format
2023-12-12 14:36:54 +02:00
Abbas Al-Mansoori
4c6cadea6d fix: lint 2023-12-12 12:04:38 +02:00
Abbas Al-Mansoori
1bc50ea71c feat(rn-sdk): add getRoomsInfo ref callback 2023-12-12 12:04:38 +02:00
Abbas Al-Mansoori
60b5225ffd feat(rn-sdk): add onParticipantLeft event listener 2023-12-12 12:04:38 +02:00
damencho
5fe3685a05 fix: Drops luacheck for modules sourced from prosody-modules.
Only mod_firewall fails for now.
2023-12-11 10:41:34 -06:00
Aaron van Meerten
fbfc0f6c2f task: vendor mod_firewall from prosody plugins
changeset 6696075e26e2
https://hg.prosody.im/prosody-modules/raw-file/6696075e26e2/mod_firewall/mod_firewall.lua
2023-12-11 10:41:34 -06:00
Aaron van Meerten
bbed4be61b task: vendor mod_measure_stanza_counts.lua
changeset 6696075e26e2
https://hg.prosody.im/prosody-modules/raw-file/6696075e26e2/mod_measure_stanza_counts/mod_measure_stanza_counts.lua
2023-12-11 10:41:34 -06:00
Aaron van Meerten
68f954d068 task: vendor mod_debug_traceback.lua
changeset 6696075e26e2
https://hg.prosody.im/prosody-modules/raw-file/6696075e26e2/mod_debug_traceback/mod_debug_traceback.lua
2023-12-11 10:41:34 -06:00
Aaron van Meerten
30144b8707 feat: vendor mod_log_ringbuffer from prosody hg
changeset 6696075e26e2
https://hg.prosody.im/prosody-modules/raw-file/6696075e26e2/mod_log_ringbuffer/mod_log_ringbuffer.lua
2023-12-11 10:41:34 -06:00
Saúl Ibarra Corretgé
dd232f55a9 fix(rn,room-lock) use numeric input for password dialog if appropriate (#14142) 2023-12-11 14:10:31 +01:00
119 changed files with 3705 additions and 567 deletions

View File

@@ -17,7 +17,8 @@ jobs:
- name: Check lua codes
run: |
set -o pipefail && luacheck . | awk -F: '
set -o pipefail && luacheck . \
--exclude-files=resources/prosody-plugins/mod_firewall/mod_firewall.lua | awk -F: '
{
print $0
printf "::warning file=%s,line=%s,col=%s::%s\n", $1, $2, $3, $4

View File

@@ -27,7 +27,7 @@ And many more!
## Using Jitsi Meet
Using Jitsi Meet is straightforward, as it's browser based. Head over to [meet.jit.si](https://meet.jit.si) and give it a try. It's anonymous, scalable and free to use. All browsers are supported!
Using Jitsi Meet is straightforward, as it's browser based. Head over to [meet.jit.si](https://meet.jit.si) and give it a try. It's scalable and free to use. All you need is a Google, Facebook or GitHub account in order to start a meeting. All browsers are supported!
Using mobile? No problem, you can either use your mobile web browser or our fully-featured
mobile apps:

View File

@@ -378,7 +378,7 @@ var config = {
// DEPRECATED. Use transcription.preferredLanguage instead.
// preferredTranscribeLanguage: 'en-US',
// DEPRECATED. Use transcription.autoCaptionOnRecord instead.
// DEPRECATED. Use transcription.autoTranscribeOnRecord instead.
// autoCaptionOnRecord: false,
// Transcription options.
@@ -410,8 +410,8 @@ var config = {
// // Disable start transcription for all participants.
// disableStartForAll: false,
// // Enables automatic turning on captions when recording is started
// autoCaptionOnRecord: false,
// // Enables automatic turning on transcribing when recording is started
// autoTranscribeOnRecord: false,
// },
// Misc

View File

@@ -15,6 +15,10 @@
font-size: 14px;
margin-left: 16px;
max-width: 70%;
&-no-space {
margin-left: 0;
}
}
&.space-top {

View File

@@ -557,8 +557,6 @@
"youtubeTerms": "شروط خدمة يوتيوب"
},
"lobby": {
"admit": "سمح بالدخول",
"admitAll": "سمح للجميع بالدخول",
"allow": "اسمح",
"backToKnockModeButton": "لا يوجد كلمة مرور، اطلب الإذن بالدخول بدلًا من ذلك.",
"chat": "دردشة",
@@ -593,8 +591,6 @@
"notificationTitle": "غرفة الانتظار",
"passwordField": "أدخل كلمة الدخول إلى المُلتقى",
"passwordJoinButton": "انضم",
"reject": "رفض",
"rejectAll": "رفض الكل",
"title": "غرفة الانتظار",
"toggleLabel": "فعِّل غرفة الانتظار"
},
@@ -720,6 +716,8 @@
},
"participantsPane": {
"actions": {
"admit": "سمح بالدخول",
"admitAll": "سمح للجميع بالدخول",
"allow": "السماح للحاضرين بـ:",
"allowVideo": "السماح بالفيديو",
"askUnmute": "اطلب إعادة الصوت",
@@ -732,6 +730,7 @@
"mute": "كتم الصوت",
"muteAll": "كتم الكل",
"muteEveryoneElse": "كتم صوت الآخرين",
"reject": "رفض",
"stopEveryonesVideo": "أوقف فيديو الجميع",
"stopVideo": "أوقف الفيديو",
"unblockEveryoneMicCamera": "قم بإلغاء حظر ميكروفون وكاميرا الجميع",

View File

@@ -420,8 +420,6 @@
"youtubeTerms": "Условия за ползване на YouTube"
},
"lobby": {
"admit": "Допусни",
"allow": "Разреши",
"backToKnockModeButton": "Заявка за включване без парола",
"dialogTitle": "Режим лоби",
"disableDialogContent": "Режим Лоби е включен. Този решим защитава срещите Ви от случайни посетители. Искате ли да го изключите?",
@@ -450,7 +448,6 @@
"notificationTitle": "Лоби",
"passwordField": "Въведи парола за срещата",
"passwordJoinButton": "Влез",
"reject": "Откажи",
"title": "Лоби",
"toggleLabel": "Включи лоби"
},
@@ -521,6 +518,13 @@
"suboptimalExperienceTitle": "Внимание",
"unmute": "Пускане на микрофона"
},
"participantsPane": {
"actions": {
"admit": "Допусни",
"allow": "Разреши",
"reject": "Откажи"
}
},
"passwordDigitsOnly": "До {{number}} цифри",
"passwordSetRemotely": "зададена от друг участник",
"poweredby": "с подкрепата на",

View File

@@ -561,8 +561,6 @@
"youtubeTerms": "Condicions de servei de YouTube"
},
"lobby": {
"admit": "Admet",
"admitAll": "Admet tothom",
"allow": "Permet",
"backToKnockModeButton": "Demaneu per a unir-vos",
"chat": "Xat",
@@ -597,8 +595,6 @@
"notificationTitle": "Sala d'espera",
"passwordField": "Introduïu la contrasenya de la reunió",
"passwordJoinButton": "Entra",
"reject": "Rebuja",
"rejectAll": "Rebutja-ho tot",
"title": "Sala d'espera",
"toggleLabel": "Activa la sala d'espera"
},
@@ -727,6 +723,8 @@
},
"participantsPane": {
"actions": {
"admit": "Admet",
"admitAll": "Admet tothom",
"allow": "Permet als assistents:",
"allowVideo": "Permet el vídeo",
"askUnmute": "Demanar l'activació el micròfon",
@@ -739,6 +737,7 @@
"mute": "Silenciar",
"muteAll": "Silencia tothom",
"muteEveryoneElse": "Silenciar tothom",
"reject": "Rebuja",
"stopEveryonesVideo": "Atura el vídeo a tothom",
"stopVideo": "Atura el vídeo",
"unblockEveryoneMicCamera": "Desbloquejar el micròfon i la càmera de tothom",

View File

@@ -557,8 +557,6 @@
"youtubeTerms": "Podmínky používání YouTube"
},
"lobby": {
"admit": "",
"admitAll": "",
"allow": "Povolit",
"backToKnockModeButton": "Žádné heslo, místo toho požádat o přijetí",
"chat": "",
@@ -593,7 +591,6 @@
"notificationTitle": "Předsálí",
"passwordField": "Zadejte heslo setkání",
"passwordJoinButton": "Vstoupit",
"reject": "Odmítnout",
"title": "Předsálí",
"toggleLabel": "Zapnout předsálí"
},
@@ -719,22 +716,7 @@
},
"participantsPane": {
"actions": {
"allow": "",
"allowVideo": "",
"askUnmute": "",
"audioModeration": "",
"blockEveryoneMicCamera": "",
"invite": "",
"moreModerationActions": "",
"moreModerationControls": "",
"moreParticipantOptions": "",
"mute": "",
"muteAll": "",
"muteEveryoneElse": "",
"stopEveryonesVideo": "",
"stopVideo": "",
"unblockEveryoneMicCamera": "",
"videoModeration": ""
"reject": "Odmítnout"
},
"close": "",
"header": "",

View File

@@ -636,8 +636,6 @@
"youtubeTerms": "YouTube-Nutzungsbedingungen"
},
"lobby": {
"admit": "Zulassen",
"admitAll": "Alle zulassen",
"backToKnockModeButton": "Kein Passwort, stattdessen Beitritt anfragen",
"chat": "Chat",
"dialogTitle": "Lobbymodus",
@@ -671,8 +669,6 @@
"notificationLobbyEnabled": "{{originParticipantName}} hat die Lobby aktiviert",
"notificationTitle": "Lobby",
"passwordJoinButton": "Beitreten",
"reject": "Ablehnen",
"rejectAll": "Alle ablehnen",
"title": "Lobby",
"toggleLabel": "Lobby aktivieren"
},
@@ -807,6 +803,8 @@
},
"participantsPane": {
"actions": {
"admit": "Zulassen",
"admitAll": "Alle zulassen",
"allow": "Anwesenden erlauben:",
"allowVideo": "Kamera einschalten",
"askUnmute": "Anfragen, Stummschaltung aufzuheben",
@@ -819,6 +817,7 @@
"mute": "Stummschalten",
"muteAll": "Alle stummschalten",
"muteEveryoneElse": "Alle anderen stummschalten",
"reject": "Ablehnen",
"stopEveryonesVideo": "Alle Kameras ausschalten",
"stopVideo": "Kamera ausschalten",
"unblockEveryoneMicCamera": "Kamera und Mikrofon von allen entsperren",

View File

@@ -563,8 +563,6 @@
"youtubeTerms": "wužywaŕske wustawki za youtube"
},
"lobby": {
"admit": "pśizwóliś",
"admitAll": "wšyknym pśizwólenje daś",
"backToKnockModeButton": "mimo kodowego słowa, město togo wó pśistup pšosyś",
"chat": "chat",
"dialogTitle": "lobbyjowy modus",
@@ -598,8 +596,6 @@
"notificationTitle": "lobby",
"passwordField": "kodowe słowo za konferencu zapódaś",
"passwordJoinButton": "pśistupiś",
"reject": "wótpokazaś",
"rejectAll": "wšykne wótpokazaś",
"title": "",
"toggleLabel": "lobby aktiwěrowaś / deaktiwěrowaś"
},
@@ -730,6 +726,8 @@
},
"participantsPane": {
"actions": {
"admit": "pśizwóliś",
"admitAll": "wšyknym pśizwólenje daś",
"allow": "wobźělnikam pšawo daś:",
"allowVideo": "kameru aktiwěrowaś",
"askUnmute": "pšosbu wó anulěrowanje wuśišenja stajiś",
@@ -742,6 +740,7 @@
"mute": "wuśišyś",
"muteAll": "wšyknych wuśišyś",
"muteEveryoneElse": "wšykne druge wuśišyś",
"reject": "wótpokazaś",
"stopEveryonesVideo": "wšykne kamery wušaltowaś",
"stopVideo": "kameru wušaltowaś",
"unblockEveryoneMicCamera": "blokěrowane kamery a mikrofon wšyknych zasej aktiwěrowaś",

View File

@@ -580,8 +580,6 @@
"youtubeTerms": "Όροι υπηρεσιών YouTube"
},
"lobby": {
"admit": "Αποδοχή",
"admitAll": "Αποδοχή όλων",
"backToKnockModeButton": "Αίτημα εισόδου",
"chat": "Συνομιλία",
"dialogTitle": "Λειτουργία υποδοχής",
@@ -615,8 +613,6 @@
"notificationTitle": "Υποδοχή",
"passwordField": "Εισάγετε τον κωδικό σύσκεψης",
"passwordJoinButton": "Συμμετοχή",
"reject": "Απόρριψη",
"rejectAll": "Απόρριψη όλων",
"title": "Υποδοχή",
"toggleLabel": "Ενεργοποίηση υποδοχής"
},
@@ -745,6 +741,8 @@
},
"participantsPane": {
"actions": {
"admit": "Αποδοχή",
"admitAll": "Αποδοχή όλων",
"allow": "Επιτρέψτε στους συμμετέχοντες να:",
"allowVideo": "Επιτρέψτε το βίντεο",
"askUnmute": "Αίτηση για κατάργηση σίγησης",
@@ -757,6 +755,7 @@
"mute": "Σίγηση",
"muteAll": "Σίγηση όλων",
"muteEveryoneElse": "Σίγηση όλων των άλλων",
"reject": "Απόρριψη",
"stopEveryonesVideo": "Διακοπή όλων των βίντεο",
"stopVideo": "Διακοπή του βίντεο",
"unblockEveryoneMicCamera": "Επιτρέψτε τα μικρόφωνα και τις κάμερες όλων",

View File

@@ -561,8 +561,6 @@
"youtubeTerms": "Uzkondiĉoj de YouTube"
},
"lobby": {
"admit": "Akcepti",
"admitAll": "Akcepti ĉion",
"allow": "Permesi",
"backToKnockModeButton": "Petu por aliĝi",
"chat": "Babilejo",
@@ -597,8 +595,6 @@
"notificationTitle": "Atendejo",
"passwordField": "Entajpu pasvorton de la renkontiĝo",
"passwordJoinButton": "Aliĝi",
"reject": "Malakceptu",
"rejectAll": "Malakceptu ĉion",
"title": "Atendejo",
"toggleLabel": "Ŝaltu atendejon"
},
@@ -725,6 +721,8 @@
},
"participantsPane": {
"actions": {
"admit": "Akcepti",
"admitAll": "Akcepti ĉion",
"allow": "Al la partoprenantoj permesi:",
"allowVideo": "Permesi kameraon",
"askUnmute": "Peti malsilentigi",
@@ -737,6 +735,7 @@
"mute": "Silentigi",
"muteAll": "Silentigi ĉiujn",
"muteEveryoneElse": "Silentigi ĉiujn aliajn",
"reject": "Malakceptu",
"stopEveryonesVideo": "Ĉesigu ĉies videaĵon",
"stopVideo": "Ĉesigu la videaĵon",
"unblockEveryoneMicCamera": "Malbloku ĉies mikrofonon kaj kameraon",

View File

@@ -598,8 +598,6 @@
"youtubeTerms": "Términos de servicios de YouTube"
},
"lobby": {
"admit": "Admitir",
"admitAll": "Admitir todo",
"backToKnockModeButton": "No hay contraseña, pide permiso para entrar.",
"chat": "Chat",
"dialogTitle": "Sala de espera",
@@ -633,8 +631,6 @@
"notificationTitle": "Sala de espera",
"passwordField": "Introduce la contraseña de la reunión",
"passwordJoinButton": "Entrar",
"reject": "Rechazar",
"rejectAll": "Rechazar todo",
"title": "Sala de espera",
"toggleLabel": "Activar sala de espera"
},
@@ -768,6 +764,8 @@
},
"participantsPane": {
"actions": {
"admit": "Admitir",
"admitAll": "Admitir todo",
"allow": "Permitir a los asistentes:",
"allowVideo": "Permitir vídeo",
"askUnmute": "Pida que le quiten el silencio",
@@ -780,6 +778,7 @@
"mute": "Silenciar",
"muteAll": "Silenciar a todos",
"muteEveryoneElse": "Silenciar al resto",
"reject": "Rechazar",
"stopEveryonesVideo": "Detener el vídeo de todos",
"stopVideo": "Detener el vídeo",
"unblockEveryoneMicCamera": "Desbloquear el micrófono y la cámara de todos",

View File

@@ -521,8 +521,6 @@
"youtubeTerms": "Términos de servicios de YouTube"
},
"lobby": {
"admit": "Admitir",
"admitAll": "Admitir todo",
"allow": "permitir",
"backToKnockModeButton": "No hay contraseña, pide permiso para entrar.",
"dialogTitle": "Sala de espera",
@@ -553,8 +551,6 @@
"notificationTitle": "Sala de espera",
"passwordField": "Introduce la contraseña de la reunión",
"passwordJoinButton": "Entrar",
"reject": "Rechazar",
"rejectAll": "Rechazar todo",
"title": "Sala de espera",
"toggleLabel": "Activar sala de espera"
},
@@ -652,6 +648,8 @@
},
"participantsPane": {
"actions": {
"admit": "Admitir",
"admitAll": "Admitir todo",
"allow": "Permitir a los asistentes:",
"allowVideo": "Permitir vídeo",
"askUnmute": "Pida que le quiten el silencio",
@@ -661,6 +659,7 @@
"mute": "Silenciar",
"muteAll": "Silenciar a todos los demás",
"muteEveryoneElse": "Silenciar al resto",
"reject": "Rechazar",
"stopEveryonesVideo": "Detener el vídeo de todos",
"stopVideo": "Detener el vídeo",
"unblockEveryoneMicCamera": "Desbloquear el micrófono y la cámara de todos",

View File

@@ -463,8 +463,6 @@
"youtubeTerms": "YouTuberen erabilpen baldintzak"
},
"lobby": {
"admit": "Onartu",
"admitAll": "Onartu guztiak",
"allow": "Baimendu",
"backToKnockModeButton": "Ez du pasahitza erabili, baina sartzea eskatu du",
"dialogTitle": "Itxaron-gela modua",
@@ -494,7 +492,6 @@
"notificationTitle": "Itxaron-gela",
"passwordField": "Idatzi bileraren pasahitza",
"passwordJoinButton": "Sartu",
"reject": "Baztertu",
"title": "Itxaron-gela",
"toggleLabel": "Itxaron-gela aktibatu"
},
@@ -576,8 +573,11 @@
},
"participantsPane": {
"actions": {
"admit": "Onartu",
"admitAll": "Onartu guztiak",
"invite": "Gonbidatu norbait",
"muteAll": "Ixilarazi guztiak",
"reject": "Baztertu",
"stopVideo": "Gelditu bideoa"
},
"close": "Itxi",

View File

@@ -606,8 +606,6 @@
"youtubeTerms": "شرایط خدمات یوتیوب"
},
"lobby": {
"admit": "پذیرفتن",
"admitAll": "پذیرفتن همه",
"backToKnockModeButton": "درخواست برای پیوستن",
"chat": "گپ",
"dialogTitle": "حالت اتاق انتظار",
@@ -641,8 +639,6 @@
"notificationTitle": "اتاق انتظار",
"passwordField": "گذرواژهٔ جلسه را وارد کنید",
"passwordJoinButton": "پیوستن",
"reject": "ردکردن",
"rejectAll": "ردکردن همه",
"title": "اتاق انتظار",
"toggleLabel": "فعال‌کردن اتاق انتظار"
},
@@ -776,6 +772,8 @@
},
"participantsPane": {
"actions": {
"admit": "پذیرفتن",
"admitAll": "پذیرفتن همه",
"allow": "به حاضران اجازه دهید:",
"allowVideo": "اجازهٔ ویدیو",
"askUnmute": "درخواست وصل‌کردن صدا",
@@ -788,6 +786,7 @@
"mute": "بی‌صداکردن",
"muteAll": "بی‌صداکردن همه",
"muteEveryoneElse": "بی‌صداکردن بقیه افراد",
"reject": "ردکردن",
"stopEveryonesVideo": "توقف ویدیوی همه",
"stopVideo": "توقف ویدیو",
"unblockEveryoneMicCamera": "رفع مسدودی میکروفون و دوربین همه",

View File

@@ -584,8 +584,6 @@
"youtubeTerms": "Conditions d'utilisation de YouTube"
},
"lobby": {
"admit": "Accepter",
"admitAll": "Tout accepter",
"backToKnockModeButton": "Aucun mot de passe, demander à rejoindre plutôt",
"chat": "Chat",
"dialogTitle": "Mode salle d'attente",
@@ -619,8 +617,6 @@
"notificationTitle": "Salle d'attente",
"passwordField": "Veuillez saisir le mot de passe de la réunion",
"passwordJoinButton": "Rejoindre",
"reject": "Refuser",
"rejectAll": "Refuser tout",
"title": "Salle d'attente",
"toggleLabel": "Activer la salle d'attente"
},
@@ -751,6 +747,8 @@
},
"participantsPane": {
"actions": {
"admit": "Accepter",
"admitAll": "Tout accepter",
"allow": "Autoriser les participants à:",
"allowVideo": "permettre la vidéo",
"askUnmute": "Demander de réactiver le micro",
@@ -763,6 +761,7 @@
"mute": "Couper le micro",
"muteAll": "Couper le micro de tout le monde",
"muteEveryoneElse": "Couper le micro de tous les autres",
"reject": "Refuser",
"stopEveryonesVideo": "Couper toutes les caméras",
"stopVideo": "Couper la vidéo",
"unblockEveryoneMicCamera": "Débloquer tous les micros et caméras",

View File

@@ -482,7 +482,6 @@
"notificationTitle": "लॉबी",
"passwordField": "मीटिंग पासवर्ड दर्ज करें",
"passwordJoinButton": "Join",
"reject": "अस्वीकार",
"title": "लॉबी",
"toggleLabel": "लॉबी सक्षम करें"
},
@@ -559,6 +558,11 @@
"videoMutedRemotelyDescription": "You can always turn it on again.",
"videoMutedRemotelyTitle": "आपका कैमरा {{participantDisplayName}}द्वारा अक्षम कर दिया गया है!"
},
"participantsPane": {
"actions": {
"reject": "अस्वीकार"
}
},
"passwordDigitsOnly": "Up to {{number}} digits",
"passwordSetRemotely": "दूसरे प्रतिभागी द्वारा निर्धारित",
"poweredby": "powered by",

View File

@@ -561,8 +561,6 @@
"youtubeTerms": "Uvjeti YouTube usluge"
},
"lobby": {
"admit": "Prihvati",
"admitAll": "Prihvati sve",
"allow": "Dopusti",
"backToKnockModeButton": "Zatraži pridruživanje",
"chat": "Chat",
@@ -597,8 +595,6 @@
"notificationTitle": "Predvorje",
"passwordField": "Upiši lozinku sastanka",
"passwordJoinButton": "Pridruži se",
"reject": "Odbij",
"rejectAll": "Odbij sve",
"title": "Predvorje",
"toggleLabel": "Uključi predvorje"
},
@@ -725,6 +721,8 @@
},
"participantsPane": {
"actions": {
"admit": "Prihvati",
"admitAll": "Prihvati sve",
"allow": "Dozvoli sudionicima da:",
"allowVideo": "Dozvole video",
"askUnmute": "Zatraže isključivanje zvuka",
@@ -737,6 +735,7 @@
"mute": "Isključe zvuk",
"muteAll": "Isključe zvuk svih sudionika",
"muteEveryoneElse": "Isključe zvuk svih drugih",
"reject": "Odbij",
"stopEveryonesVideo": "Prekinu videa svih",
"stopVideo": "Prekinu video",
"unblockEveryoneMicCamera": "Deblokiraju mikrofone i kamere svih sudionika",

View File

@@ -551,8 +551,6 @@
"youtubeTerms": "wuměnjenja wužiwanja na YouTube"
},
"lobby": {
"admit": "přizwolić",
"admitAll": "wšitko přizwolić",
"allow": "přiwzać",
"backToKnockModeButton": "žane hesło, město toho wo přistup prosyć",
"chat": "chat",
@@ -587,8 +585,6 @@
"notificationTitle": "lobby",
"passwordField": "konferencne hesło zapodać",
"passwordJoinButton": "přistupić",
"reject": "wotpokazać",
"rejectAll": "wšitko wotpokazać",
"title": "lobby",
"toggleLabel": "lobby aktiwěrować"
},
@@ -710,6 +706,8 @@
},
"participantsPane": {
"actions": {
"admit": "přizwolić",
"admitAll": "wšitko přizwolić",
"allow": "přitomnym dowolić",
"allowVideo": "kameru zaswěčić",
"askUnmute": "wo wotstajenje šaltowanja na němosć prosyć",
@@ -722,6 +720,7 @@
"mute": "něme šaltować",
"muteAll": "wšěch němych šaltować",
"muteEveryoneElse": "wšěch druhich němych šaltować",
"reject": "wotpokazać",
"stopEveryonesVideo": "wšitke kamery hasnyć",
"stopVideo": "kameru hasnyć",
"unblockEveryoneMicCamera": "kameru a mikrofon wšěch wočinić",

View File

@@ -462,8 +462,6 @@
"youtubeTerms": "YouTube szolgáltatási feltételek"
},
"lobby": {
"admit": "Engedélyezés",
"admitAll": "Mindet engedélyez",
"allow": "Engedélyez",
"backToKnockModeButton": "Csatlakozási kérelem küldése",
"chat": "Chat",
@@ -495,8 +493,6 @@
"notificationTitle": "Lobby",
"passwordField": "Adja meg az értekezlet jelszavát",
"passwordJoinButton": "Csatlakozás",
"reject": "Elutasít",
"rejectAll": "Mindet elutasít",
"toggleLabel": "Lobby engedélyezése"
},
"localRecording": {
@@ -579,6 +575,8 @@
},
"participantsPane": {
"actions": {
"admit": "Engedélyezés",
"admitAll": "Mindet engedélyez",
"allow": "Engedélyezés a résztvevőknek, hogy:",
"allowVideo": "Videó engedélyezése",
"askUnmute": "Kérje a némítás feloldását",
@@ -591,6 +589,7 @@
"mute": "Némítás",
"muteAll": "Mindenkit elnémít",
"muteEveryoneElse": "Mute everyone else",
"reject": "Elutasít",
"stopEveryonesVideo": "Mindenki videójának leállítása",
"stopVideo": "Videó leállítása",
"unblockEveryoneMicCamera": "Unblock everyone's mic and camera",

View File

@@ -561,8 +561,6 @@
"youtubeTerms": "Condizioni di utilizzo di YouTube"
},
"lobby": {
"admit": "Ammetti",
"admitAll": "Ammetti tutti",
"allow": "Autorizza",
"backToKnockModeButton": "Nessuna password, richiedi l'accesso",
"chat": "Conversazione",
@@ -597,8 +595,6 @@
"notificationTitle": "Sala d'attesa",
"passwordField": "Inserisci la password della riunione",
"passwordJoinButton": "Entra",
"reject": "Respingi",
"rejectAll": "Respingi tutti",
"title": "Sala d'attesa",
"toggleLabel": "Attiva sala d'attesa"
},
@@ -725,6 +721,8 @@
},
"participantsPane": {
"actions": {
"admit": "Ammetti",
"admitAll": "Ammetti tutti",
"allow": "Permetti ai partecipanti di:",
"allowVideo": "Autorizza video",
"askUnmute": "Chiedi di accendere microfono",
@@ -737,6 +735,7 @@
"mute": "Silenzia",
"muteAll": "Silenzia tutti",
"muteEveryoneElse": "Silenzia tutti gli altri",
"reject": "Respingi",
"stopEveryonesVideo": "Ferma il video di tutti",
"stopVideo": "Ferma il video",
"unblockEveryoneMicCamera": "Sblocca audio e video a tutti",

View File

@@ -525,8 +525,6 @@
"youtubeTerms": "YouTube サービス利用規約"
},
"lobby": {
"admit": "許可",
"admitAll": "全員許可",
"allow": "許可",
"backToKnockModeButton": "参加を依頼",
"dialogTitle": "ロビーモード",
@@ -557,8 +555,6 @@
"notificationTitle": "ロビー",
"passwordField": "ミーティングパスワードを入力してください",
"passwordJoinButton": "参加",
"reject": "却下",
"rejectAll": "全員却下",
"title": "ロビー",
"toggleLabel": "ロビーを有効"
},
@@ -671,6 +667,8 @@
},
"participantsPane": {
"actions": {
"admit": "許可",
"admitAll": "全員許可",
"allow": "参加者に次のことを許可:",
"allowVideo": "ビデオを許可",
"askUnmute": "ミュート解除を依頼",
@@ -683,6 +681,7 @@
"mute": "ミュート",
"muteAll": "全員をミュート",
"muteEveryoneElse": "他のすべての人をミュート",
"reject": "却下",
"stopEveryonesVideo": "全員のビデオを停止",
"stopVideo": "ビデオを停止",
"unblockEveryoneMicCamera": "全員のマイクとビデオのブロックを解除",

View File

@@ -498,8 +498,6 @@
"youtubeTerms": "Tiwtilin n yimeẓla n Youtube"
},
"lobby": {
"admit": "Steεref",
"admitAll": "Steεref s kullec",
"allow": "Sireg",
"backToKnockModeButton": "Ulac awal uffir, suter attekki deg ubdil-is",
"dialogTitle": "Askar Lobby",
@@ -530,8 +528,6 @@
"notificationTitle": "Taxxamt n uraǧu",
"passwordField": "Sekcem awal uffir n temlilit",
"passwordJoinButton": "Semlil",
"reject": "Agi",
"rejectAll": "Agi akk",
"title": "Taxxamt n uraǧu",
"toggleLabel": "Rmed Lobby"
},
@@ -628,6 +624,8 @@
},
"participantsPane": {
"actions": {
"admit": "Steεref",
"admitAll": "Steεref s kullec",
"allow": "Sireg i yimttekkiyen ad:",
"allowVideo": "Sireg tavidyut",
"askUnmute": "Suter tririt n ṣṣut",
@@ -637,6 +635,7 @@
"mute": "Asusam",
"muteAll": "Sgugem meṛṛa",
"muteEveryoneElse": "Sgugem-iten i meṛṛa",
"reject": "Agi",
"stopEveryonesVideo": "Seḥbes tavidyut n yal yiwen",
"stopVideo": "Seḥbes tavidyut n Youtube",
"unblockEveryoneMicCamera": "Serreḥ i usawaḍ d tkamiṛat n yal yiwen",

View File

@@ -642,8 +642,6 @@
"youtubeTerms": "YouTube pakalpojumu sniegšanas noteikumi"
},
"lobby": {
"admit": "Apstiprināt",
"admitAll": "Apstiprināt visus",
"backToKnockModeButton": "Pajautāt pievienoties",
"chat": "Tērzēšana",
"dialogTitle": "Vestibila režīms",
@@ -677,8 +675,6 @@
"notificationLobbyEnabled": "Vestibilu iespējoja {{originParticipantName}}",
"notificationTitle": "Vestibils",
"passwordJoinButton": "Pievienoties",
"reject": "Noraidīt",
"rejectAll": "Noraidīt visus",
"title": "Vestibils",
"toggleLabel": "Iespējot vestibilu"
},
@@ -816,6 +812,8 @@
},
"participantsPane": {
"actions": {
"admit": "Apstiprināt",
"admitAll": "Apstiprināt visus",
"allow": "Atļaut dalībniekiem:",
"allowVideo": "Atļaut video",
"askUnmute": "Lūgt ieslēgt skaņu",
@@ -829,6 +827,7 @@
"mute": "Apklusināt",
"muteAll": "Apklusināt visus",
"muteEveryoneElse": "Apklusināt pārējos",
"reject": "Noraidīt",
"stopEveryonesVideo": "Izslēgt visiem video",
"stopVideo": "Izslēgt video",
"unblockEveryoneMicCamera": "Atbloķēt visiem mikrofonu un kameru",

View File

@@ -464,7 +464,6 @@
"notificationTitle": "ലോബി",
"passwordField": "മീറ്റിംഗ് പാസ്‌വേഡ് നൽകുക",
"passwordJoinButton": "ചേരുക",
"reject": "നിരസിക്കുക",
"title": "ലോബി",
"toggleLabel": "ലോബി പ്രവർത്തനക്ഷമമാക്കുക"
},
@@ -539,6 +538,11 @@
"suboptimalExperienceTitle": "ബ്രൗസർ മുന്നറിയിപ്പ്",
"unmute": "അൺമ്യൂട്ട്"
},
"participantsPane": {
"actions": {
"reject": "നിരസിക്കുക"
}
},
"passwordDigitsOnly": "{{number}} അക്കങ്ങൾ വരെ",
"passwordSetRemotely": "മറ്റൊരു പങ്കാളി സജ്ജമാക്കിയത്",
"poweredby": "powered by",

View File

@@ -586,8 +586,6 @@
"youtubeTerms": "YouTube үйлчилгээний нөхцөл"
},
"lobby": {
"admit": "Ok",
"admitAll": "Бүгдийг зөвшөөр",
"backToKnockModeButton": "Зөвшөөрөл хүсэх",
"chat": "Зурвас",
"dialogTitle": "Лобби горим",
@@ -621,8 +619,6 @@
"notificationTitle": "Лобби",
"passwordField": "Нууц үгээ оруулна уу",
"passwordJoinButton": "Оролцох",
"reject": "Татгалзах",
"rejectAll": "Бүгдийг татгалзуулах",
"title": "Лобби",
"toggleLabel": "Лобби идэвхижүүлэх"
},
@@ -755,6 +751,8 @@
},
"participantsPane": {
"actions": {
"admit": "Ok",
"admitAll": "Бүгдийг зөвшөөр",
"allow": "Оролцогчийг зөвшөөрөх:",
"allowVideo": "Дүрс зөвшөөрөх",
"askUnmute": "Дуугаа нээхийг хүсэх",
@@ -767,6 +765,7 @@
"mute": "Дуугүй болгох",
"muteAll": "Бүгдийг дуугүй болгох",
"muteEveryoneElse": "Бүгдийг дуугүй болгох",
"reject": "Татгалзах",
"stopEveryonesVideo": "Бүгдийн дүрсийг хаах",
"stopVideo": "Дүрс хаах",
"unblockEveryoneMicCamera": "Бүх хүний микрофон, камерыг нээх",

View File

@@ -486,8 +486,6 @@
"youtubeTerms": "Servicevoorwaarden YouTube"
},
"lobby": {
"admit": "Toelaten",
"admitAll": "Allen toelaten",
"allow": "Toestaan",
"backToKnockModeButton": "Geen wachtwoord, vraag om deel te mogen nemen",
"dialogTitle": "Lobby-modus",
@@ -519,8 +517,6 @@
"notificationTitle": "Lobby",
"passwordField": "Voer wachtwoord voor vergadering in",
"passwordJoinButton": "Deelnemen",
"reject": "Afwijzen",
"rejectAll": "Allen afwijzen",
"title": "Lobby",
"toggleLabel": "Lobby inschakelen"
},
@@ -624,6 +620,8 @@
},
"participantsPane": {
"actions": {
"admit": "Toelaten",
"admitAll": "Allen toelaten",
"allow": "Sta deelnemers toe:",
"allowVideo": "Video toestaan",
"askUnmute": "Vragen om dempen op te heffen",
@@ -636,6 +634,7 @@
"mute": "Dempen",
"muteAll": "Allen dempen",
"muteEveryoneElse": "Alle anderen dempen",
"reject": "Afwijzen",
"stopEveryonesVideo": "Camera's van iedereen uitzetten",
"stopVideo": "Camera uitzetten",
"unblockEveryoneMicCamera": "Deblokkeer microfoon en camera van allen",

View File

@@ -524,8 +524,6 @@
"youtubeTerms": "Condicions dutilizacion de YouTube"
},
"lobby": {
"admit": "Acceptar",
"admitAll": "Tot acceptar",
"allow": "Autorizar",
"backToKnockModeButton": "Cap de senhal, demandar a participar a la plaça",
"dialogTitle": "Mòde sala d'espèra",
@@ -556,8 +554,6 @@
"notificationTitle": "Sala d'espèra",
"passwordField": "Picatz lo senhal de la conferéncia",
"passwordJoinButton": "Rejónher",
"reject": "Regetar",
"rejectAll": "Tot regetar",
"title": "Sala d'espèra",
"toggleLabel": "Activar la sala d'espèra"
},
@@ -670,6 +666,8 @@
},
"participantsPane": {
"actions": {
"admit": "Acceptar",
"admitAll": "Tot acceptar",
"allow": "Permetre als convidats de:",
"allowVideo": "Autorizar la vidèo",
"askUnmute": "Demandar a restablir lo son",
@@ -682,6 +680,7 @@
"mute": "Amudir",
"muteAll": "Amudir tot lo monde",
"muteEveryoneElse": "Amudir tot los demai",
"reject": "Regetar",
"stopEveryonesVideo": "Arrestar la vidèo de tot lo monde",
"stopVideo": "Arrestar la vidèo",
"unblockEveryoneMicCamera": "Desblocar lo microfòn e la camèra de tot lo monde",

View File

@@ -586,8 +586,6 @@
"youtubeTerms": "Warunki użytkowania YouTube"
},
"lobby": {
"admit": "Pozwól",
"admitAll": "Pozwól wszystkim",
"backToKnockModeButton": "Brak hasła, poproś o dołączenie",
"chat": "Chat",
"dialogTitle": "Lobby",
@@ -621,8 +619,6 @@
"notificationTitle": "Lobby",
"passwordField": "Wprowadź hasło",
"passwordJoinButton": "Dołącz",
"reject": "Odrzuć",
"rejectAll": "Odrzuć wszystkich",
"title": "Lobby",
"toggleLabel": "Włącz / Wyłącz lobby"
},
@@ -756,6 +752,8 @@
},
"participantsPane": {
"actions": {
"admit": "Pozwól",
"admitAll": "Pozwól wszystkim",
"allow": "Zezwól uczestnikom na:",
"allowVideo": "Zezwól na wideo",
"askUnmute": "Poproś o wyłączenie wyciszenia",
@@ -768,6 +766,7 @@
"mute": "Wycisz",
"muteAll": "Wycisz wszystkich",
"muteEveryoneElse": "Wycisz pozostałych",
"reject": "Odrzuć",
"stopEveryonesVideo": "Wyłącz wszystkie kamery",
"stopVideo": "Wyłącz kamerę",
"unblockEveryoneMicCamera": "Odblokuj wszystkim kamerę i mikrofon",

View File

@@ -641,8 +641,6 @@
"youtubeTerms": "Termos de serviços do YouTube"
},
"lobby": {
"admit": "Aceitar",
"admitAll": "Aceitar todos",
"backToKnockModeButton": "Peça para aderir",
"chat": "Chat",
"dialogTitle": "Modo sala de espera",
@@ -676,8 +674,6 @@
"notificationLobbyEnabled": "A sala de espera foi activada por {{originParticipantName}}",
"notificationTitle": "Sala de espera",
"passwordJoinButton": "Solicitar",
"reject": "Rejeitar",
"rejectAll": "Rejeitar todos",
"title": "Sala de espera",
"toggleLabel": "Ativar sala de espera"
},
@@ -815,6 +811,8 @@
},
"participantsPane": {
"actions": {
"admit": "Aceitar",
"admitAll": "Aceitar todos",
"allow": "Permitir aos participantes:",
"allowVideo": "Permitir vídeo",
"askUnmute": "Pedir para ligar o som",
@@ -828,6 +826,7 @@
"mute": "Silenciar",
"muteAll": "Silenciar todos",
"muteEveryoneElse": "Silenciar todos os outros",
"reject": "Rejeitar",
"stopEveryonesVideo": "Desligar a câmara de todos",
"stopVideo": "Desligar a câmara",
"unblockEveryoneMicCamera": "Desbloquear o microfone e a câmara de todos",

View File

@@ -642,8 +642,6 @@
"youtubeTerms": "Termos de serviços do YouTube"
},
"lobby": {
"admit": "Aceitar",
"admitAll": "Aceitar todos",
"backToKnockModeButton": "Sem senha, peça para se juntar",
"chat": "Chat",
"dialogTitle": "Modo sala de espera",
@@ -677,8 +675,6 @@
"notificationLobbyEnabled": "Sala de espera foi habilitada por {{originParticipantName}}",
"notificationTitle": "Sala de espera",
"passwordJoinButton": "Solicitar",
"reject": "Rejeitar",
"rejectAll": "Rejeitar todos",
"title": "Sala de espera",
"toggleLabel": "Habilitar sala de espera"
},
@@ -816,6 +812,8 @@
},
"participantsPane": {
"actions": {
"admit": "Aceitar",
"admitAll": "Aceitar todos",
"allow": "Permitir aos participantes:",
"allowVideo": "Permitir vídeo",
"askUnmute": "Pedir para ativar som",
@@ -829,6 +827,7 @@
"mute": "Silenciar",
"muteAll": "Silenciar todos",
"muteEveryoneElse": "Silenciar todos os demais",
"reject": "Rejeitar",
"stopEveryonesVideo": "Parar vídeo de todos",
"stopVideo": "Parar vídeo",
"unblockEveryoneMicCamera": "Desbloquear microfone e câmera de todos",

View File

@@ -557,8 +557,6 @@
"youtubeTerms": "Условия использования YouTube"
},
"lobby": {
"admit": "Признать",
"admitAll": "Признать все",
"backToKnockModeButton": "Попросить присоединиться",
"chat": "Чат",
"dialogTitle": "Режим лобби",
@@ -592,8 +590,6 @@
"notificationTitle": "Лобби",
"passwordField": "Введите пароль встречи",
"passwordJoinButton": "Присоединиться",
"reject": "Отказать",
"rejectAll": "Отказать всем",
"title": "Лобби",
"toggleLabel": "Включить лобби"
},
@@ -720,6 +716,8 @@
},
"participantsPane": {
"actions": {
"admit": "Признать",
"admitAll": "Признать все",
"allow": "Разрешить",
"allowVideo": "Разрешить видео",
"askUnmute": "Попросить разрешение включить микрофон",
@@ -732,6 +730,7 @@
"mute": "Выключить звук",
"muteAll": "Выключить звук у всех",
"muteEveryoneElse": "Выключить микрофон у остальных",
"reject": "Отказать",
"stopEveryonesVideo": "Выключить у всех камеру",
"stopVideo": "Остановить видео",
"unblockEveryoneMicCamera": "Разблокировать у всех микрофон и камеру",

View File

@@ -561,8 +561,6 @@
"youtubeTerms": "Cunditziones de servìtziu de YouTube"
},
"lobby": {
"admit": "Ammite",
"admitAll": "Ammite totu",
"allow": "Permite",
"backToKnockModeButton": "Pedi de intrare",
"chat": "Tzarrada",
@@ -597,8 +595,6 @@
"notificationTitle": "Aposentu de abetu",
"passwordField": "Inserta sa crae de sa riunione",
"passwordJoinButton": "Aderi",
"reject": "Refuda",
"rejectAll": "Refuda totu",
"title": "Aposentu de abetu",
"toggleLabel": "Ativa s'aposentu de abetu"
},
@@ -727,6 +723,8 @@
},
"participantsPane": {
"actions": {
"admit": "Ammite",
"admitAll": "Ammite totu",
"allow": "Permite a is partetzipantes:",
"allowVideo": "Permite vìdeu",
"askUnmute": "Pedi de ativare su micròfonu",
@@ -739,6 +737,7 @@
"mute": "A sa muda",
"muteAll": "Totu a sa muda",
"muteEveryoneElse": "Pone totus a sa muda",
"reject": "Refuda",
"stopEveryonesVideo": "Istuda su vìdeu de totu is partetzipantes",
"stopVideo": "Firma su vìdeu",
"unblockEveryoneMicCamera": "Isbloca su micròfonu e sa càmera de totu is partetzipantes",

View File

@@ -466,7 +466,6 @@
"notificationTitle": "Čakáreň",
"passwordField": "Zadajte heslo do konferencie",
"passwordJoinButton": "Vstúpiť",
"reject": "Odmietnuť",
"title": "Čakáreň",
"toggleLabel": "Zapnúť čakáreň"
},
@@ -541,6 +540,11 @@
"suboptimalExperienceTitle": "Prehliadačové varovanie",
"unmute": "Zapnúť mikrofón"
},
"participantsPane": {
"actions": {
"reject": "Odmietnuť"
}
},
"passwordDigitsOnly": "až {{number}} číslic",
"passwordSetRemotely": "nastavené iným účastníkom",
"poweredby": "založené na",

View File

@@ -499,8 +499,6 @@
"youtubeTerms": "Pogoji uporabe YouTube"
},
"lobby": {
"admit": "Sprejmi",
"admitAll": "Sprejmi vse",
"allow": "Dovoli",
"backToKnockModeButton": "Prosi za dostop",
"dialogTitle": "Način predsobe",
@@ -531,8 +529,6 @@
"notificationTitle": "Predsoba",
"passwordField": "Vnesite geslo sestanka",
"passwordJoinButton": "Pridruži se",
"reject": "Zavrni",
"rejectAll": "Zavrni vse",
"title": "Predsoba",
"toggleLabel": "Omogoči predsobo"
},
@@ -629,6 +625,8 @@
},
"participantsPane": {
"actions": {
"admit": "Sprejmi",
"admitAll": "Sprejmi vse",
"allow": "Udeleženci si lahko:",
"allowVideo": "Dovoli video",
"askUnmute": "Prosi za vklop mikrofona",
@@ -638,6 +636,7 @@
"mute": "Izklopi zvok",
"muteAll": "Izklopi zvok vsem",
"muteEveryoneElse": "Izklopi zvok vsem ostalim",
"reject": "Zavrni",
"stopEveryonesVideo": "Izklopi video vsem ostalim",
"stopVideo": "Izklopi video",
"unblockEveryoneMicCamera": "Dovoli zvok in video vsem udeležencem",

View File

@@ -582,8 +582,6 @@
"youtubeTerms": "Kushte shërbimi YouTube"
},
"lobby": {
"admit": "Pranoje",
"admitAll": "Pranoji të tërë",
"backToKnockModeButton": "Kërkoji të marrë pjesë",
"chat": "Fjalosje",
"dialogTitle": "Mënyra holl",
@@ -617,8 +615,6 @@
"notificationTitle": "Holl",
"passwordField": "Jepni fjalëkalim takimi",
"passwordJoinButton": "Hyni",
"reject": "Hidhe poshtë",
"rejectAll": "Hidhi poshtë të tërë",
"title": "Holl",
"toggleLabel": "Aktivizoni hollin"
},
@@ -749,6 +745,8 @@
},
"participantsPane": {
"actions": {
"admit": "Pranoje",
"admitAll": "Pranoji të tërë",
"allow": "Lejoju pjesëmarrësve të:",
"allowVideo": "Çaktivizoni videon",
"askUnmute": "Kërkoni heqje heshtimi",
@@ -761,6 +759,7 @@
"mute": "Heshtoje",
"muteAll": "Heshtoji të tërë",
"muteEveryoneElse": "Heshto gjithkënd tjetër",
"reject": "Hidhe poshtë",
"stopEveryonesVideo": "Ndal videon e gjithkujt",
"stopVideo": "Ndale videon",
"unblockEveryoneMicCamera": "Zhblloko mikrofonin dhe kamerën e gjithkujt",

View File

@@ -587,8 +587,6 @@
"youtubeTerms": "Tjänstevillkor för YouTube"
},
"lobby": {
"admit": "Godkänn",
"admitAll": "Godkänn alla",
"backToKnockModeButton": "Tillbaka till väntrum",
"chat": "Chatt",
"dialogTitle": "Väntrum",
@@ -622,8 +620,6 @@
"notificationTitle": "Väntrum",
"passwordField": "Ange möteslösenord",
"passwordJoinButton": "Anslut",
"reject": "Avvisa",
"rejectAll": "Avvisa alla",
"title": "Lobby",
"toggleLabel": "Aktivera väntrum"
},
@@ -757,6 +753,8 @@
},
"participantsPane": {
"actions": {
"admit": "Godkänn",
"admitAll": "Godkänn alla",
"allow": "Låt deltagarna:",
"allowVideo": "Tillåt kamera",
"askUnmute": "Be om att aktivera ljud",
@@ -769,6 +767,7 @@
"mute": "Stäng av ljud",
"muteAll": "Stäng av allt ljud",
"muteEveryoneElse": "Inaktivera ljud för alla deltagare",
"reject": "Avvisa",
"stopEveryonesVideo": "Inaktivera allas video",
"stopVideo": "Inaktivera video",
"unblockEveryoneMicCamera": "Aktivera allas mikrofon och kamera",

View File

@@ -455,8 +455,6 @@
"youtubeTerms": "యూట్యూబ్ సేవా నియమాలు"
},
"lobby": {
"admit": "అనుమతించు",
"allow": "అనుమతించు",
"backToKnockModeButton": "సంకేతపదం లేదు, చేర్చుకోమని అడుగు",
"dialogTitle": "Lobby mode",
"disableDialogContent": "Lobby mode is currently enabled. This feature ensures that unwanted participants can't join your meeting. Do you want to disable it?",
@@ -485,7 +483,6 @@
"notificationTitle": "Lobby",
"passwordField": "సమావేశం సంకేతపదం ఇవ్వండి",
"passwordJoinButton": "చేరు",
"reject": "నిరాకరించు",
"title": "Lobby",
"toggleLabel": "Enable lobby"
},
@@ -566,8 +563,11 @@
},
"participantsPane": {
"actions": {
"admit": "అనుమతించు",
"allow": "అనుమతించు",
"invite": "ప్రజలను ఆహ్వానించు",
"muteAll": "అందరినీ మౌనించు",
"reject": "నిరాకరించు",
"stopVideo": "వీడియో ఆపివేయి"
},
"headings": {

View File

@@ -561,8 +561,6 @@
"youtubeTerms": "YouTube hizmet şartları"
},
"lobby": {
"admit": "Kabul et",
"admitAll": "Hepsini kabul et",
"allow": "İzin ver",
"backToKnockModeButton": "Parola yok, bunun yerine katılmayı isteyin",
"chat": "Sohbet et",
@@ -597,8 +595,6 @@
"notificationTitle": "Lobi",
"passwordField": "Toplantı parolasını giriniz",
"passwordJoinButton": "Katıl",
"reject": "Reddet",
"rejectAll": "Hepsini reddet",
"title": "Lobi",
"toggleLabel": "Lobiyi etkinleştir"
},
@@ -725,6 +721,8 @@
},
"participantsPane": {
"actions": {
"admit": "Kabul et",
"admitAll": "Hepsini kabul et",
"allow": "Katılımcıların şunları yapmasına izin ver:",
"allowVideo": "Video'ya izin ver",
"askUnmute": "Sesi açmayı iste",
@@ -737,6 +735,7 @@
"mute": "Sessize al",
"muteAll": "Herkesi sessize al",
"muteEveryoneElse": "Diğer herkesi sessize al",
"reject": "Reddet",
"stopEveryonesVideo": "Herkesin videosunu durdur",
"stopVideo": "Video'yu durdur",
"unblockEveryoneMicCamera": "Herkesin mikrofonunun ve kamerasının engellemesini kaldır",

View File

@@ -584,8 +584,6 @@
"youtubeTerms": "Умови надання послуг YouTube"
},
"lobby": {
"admit": "Допустити",
"admitAll": "Допустити всіх",
"backToKnockModeButton": "Запитати дозволу",
"chat": "Чат",
"dialogTitle": "Приймальна",
@@ -619,8 +617,6 @@
"notificationTitle": "Приймальна",
"passwordField": "Ввести пароль зустрічі",
"passwordJoinButton": "Приєднатися",
"reject": "Відмовити",
"rejectAll": "Відмовити всім",
"title": "Приймальна",
"toggleLabel": "Увімкнути приймальну"
},
@@ -753,6 +749,8 @@
},
"participantsPane": {
"actions": {
"admit": "Допустити",
"admitAll": "Допустити всіх",
"allow": "Дозволити учасникам:",
"allowVideo": "Розблокувати камеру",
"askUnmute": "Надати слово",
@@ -765,6 +763,7 @@
"mute": "Вимкнути мікрофон",
"muteAll": "Вимкнути мікрофони всім",
"muteEveryoneElse": "Вимкнути мікрофони всім іншим",
"reject": "Відмовити",
"stopEveryonesVideo": "Вимкнути камери всім",
"stopVideo": "Вимкнути камеру",
"unblockEveryoneMicCamera": "Розблокувати всім мікрофон і камеру",

View File

@@ -627,8 +627,6 @@
"youtubeTerms": "YouTube服务条款"
},
"lobby": {
"admit": "同意",
"admitAll": "同意全部",
"backToKnockModeButton": "请求加入",
"chat": "聊天",
"dialogTitle": "大厅模式",
@@ -662,8 +660,6 @@
"notificationTitle": "大厅",
"passwordField": "输入会议密码",
"passwordJoinButton": "加入",
"reject": "拒绝",
"rejectAll": "拒绝全部",
"title": "大厅",
"toggleLabel": "开启大厅模式"
},
@@ -798,6 +794,8 @@
},
"participantsPane": {
"actions": {
"admit": "同意",
"admitAll": "同意全部",
"allow": "允许参会者:",
"allowVideo": "允许视频",
"askUnmute": "请求解除静音",
@@ -810,6 +808,7 @@
"mute": "静音",
"muteAll": "全体静音",
"muteEveryoneElse": "全体静音",
"reject": "拒绝",
"stopEveryonesVideo": "禁用所有人视频",
"stopVideo": "禁用视频",
"unblockEveryoneMicCamera": "允许所有人的麦克风和摄像头",

View File

@@ -641,8 +641,6 @@
"youtubeTerms": "YouTube 服務條款"
},
"lobby": {
"admit": "準許",
"admitAll": "準許所有人",
"backToKnockModeButton": "請求加入",
"chat": "聊天",
"dialogTitle": "大廳模式",
@@ -676,8 +674,6 @@
"notificationTitle": "大廳",
"passwordField": "輸入會議密碼",
"passwordJoinButton": "加入",
"reject": "拒絕",
"rejectAll": "拒絕所有人",
"title": "大廳",
"toggleLabel": "啟用大廳模式"
},
@@ -815,6 +811,8 @@
},
"participantsPane": {
"actions": {
"admit": "準許",
"admitAll": "準許所有人",
"allow": "允許與會者能夠:",
"allowVideo": "允許視訊",
"askUnmute": "要求解除靜音",
@@ -828,6 +826,7 @@
"mute": "靜音",
"muteAll": "靜音所有人",
"muteEveryoneElse": "靜音其他人",
"reject": "拒絕",
"stopEveryonesVideo": "停用所有人的視訊",
"stopVideo": "停用視訊",
"unblockEveryoneMicCamera": "解除封鎖所有人的麥克風及網路攝影機",

View File

@@ -642,8 +642,6 @@
"youtubeTerms": "YouTube terms of services"
},
"lobby": {
"admit": "Admit",
"admitAll": "Admit all",
"backToKnockModeButton": "Ask to join",
"chat": "Chat",
"dialogTitle": "Lobby mode",
@@ -677,8 +675,6 @@
"notificationLobbyEnabled": "Lobby has been enabled by {{originParticipantName}}",
"notificationTitle": "Lobby",
"passwordJoinButton": "Join",
"reject": "Reject",
"rejectAll": "Reject all",
"title": "Lobby",
"toggleLabel": "Enable lobby"
},
@@ -816,6 +812,8 @@
},
"participantsPane": {
"actions": {
"admit": "Admit",
"admitAll": "Admit all",
"allow": "Allow attendees to:",
"allowVideo": "Allow video",
"askUnmute": "Ask to unmute",
@@ -829,6 +827,7 @@
"mute": "Mute",
"muteAll": "Mute all",
"muteEveryoneElse": "Mute everyone else",
"reject": "Reject",
"stopEveryonesVideo": "Stop everyone's video",
"stopVideo": "Stop video",
"unblockEveryoneMicCamera": "Unblock everyone's mic and camera",
@@ -838,7 +837,8 @@
"headings": {
"lobby": "Lobby ({{count}})",
"participantsList": "Meeting participants ({{count}})",
"visitors": "Visitors ({{count}})",
"visitorRequests": " (requests {{count}})",
"visitors": "Visitors {{count}}",
"waitingLobby": "Waiting in lobby ({{count}})"
},
"search": "Search participants",
@@ -1016,12 +1016,15 @@
"onlyRecordSelf": "Record only my audio and video streams",
"pending": "Preparing to record the meeting...",
"rec": "REC",
"recordAudioAndVideo": "Record audio and video",
"recordTranscription": "Record transcription",
"saveLocalRecording": "Save recording file locally (Beta)",
"serviceDescription": "Your recording will be saved by the recording service",
"serviceDescriptionCloud": "Cloud recording",
"serviceDescriptionCloudInfo": "Recorded meetings are automatically cleared 24h after their recording time.",
"serviceName": "Recording service",
"sessionAlreadyActive": "This session is already being recorded or live streamed.",
"showAdvancedOptions": "Advanced options",
"signIn": "Sign in",
"signOut": "Sign out",
"surfaceError": "Please select the current tab.",

View File

@@ -466,8 +466,8 @@ function initCommands() {
'toggle-subtitles': () => {
APP.store.dispatch(toggleRequestingSubtitles());
},
'set-subtitles': enabled => {
APP.store.dispatch(setRequestingSubtitles(enabled));
'set-subtitles': (enabled, displaySubtitles, language) => {
APP.store.dispatch(setRequestingSubtitles(enabled, displaySubtitles, language));
},
'toggle-tile-view': () => {
sendAnalytics(createApiEvent('tile-view.toggled'));
@@ -835,8 +835,6 @@ function initCommands() {
return true;
}
logger.warn(`Unknown API command received: ${name}`);
return false;
});
transport.on('request', (request, callback) => {

10
package-lock.json generated
View File

@@ -59,7 +59,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/v1734.0.0+34ceebd2/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1736.0.0+8bee4514/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -11860,8 +11860,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1734.0.0+34ceebd2/lib-jitsi-meet.tgz",
"integrity": "sha512-mHWUJ8Q4uhFsx2EZoRhgq8iGgitXig9hZ+uOuHana2YWjj1ZU0GhS5fl7VGr+LxtsonclQBBbGDt8/JkNLcfgg==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1736.0.0+8bee4514/lib-jitsi-meet.tgz",
"integrity": "sha512-GgbRX3fZKjhYpmC9PBx1iXML9S8oWXGkUAzVh3gbWhwPPz9dGFScEzUt90N7v+Z3JMQELHn1XoUbzHtPe08yHA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -27399,8 +27399,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1734.0.0+34ceebd2/lib-jitsi-meet.tgz",
"integrity": "sha512-mHWUJ8Q4uhFsx2EZoRhgq8iGgitXig9hZ+uOuHana2YWjj1ZU0GhS5fl7VGr+LxtsonclQBBbGDt8/JkNLcfgg==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1736.0.0+8bee4514/lib-jitsi-meet.tgz",
"integrity": "sha512-GgbRX3fZKjhYpmC9PBx1iXML9S8oWXGkUAzVh3gbWhwPPz9dGFScEzUt90N7v+Z3JMQELHn1XoUbzHtPe08yHA==",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",

View File

@@ -65,7 +65,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/v1734.0.0+34ceebd2/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1736.0.0+8bee4514/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -13,9 +13,12 @@ import React, {
} from 'react';
import { View, ViewStyle } from 'react-native';
import type { IRoomsInfo } from '../react/features/breakout-rooms/types';
import { appNavigate } from './react/features/app/actions.native';
import { App } from './react/features/app/components/App.native';
import { setAudioMuted, setVideoMuted } from './react/features/base/media/actions';
import { getRoomsInfo } from './react/features/breakout-rooms/functions';
interface IEventListeners {
@@ -28,6 +31,7 @@ interface IEventListeners {
onConferenceWillJoin?: Function;
onEnterPictureInPicture?: Function;
onParticipantJoined?: Function;
onParticipantLeft?: ({ id }: { id: string }) => void;
onReadyToClose?: Function;
}
@@ -48,10 +52,17 @@ interface IAppProps {
userInfo?: IUserInfo;
}
export interface JitsiRefProps {
close: Function;
setAudioMuted?: (muted: boolean) => void;
setVideoMuted?: (muted: boolean) => void;
getRoomsInfo?: () => IRoomsInfo;
}
/**
* Main React Native SDK component that displays a Jitsi Meet conference and gets all required params as props
*/
export const JitsiMeeting = forwardRef((props: IAppProps, ref) => {
export const JitsiMeeting = forwardRef<JitsiRefProps, IAppProps>((props, ref) => {
const [ appProps, setAppProps ] = useState({});
const app = useRef(null);
const {
@@ -81,6 +92,11 @@ export const JitsiMeeting = forwardRef((props: IAppProps, ref) => {
const dispatch = app.current.state.store.dispatch;
dispatch(setVideoMuted(muted));
},
getRoomsInfo: () => {
const state = app.current.state.store.getState();
return getRoomsInfo(state);
}
}));
@@ -118,6 +134,7 @@ export const JitsiMeeting = forwardRef((props: IAppProps, ref) => {
onConferenceLeft: eventListeners?.onConferenceLeft,
onEnterPictureInPicture: eventListeners?.onEnterPictureInPicture,
onParticipantJoined: eventListeners?.onParticipantJoined,
onParticipantLeft: eventListeners?.onParticipantLeft,
onReadyToClose: eventListeners?.onReadyToClose
},
'url': urlProps,

View File

@@ -159,10 +159,9 @@ export async function createHandlers({ getState }: IStore) {
*
* @param {Store} store - The redux store in which the specified {@code action} is being dispatched.
* @param {Array<Object>} handlers - The analytics handlers.
* @param {boolean|undefined} willShowPrejoin -
* @returns {void}
*/
export function initAnalytics(store: IStore, handlers: Array<Object>, willShowPrejoin?: boolean) {
export function initAnalytics(store: IStore, handlers: Array<Object>) {
const { getState, dispatch } = store;
if (!isAnalyticsEnabled(getState) || handlers.length === 0) {
@@ -213,7 +212,7 @@ export function initAnalytics(store: IStore, handlers: Array<Object>, willShowPr
// Report the tenant from the URL.
permanentProperties.tenant = tenant || '/';
permanentProperties.wasPrejoinDisplayed = willShowPrejoin ?? isPrejoinPageVisible(state);
permanentProperties.wasPrejoinDisplayed = isPrejoinPageVisible(state);
// Currently we don't know if there will be lobby. We will update it to true if we go through lobby.
permanentProperties.wasLobbyVisible = false;

View File

@@ -114,7 +114,7 @@ MiddlewareRegistry.register(store => next => action => {
const result = next(action);
createHandlersPromise.then(handlers => {
initAnalytics(store, handlers, action.willShowPrejoin);
initAnalytics(store, handlers);
});
return result;

View File

@@ -150,20 +150,9 @@ export function appNavigate(uri?: string, options: IReloadNowOptions = {}) {
return;
}
let willShowPrejoin = false;
let willShowUnsafeRoomWarning = false;
if (!options.hidePrejoin && isPrejoinPageEnabled(getState()) && room) {
if (isUnsafeRoomWarningEnabled(getState()) && isInsecureRoomName(room)) {
willShowUnsafeRoomWarning = true;
} else {
willShowPrejoin = true;
}
}
dispatch(setLocationURL(locationURL));
dispatch(setConfig(config));
dispatch(setRoom(room, willShowPrejoin));
dispatch(setRoom(room));
if (!room) {
goBackToRoot(getState(), dispatch);
@@ -174,10 +163,12 @@ export function appNavigate(uri?: string, options: IReloadNowOptions = {}) {
dispatch(createDesiredLocalTracks());
dispatch(clearNotifications());
if (willShowUnsafeRoomWarning) {
navigateRoot(screen.unsafeRoomWarning);
} else if (willShowPrejoin) {
navigateRoot(screen.preJoin);
if (!options.hidePrejoin && isPrejoinPageEnabled(getState())) {
if (isUnsafeRoomWarningEnabled(getState()) && isInsecureRoomName(room)) {
navigateRoot(screen.unsafeRoomWarning);
} else {
navigateRoot(screen.preJoin);
}
} else {
dispatch(connect());
navigateRoot(screen.conference.root);

View File

@@ -900,20 +900,15 @@ export function setObfuscatedRoom(obfuscatedRoom: string, obfuscatedRoomSource:
*
* @param {(string|undefined)} room - The name of the room of the conference to
* be joined.
* @param {boolean|undefined} willShowPrejoin - Whether the prejoin should be hidden or not.
* NOTE: This argument is used only for mobile currently!
*
* @returns {{
* type: SET_ROOM,
* room: string,
* willShowPrejoin: boolean
* room: string
* }}
*/
export function setRoom(room?: string, willShowPrejoin?: boolean) {
export function setRoom(room?: string) {
return {
type: SET_ROOM,
room,
willShowPrejoin
room
};
}

View File

@@ -583,7 +583,7 @@ export interface IConfig {
transcribeWithAppLanguage?: boolean;
transcribingEnabled?: boolean;
transcription?: {
autoCaptionOnRecord?: boolean;
autoTranscribeOnRecord?: boolean;
disableStartForAll?: boolean;
enabled?: boolean;
preferredLanguage?: string;

View File

@@ -465,7 +465,7 @@ function _translateLegacyConfig(oldValue: IConfig) {
if (oldValue.autoCaptionOnRecord !== undefined) {
newValue.transcription = {
...newValue.transcription,
autoCaptionOnRecord: oldValue.autoCaptionOnRecord
autoTranscribeOnRecord: oldValue.autoCaptionOnRecord
};
}

View File

@@ -40,14 +40,6 @@ export const LANGUAGES: Array<string> = Object.keys(LANGUAGES_RESOURCES);
*/
export const TRANSLATION_LANGUAGES: Array<string> = Object.keys(TRANSLATION_LANGUAGES_RESOURCES);
/**
* The available/supported translation languages head. (Languages displayed on the top ).
*
* @public
* @type {Array<string>}
*/
export const TRANSLATION_LANGUAGES_HEAD: Array<string> = [ 'en' ];
/**
* The default language.
*
@@ -58,6 +50,14 @@ export const TRANSLATION_LANGUAGES_HEAD: Array<string> = [ 'en' ];
*/
export const DEFAULT_LANGUAGE = 'en';
/**
* The available/supported translation languages head. (Languages displayed on the top ).
*
* @public
* @type {Array<string>}
*/
export const TRANSLATION_LANGUAGES_HEAD: Array<string> = [ DEFAULT_LANGUAGE ];
/**
* The options to initialize i18next with.
*

View File

@@ -7,7 +7,6 @@ import { PARTICIPANT_LEFT } from '../participants/actionTypes';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import JitsiMeetJS from './_';
import { LIB_WILL_INIT } from './actionTypes';
import { disposeLib, initLib } from './actions';
/**
@@ -22,14 +21,6 @@ import { disposeLib, initLib } from './actions';
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case LIB_WILL_INIT:
// Moved from conference.js init method. It appears the error handlers
// are not used for mobile.
if (typeof APP !== 'undefined') {
_setErrorHandlers();
}
break;
case SET_NETWORK_INFO:
JitsiMeetJS.setNetworkInfo({
isOnline: action.isOnline
@@ -81,47 +72,3 @@ function _setConfig({ dispatch, getState }: IStore, next: Function, action: AnyA
return result;
}
/**
* Attaches our custom error handlers to the window object.
*
* @returns {void}
*/
function _setErrorHandlers() {
// attaches global error handler, if there is already one, respect it
if (JitsiMeetJS.getGlobalOnErrorHandler) {
const oldOnErrorHandler = window.onerror;
// TODO: Don't remove this ignore. The build fails on macOS and we don't know yet why.
// @ts-ignore
window.onerror = (message, source, lineno, colno, error) => { // eslint-disable-line max-params
const errMsg = message || error?.message;
const stack = error?.stack;
JitsiMeetJS.getGlobalOnErrorHandler(errMsg, source, lineno, colno, stack);
if (oldOnErrorHandler) {
oldOnErrorHandler(message, source, lineno, colno, error);
}
};
const oldOnUnhandledRejection = window.onunhandledrejection;
window.onunhandledrejection = function(event) {
let message = event.reason;
let stack: string | undefined = 'n/a';
if (event.reason instanceof Error) {
({ message, stack } = event.reason);
}
JitsiMeetJS.getGlobalOnErrorHandler(message, null, null, null, stack);
if (oldOnUnhandledRejection) {
// @ts-ignore
oldOnUnhandledRejection(event);
}
};
}
}

View File

@@ -1,8 +1,8 @@
// @ts-expect-error
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
import { IStore } from '../app/types';
import { MEDIA_TYPE } from '../base/media/constants';
import { getTrackByMediaTypeAndParticipant } from '../base/tracks/functions.web';
import { getParticipantById } from '../base/participants/functions';
import { getVideoTrackByParticipant } from '../base/tracks/functions.web';
import { SET_SEE_WHAT_IS_BEING_SHARED } from './actionTypes';
@@ -19,11 +19,12 @@ export function captureLargeVideoScreenshot() {
const largeVideo = state['features/large-video'];
const promise = Promise.resolve();
if (!largeVideo) {
if (!largeVideo?.participantId) {
return promise;
}
const tracks = state['features/base/tracks'];
const participantTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, largeVideo.participantId);
const participant = getParticipantById(state, largeVideo.participantId);
const participantTrack = getVideoTrackByParticipant(state, participant);
// Participants that join the call video muted do not have a jitsiTrack attached.
if (!participantTrack?.jitsiTrack) {

View File

@@ -207,7 +207,7 @@ function _handleLobbyNotification(store: IStore) {
descriptionKey = 'notify.participantWantsToJoin';
notificationTitle = firstParticipant.name;
icon = NOTIFICATION_ICON.PARTICIPANT;
customActionNameKey = [ 'lobby.admit', 'lobby.reject' ];
customActionNameKey = [ 'participantsPane.actions.admit', 'participantsPane.actions.reject' ];
customActionType = [ BUTTON_TYPES.PRIMARY, BUTTON_TYPES.DESTRUCTIVE ];
customActionHandler = [ () => batch(() => {
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));

View File

@@ -154,10 +154,11 @@ function _conferenceFailed({ getState }: IStore, next: Function, action: AnyActi
// prevented the user from joining a specific conference but the app may be
// able to eventually join the conference.
if (!action.error.recoverable) {
const { callUUID } = action.conference;
if (action?.conference?.callUUID) {
if (callUUID) {
delete action.conference.callUUID;
CallIntegration.reportCallFailed(action.conference.callUUIDID);
CallIntegration.reportCallFailed(callUUID);
}
}
@@ -184,9 +185,9 @@ function _conferenceJoined({ getState }: IStore, next: Function, action: AnyActi
return result;
}
if (action?.conference?.callUUID) {
const { callUUID } = action.conference;
const { callUUID } = action.conference;
if (callUUID) {
CallIntegration.reportConnectedOutgoingCall(callUUID)
.then(() => {
// iOS 13 doesn't like the mute state to be false before the call is started
@@ -229,9 +230,11 @@ function _conferenceLeft({ getState }: IStore, next: Function, action: AnyAction
return result;
}
if (action?.conference?.callUUID) {
const { callUUID } = action.conference;
if (callUUID) {
delete action.conference.callUUID;
CallIntegration.endCall(action.conference.callUUID);
CallIntegration.endCall(callUUID);
}
return result;
@@ -270,7 +273,7 @@ function _conferenceWillJoin({ dispatch, getState }: IStore, next: Function, act
}
// When assigning the call UUID, do so in upper case, since iOS will return
// it upper cased.
// it upper-cased.
conference.callUUID = (callUUID || uuidv4()).toUpperCase();
CallIntegration.startCall(conference.callUUID, handle, hasVideo)

View File

@@ -379,9 +379,10 @@ function _registerForNativeEvents(store: IStore) {
dispatch(sendMessage(message));
});
eventEmitter.addListener(ExternalAPI.SET_CLOSED_CAPTIONS_ENABLED, ({ enabled }: any) => {
dispatch(setRequestingSubtitles(enabled));
});
eventEmitter.addListener(ExternalAPI.SET_CLOSED_CAPTIONS_ENABLED,
({ enabled, displaySubtitles, language }: any) => {
dispatch(setRequestingSubtitles(enabled, displaySubtitles, language));
});
eventEmitter.addListener(ExternalAPI.TOGGLE_CAMERA, () => {
dispatch(toggleCameraFacingMode());

View File

@@ -9,7 +9,7 @@ import {
CONFERENCE_WILL_JOIN
} from '../../base/conference/actionTypes';
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../../base/media/actionTypes';
import { PARTICIPANT_JOINED } from '../../base/participants/actionTypes';
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../../base/participants/actionTypes';
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
import { READY_TO_CLOSE } from '../external-api/actionTypes';
@@ -63,6 +63,14 @@ const { JMOngoingConference } = NativeModules;
rnSdkHandlers?.onParticipantJoined && rnSdkHandlers?.onParticipantJoined(participantInfo);
break;
}
case PARTICIPANT_LEFT: {
const { participant } = action;
const { id } = participant ?? {};
rnSdkHandlers?.onParticipantLeft && rnSdkHandlers?.onParticipantLeft({ id });
break;
}
case READY_TO_CLOSE:
rnSdkHandlers?.onReadyToClose && rnSdkHandlers?.onReadyToClose();
break;

View File

@@ -58,7 +58,7 @@ const ContextMenuLobbyParticipantReject = ({ participant: p }: IProps) => {
<Icon
size = { 24 }
src = { IconCloseLarge } />
<Text style = { styles.contextMenuItemText }>{ t('lobby.reject') }</Text>
<Text style = { styles.contextMenuItemText }>{ t('participantsPane.actions.reject') }</Text>
</TouchableOpacity>
</BottomSheet>
);

View File

@@ -19,8 +19,8 @@ interface IProps {
export const LobbyParticipantItem = ({ participant: p }: IProps) => {
const dispatch = useDispatch();
const admit = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, true)), [ dispatch ]);
const reject = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, false)), [ dispatch ]);
const admit = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, true)), [ dispatch, p.id ]);
const reject = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, false)), [ dispatch, p.id ]);
return (
<ParticipantItem
@@ -29,16 +29,16 @@ export const LobbyParticipantItem = ({ participant: p }: IProps) => {
key = { p.id }
participantID = { p.id } >
<Button
accessibilityLabel = 'lobby.reject'
labelKey = 'lobby.reject'
accessibilityLabel = 'participantsPane.actions.reject'
labelKey = 'participantsPane.actions.reject'
onClick = { reject }
style = { styles.lobbyButtonReject }
style = { styles.buttonReject }
type = { BUTTON_TYPES.DESTRUCTIVE } />
<Button
accessibilityLabel = 'lobby.admit'
labelKey = 'lobby.admit'
accessibilityLabel = 'participantsPane.actions.admit'
labelKey = 'participantsPane.actions.admit'
onClick = { admit }
style = { styles.lobbyButtonAdmit }
style = { styles.buttonAdmit }
type = { BUTTON_TYPES.PRIMARY } />
</ParticipantItem>
);

View File

@@ -28,15 +28,15 @@ const LobbyParticipantList = () => {
return (
<>
<View style = { styles.lobbyListDetails as ViewStyle } >
<View style = { styles.listDetails as ViewStyle } >
<Text style = { styles.lobbyListDescription as TextStyle }>
{ title }
</Text>
{
participants.length > 1 && (
<Button
accessibilityLabel = 'lobby.admitAll'
labelKey = 'lobby.admitAll'
accessibilityLabel = 'participantsPane.actions.admitAll'
labelKey = 'participantsPane.actions.admitAll'
mode = { BUTTON_MODES.TEXT }
onClick = { admitAll }
type = { BUTTON_TYPES.PRIMARY } />

View File

@@ -69,19 +69,9 @@ const MeetingParticipantList = () => {
: t('participantsPane.headings.participantsList',
{ count: participantsCount });
const { color, shareDialogVisible } = inviteOthersControl;
const visitorsCount = useSelector((state: IReduxState) => state['features/visitors'].count || 0);
const visitorsLabelText = visitorsCount > 0
? t('participantsPane.headings.visitors', { count: visitorsCount })
: undefined;
return (
<View style = { styles.meetingListContainer }>
{
visitorsCount > 0
&& <Text style = { styles.visitorsLabel }>
{ visitorsLabelText }
</Text>
}
<Text
style = { styles.meetingListDescription as TextStyle }>
{ title }

View File

@@ -8,6 +8,7 @@ import { isLocalParticipantModerator } from '../../../base/participants/function
import LobbyParticipantList from './LobbyParticipantList';
import MeetingParticipantList from './MeetingParticipantList';
import ParticipantsPaneFooter from './ParticipantsPaneFooter';
import VisitorsList from './VisitorsList';
import styles from './styles';
@@ -32,6 +33,7 @@ const ParticipantsPane = () => {
// eslint-disable-next-line react/jsx-no-bind
ListHeaderComponent = { () => (
<>
<VisitorsList />
<LobbyParticipantList />
<MeetingParticipantList />
</>

View File

@@ -0,0 +1,46 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import Button from '../../../base/ui/components/native/Button';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import { approveRequest, denyRequest } from '../../../visitors/actions';
import { IPromotionRequest } from '../../../visitors/types';
import ParticipantItem from './ParticipantItem';
import styles from './styles';
interface IProps {
/**
* Promotion request reference.
*/
request: IPromotionRequest;
}
export const VisitorsItem = ({ request: r }: IProps) => {
const dispatch = useDispatch();
const admit = useCallback(() => dispatch(approveRequest(r)), [ dispatch, r ]);
const reject = useCallback(() => dispatch(denyRequest(r)), [ dispatch, r ]);
const { from, nick } = r;
return (
<ParticipantItem
displayName = { nick ?? '' }
isKnockingParticipant = { true }
key = { from }
participantID = { from } >
<Button
accessibilityLabel = 'participantsPane.actions.reject'
labelKey = 'participantsPane.actions.reject'
onClick = { reject }
style = { styles.buttonReject }
type = { BUTTON_TYPES.DESTRUCTIVE } />
<Button
accessibilityLabel = 'participantsPane.actions.admit'
labelKey = 'participantsPane.actions.admit'
onClick = { admit }
style = { styles.buttonAdmit }
type = { BUTTON_TYPES.PRIMARY } />
</ParticipantItem>
);
};

View File

@@ -0,0 +1,65 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Text, View, ViewStyle } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import Button from '../../../base/ui/components/native/Button';
import { BUTTON_MODES, BUTTON_TYPES } from '../../../base/ui/constants.native';
import { admitMultiple } from '../../../visitors/actions';
import { getPromotionRequests } from '../../../visitors/functions';
import { VisitorsItem } from './VisitorsItem';
import styles from './styles';
const VisitorsList = () => {
const visitorsCount = useSelector((state: IReduxState) => state['features/visitors'].count || 0);
const dispatch = useDispatch();
const requests = useSelector(getPromotionRequests);
const admitAll = useCallback(() => {
dispatch(admitMultiple(requests));
}, [ dispatch, requests ]);
const { t } = useTranslation();
if (visitorsCount <= 0) {
return null;
}
let title = t('participantsPane.headings.visitors', { count: visitorsCount });
if (requests.length > 0) {
title += t('participantsPane.headings.visitorRequests', { count: requests.length });
}
return (
<>
<View style = { styles.listDetails as ViewStyle } >
<Text style = { styles.visitorsLabel }>
{ title }
</Text>
{
requests.length > 1 && (
<Button
accessibilityLabel = 'participantsPane.actions.admitAll'
labelKey = 'participantsPane.actions.admitAll'
mode = { BUTTON_MODES.TEXT }
onClick = { admitAll }
type = { BUTTON_TYPES.PRIMARY } />
)
}
</View>
{
requests.map(r => (
<VisitorsItem
key = { r.from }
request = { r } />)
)
}
</>
);
};
export default VisitorsList;

View File

@@ -164,12 +164,12 @@ export default {
color: BaseTheme.palette.uiBackground
},
lobbyButtonAdmit: {
buttonAdmit: {
position: 'absolute',
right: 16
},
lobbyButtonReject: {
buttonReject: {
position: 'absolute',
right: 112
},
@@ -178,7 +178,7 @@ export default {
...participantListDescription
},
lobbyListDetails: {
listDetails: {
alignItems: 'center',
display: 'flex',
flexDirection: 'row',

View File

@@ -72,9 +72,9 @@ export const LobbyParticipantItem = ({
const renderAdmitButton = () => (
<Button
accessibilityLabel = { `${t('lobby.admit')} ${p.name}` }
accessibilityLabel = { `${t('participantsPane.actions.admit')} ${p.name}` }
className = { styles.button }
labelKey = { 'lobby.admit' }
labelKey = { 'participantsPane.actions.admit' }
onClick = { admit }
size = 'small'
testId = { `admit-${id}` } />);
@@ -116,18 +116,18 @@ export const LobbyParticipantItem = ({
} ] } />
<ContextMenuItemGroup
actions = { [ {
accessibilityLabel: `${t('lobby.reject')} ${p.name}`,
accessibilityLabel: `${t('participantsPane.actions.reject')} ${p.name}`,
onClick: reject,
testId: `reject-${id}`,
icon: IconUserDeleted,
text: t('lobby.reject')
text: t('participantsPane.actions.reject')
} ] } />
</ContextMenu>
</> : <>
<Button
accessibilityLabel = { `${t('lobby.reject')} ${p.name}` }
accessibilityLabel = { `${t('participantsPane.actions.reject')} ${p.name}` }
className = { styles.button }
labelKey = { 'lobby.reject' }
labelKey = { 'participantsPane.actions.reject' }
onClick = { reject }
size = 'small'
testId = { `reject-${id}` }

View File

@@ -91,7 +91,7 @@ export default function LobbyParticipants() {
participants.length > 1
&& <div
className = { classes.link }
onClick = { admitAll }>{t('lobby.admitAll')}</div>
onClick = { admitAll }>{t('participantsPane.actions.admitAll')}</div>
}
</div>
<LobbyParticipantItems
@@ -117,7 +117,7 @@ export default function LobbyParticipants() {
className = { classes.icon }
size = { 20 }
src = { IconCheck } />
<span>{ t('lobby.admit') }</span>
<span>{ t('participantsPane.actions.admit') }</span>
</li>
<li
className = { classes.drawerItem }
@@ -126,7 +126,7 @@ export default function LobbyParticipants() {
className = { classes.icon }
size = { 20 }
src = { IconCloseLarge } />
<span>{ t('lobby.reject')}</span>
<span>{ t('participantsPane.actions.reject')}</span>
</li>
</ul>
</Drawer>

View File

@@ -105,10 +105,9 @@ function MeetingParticipants({
const participantActionEllipsisLabel = t('participantsPane.actions.moreParticipantOptions');
const youText = t('chat.you');
const isBreakoutRoom = useSelector(isInBreakoutRoom);
const visitorsCount = useSelector((state: IReduxState) => state['features/visitors'].count || 0);
const _isCurrentRoomRenamable = useSelector(isCurrentRoomRenamable);
const { classes: styles, cx } = useStyles();
const { classes: styles } = useStyles();
return (
<>
@@ -118,11 +117,6 @@ function MeetingParticipants({
role = 'heading'>
{ t('participantsPane.title') }
</span>
{visitorsCount > 0 && (
<div className = { cx(styles.heading, styles.headingW) }>
{t('participantsPane.headings.visitors', { count: visitorsCount })}
</div>
)}
<div className = { styles.heading }>
{currentRoom?.name
? `${currentRoom.name} (${participantsCount})`

View File

@@ -26,7 +26,7 @@ import { RoomList } from '../breakout-rooms/components/web/RoomList';
import { FooterContextMenu } from './FooterContextMenu';
import LobbyParticipants from './LobbyParticipants';
import MeetingParticipants from './MeetingParticipants';
import VisitorsList from './VisitorsList';
const useStyles = makeStyles()(theme => {
return {
@@ -171,6 +171,8 @@ const ParticipantsPane = () => {
onClick = { onClosePane } />
</div>
<div className = { classes.container }>
<VisitorsList />
<br className = { classes.antiCollapse } />
<LobbyParticipants />
<br className = { classes.antiCollapse } />
<MeetingParticipants

View File

@@ -0,0 +1,80 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import Button from '../../../base/ui/components/web/Button';
import { BUTTON_TYPES } from '../../../base/ui/constants.web';
import { approveRequest, denyRequest } from '../../../visitors/actions';
import { IPromotionRequest } from '../../../visitors/types';
import { ACTION_TRIGGER, MEDIA_STATE } from '../../constants';
import ParticipantItem from './ParticipantItem';
interface IProps {
/**
* Promotion request reference.
*/
request: IPromotionRequest;
}
const useStyles = makeStyles()(theme => {
return {
button: {
marginRight: theme.spacing(2)
},
moreButton: {
paddingRight: '6px',
paddingLeft: '6px',
marginRight: theme.spacing(2)
},
contextMenu: {
position: 'fixed',
top: 'auto',
marginRight: '8px'
}
};
});
export const VisitorsItem = ({
request: r
}: IProps) => {
const { from, nick } = r;
const { t } = useTranslation();
const { classes: styles } = useStyles();
const dispatch = useDispatch();
const admit = useCallback(() => dispatch(approveRequest(r)), [ dispatch, r ]);
const reject = useCallback(() => dispatch(denyRequest(r)), [ dispatch, r ]);
return (
<ParticipantItem
actionsTrigger = { ACTION_TRIGGER.PERMANENT }
audioMediaState = { MEDIA_STATE.NONE }
displayName = { nick }
participantID = { from }
raisedHand = { true }
videoMediaState = { MEDIA_STATE.NONE }
youText = { t('chat.you') }>
{<>
<Button
accessibilityLabel = { `${t('participantsPane.actions.reject')} ${r.nick}` }
className = { styles.button }
labelKey = 'participantsPane.actions.reject'
onClick = { reject }
size = 'small'
testId = { `reject-${from}` }
type = { BUTTON_TYPES.DESTRUCTIVE } />
<Button
accessibilityLabel = { `${t('participantsPane.actions.admit')} ${r.nick}` }
className = { styles.button }
labelKey = 'participantsPane.actions.admit'
onClick = { admit }
size = 'small'
testId = { `admit-${from}` } />
</>
}
</ParticipantItem>
);
};

View File

@@ -0,0 +1,111 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../app/types';
import { withPixelLineHeight } from '../../../base/styles/functions.web';
import { admitMultiple } from '../../../visitors/actions';
import { getPromotionRequests } from '../../../visitors/functions';
import { VisitorsItem } from './VisitorsItem';
const useStyles = makeStyles()(theme => {
return {
container: {
margin: `${theme.spacing(3)} 0`
},
headingW: {
color: theme.palette.warning02
},
drawerActions: {
listStyleType: 'none',
margin: 0,
padding: 0
},
drawerItem: {
alignItems: 'center',
color: theme.palette.text01,
display: 'flex',
padding: '12px 16px',
...withPixelLineHeight(theme.typography.bodyShortRegularLarge),
'&:first-child': {
marginTop: '15px'
},
'&:hover': {
cursor: 'pointer',
background: theme.palette.action02
}
},
icon: {
marginRight: 16
},
headingContainer: {
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between'
},
heading: {
...withPixelLineHeight(theme.typography.bodyShortBold),
color: theme.palette.text02
},
link: {
...withPixelLineHeight(theme.typography.labelBold),
color: theme.palette.link01,
cursor: 'pointer'
}
};
});
/**
* Component used to display a list of visitors waiting for approval to join the main meeting.
*
* @returns {ReactNode}
*/
export default function VisitorsList() {
const requests = useSelector(getPromotionRequests);
const visitorsCount = useSelector((state: IReduxState) => state['features/visitors'].count || 0);
const { t } = useTranslation();
const { classes, cx } = useStyles();
const dispatch = useDispatch();
const admitAll = useCallback(() => {
dispatch(admitMultiple(requests));
}, [ dispatch, requests ]);
if (visitorsCount <= 0) {
return null;
}
return (
<>
<div className = { classes.headingContainer }>
<div className = { cx(classes.heading, classes.headingW) }>
{ t('participantsPane.headings.visitors', { count: visitorsCount })}
{ requests.length > 0
&& t('participantsPane.headings.visitorRequests', { count: requests.length }) }
</div>
{
requests.length > 1
&& <div
className = { classes.link }
onClick = { admitAll }>{t('participantsPane.actions.admitAll')}</div>
}
</div>
<div
className = { classes.container }
id = 'visitor-list'>
{
requests.map(r => (
<VisitorsItem
key = { r.from }
request = { r } />)
)
}
</div>
</>
);
}

View File

@@ -24,16 +24,16 @@ export function useLobbyActions(participant?: IDrawerParticipant | null, closeDr
e.stopPropagation();
dispatch(approveKnockingParticipant(participant?.participantID ?? ''));
closeDrawer?.();
}, [ dispatch, closeDrawer ]),
}, [ dispatch, closeDrawer, participant?.participantID ]),
useCallback(() => {
dispatch(rejectKnockingParticipant(participant?.participantID ?? ''));
closeDrawer?.();
}, [ dispatch, closeDrawer ]),
}, [ dispatch, closeDrawer, participant?.participantID ]),
useCallback(() => {
dispatch(handleLobbyChatInitialized(participant?.participantID ?? ''));
}, [ dispatch ])
}, [ dispatch, participant?.participantID ])
];
}

View File

@@ -63,13 +63,23 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(user: any, data: any) => {
data.type === COMMAND_NEW_POLL ? data.senderId = user._id : data.voterId = user._id;
_handleReceivePollsMessage(data, dispatch);
const isNewPoll = data.type === COMMAND_NEW_POLL;
_handleReceivePollsMessage({
...data,
senderId: isNewPoll ? user._id : undefined,
voterId: isNewPoll ? undefined : user._id
}, dispatch);
});
conference.on(JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
(id: any, data: any) => {
data.type === COMMAND_NEW_POLL ? data.senderId = id : data.voterId = id;
_handleReceivePollsMessage(data, dispatch);
const isNewPoll = data.type === COMMAND_NEW_POLL;
_handleReceivePollsMessage({
...data,
senderId: isNewPoll ? id : undefined,
voterId: isNewPoll ? undefined : id
}, dispatch);
});
break;

View File

@@ -189,7 +189,17 @@ const useStyles = makeStyles()(theme => {
color: theme.palette.text04,
borderRadius: theme.shape.borderRadius,
position: 'relative',
top: `-${theme.spacing(3)}`
top: `-${theme.spacing(3)}`,
'@media (max-width: 511px)': {
margin: '0 auto',
top: 0
},
'@media (max-width: 420px)': {
top: 0,
width: 'calc(100% - 32px)'
}
}
};
});

View File

@@ -10,7 +10,7 @@ import { updateDropboxToken } from '../../../dropbox/actions';
import { getDropboxData, getNewAccessToken, isEnabled as isDropboxEnabled } from '../../../dropbox/functions.any';
import { showErrorNotification } from '../../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
import { toggleRequestingSubtitles } from '../../../subtitles/actions';
import { setRequestingSubtitles } from '../../../subtitles/actions.any';
import { setSelectedRecordingService, startLocalVideoRecording } from '../../actions';
import { RECORDING_TYPES } from '../../constants';
import { supportsLocalRecording } from '../../functions';
@@ -23,9 +23,9 @@ export interface IProps extends WithTranslation {
_appKey: string;
/**
* Requests subtitles when recording is turned on.
* Requests transcribing when recording is turned on.
*/
_autoCaptionOnRecord: boolean;
_autoTranscribeOnRecord: boolean;
/**
* The {@code JitsiConference} for the current conference.
@@ -114,6 +114,16 @@ interface IState {
*/
sharingEnabled: boolean;
/**
* True if the user requested the service to record audio and video.
*/
shouldRecordAudioAndVideo: boolean;
/**
* True if the user requested the service to record transcription.
*/
shouldRecordTranscription: boolean;
/**
* Number of MiB of available space in user's Dropbox account.
*/
@@ -144,6 +154,8 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
this._onSharingSettingChanged = this._onSharingSettingChanged.bind(this);
this._toggleScreenshotCapture = this._toggleScreenshotCapture.bind(this);
this._onLocalRecordingSelfChange = this._onLocalRecordingSelfChange.bind(this);
this._onTranscriptionChange = this._onTranscriptionChange.bind(this);
this._onRecordAudioAndVideoChange = this._onRecordAudioAndVideoChange.bind(this);
let selectedRecordingService = '';
@@ -165,6 +177,8 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
isValidating: false,
userName: undefined,
sharingEnabled: true,
shouldRecordAudioAndVideo: true,
shouldRecordTranscription: true,
spaceLeft: undefined,
selectedRecordingService,
localRecordingOnlySelf: false
@@ -241,6 +255,30 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
});
}
/**
* Handles transcription switch change.
*
* @param {boolean} value - The new value.
* @returns {void}
*/
_onTranscriptionChange(value: boolean) {
this.setState({
shouldRecordTranscription: value
});
}
/**
* Handles audio and video switch change.
*
* @param {boolean} value - The new value.
* @returns {void}
*/
_onRecordAudioAndVideoChange(value: boolean) {
this.setState({
shouldRecordAudioAndVideo: value
});
}
/**
* Validates the dropbox access token and fetches account information.
*
@@ -297,7 +335,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
_onSubmit() {
const {
_appKey,
_autoCaptionOnRecord,
_autoTranscribeOnRecord,
_conference,
_isDropboxEnabled,
_rToken,
@@ -309,57 +347,59 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
type?: string;
} = {};
switch (this.state.selectedRecordingService) {
case RECORDING_TYPES.DROPBOX: {
if (_isDropboxEnabled && _token) {
if (this.state.shouldRecordAudioAndVideo) {
switch (this.state.selectedRecordingService) {
case RECORDING_TYPES.DROPBOX: {
if (_isDropboxEnabled && _token) {
appData = JSON.stringify({
'file_recording_metadata': {
'upload_credentials': {
'service_name': RECORDING_TYPES.DROPBOX,
'token': _token,
'r_token': _rToken,
'app_key': _appKey
}
}
});
attributes.type = RECORDING_TYPES.DROPBOX;
} else {
dispatch(showErrorNotification({
titleKey: 'dialog.noDropboxToken'
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
return;
}
break;
}
case RECORDING_TYPES.JITSI_REC_SERVICE: {
appData = JSON.stringify({
'file_recording_metadata': {
'upload_credentials': {
'service_name': RECORDING_TYPES.DROPBOX,
'token': _token,
'r_token': _rToken,
'app_key': _appKey
}
'share': this.state.sharingEnabled
}
});
attributes.type = RECORDING_TYPES.DROPBOX;
} else {
dispatch(showErrorNotification({
titleKey: 'dialog.noDropboxToken'
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
return;
attributes.type = RECORDING_TYPES.JITSI_REC_SERVICE;
break;
}
break;
}
case RECORDING_TYPES.JITSI_REC_SERVICE: {
appData = JSON.stringify({
'file_recording_metadata': {
'share': this.state.sharingEnabled
}
case RECORDING_TYPES.LOCAL: {
dispatch(startLocalVideoRecording(this.state.localRecordingOnlySelf));
return true;
}
}
sendAnalytics(
createRecordingDialogEvent('start', 'confirm.button', attributes)
);
this._toggleScreenshotCapture();
_conference?.startRecording({
mode: JitsiRecordingConstants.mode.FILE,
appData
});
attributes.type = RECORDING_TYPES.JITSI_REC_SERVICE;
break;
}
case RECORDING_TYPES.LOCAL: {
dispatch(startLocalVideoRecording(this.state.localRecordingOnlySelf));
return true;
}
}
sendAnalytics(
createRecordingDialogEvent('start', 'confirm.button', attributes)
);
this._toggleScreenshotCapture();
_conference?.startRecording({
mode: JitsiRecordingConstants.mode.FILE,
appData
});
if (_autoCaptionOnRecord) {
dispatch(toggleRequestingSubtitles());
if (_autoTranscribeOnRecord || this.state.shouldRecordTranscription) {
dispatch(setRequestingSubtitles(true, false));
}
return true;
@@ -392,7 +432,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
* @private
* @returns {{
* _appKey: string,
* _autoCaptionOnRecord: boolean,
* _autoTranscribeOnRecord: boolean,
* _conference: JitsiConference,
* _fileRecordingsServiceEnabled: boolean,
* _fileRecordingsServiceSharingEnabled: boolean,
@@ -412,7 +452,7 @@ export function mapStateToProps(state: IReduxState, _ownProps: any) {
return {
_appKey: dropbox.appKey ?? '',
_autoCaptionOnRecord: transcription?.autoCaptionOnRecord ?? false,
_autoTranscribeOnRecord: transcription?.autoTranscribeOnRecord ?? false,
_conference: state['features/base/conference'].conference,
_fileRecordingsServiceEnabled: recordingService?.enabled ?? false,
_fileRecordingsServiceSharingEnabled: recordingService?.sharingEnabled ?? false,

View File

@@ -9,6 +9,7 @@ import { _abstractMapStateToProps } from '../../../base/dialog/functions';
import { isLocalParticipantModerator } from '../../../base/participants/functions';
import { authorizeDropbox, updateDropboxToken } from '../../../dropbox/actions';
import { isVpaasMeeting } from '../../../jaas/functions';
import { canStartTranscribing } from '../../../subtitles/functions';
import { RECORDING_TYPES } from '../../constants';
import { supportsLocalRecording } from '../../functions';
@@ -18,6 +19,11 @@ import { supportsLocalRecording } from '../../functions';
*/
export interface IProps extends WithTranslation {
/**
* Whether the local participant can start transcribing.
*/
_canStartTranscribing: boolean;
/**
* Style of the dialogs feature.
*/
@@ -111,11 +117,21 @@ export interface IProps extends WithTranslation {
*/
onLocalRecordingSelfChange?: () => void;
/**
* Callback to change the audio and video recording setting.
*/
onRecordAudioAndVideoChange: Function;
/**
* Callback to be invoked on sharing setting change.
*/
onSharingSettingChanged: () => void;
/**
* Callback to change the transcription recording setting.
*/
onTranscriptionChange: Function;
/**
* The currently selected recording service of type: RECORDING_TYPES.
*/
@@ -126,6 +142,16 @@ export interface IProps extends WithTranslation {
*/
sharingSetting: boolean;
/**
* Whether to show the audio and video related content.
*/
shouldRecordAudioAndVideo: boolean;
/**
* Whether to show the transcription related content.
*/
shouldRecordTranscription: boolean;
/**
* Number of MiB of available space in user's Dropbox account.
*/
@@ -137,18 +163,26 @@ export interface IProps extends WithTranslation {
userName?: string;
}
export interface IState {
/**
* Whether to show the advanced options or not.
*/
showAdvancedOptions: boolean;
}
/**
* React Component for getting confirmation to start a file recording session.
* React Component for getting confirmation to start a recording session.
*
* @augments Component
*/
class AbstractStartRecordingDialogContent<P extends IProps> extends Component<P> {
class AbstractStartRecordingDialogContent extends Component<IProps, IState> {
/**
* Initializes a new {@code AbstractStartRecordingDialogContent} instance.
*
* @inheritdoc
*/
constructor(props: P) {
constructor(props: IProps) {
super(props);
// Bind event handler; it bounds once for every instance.
@@ -157,6 +191,13 @@ class AbstractStartRecordingDialogContent<P extends IProps> extends Component<P>
this._onDropboxSwitchChange = this._onDropboxSwitchChange.bind(this);
this._onRecordingServiceSwitchChange = this._onRecordingServiceSwitchChange.bind(this);
this._onLocalRecordingSwitchChange = this._onLocalRecordingSwitchChange.bind(this);
this._onTranscriptionSwitchChange = this._onTranscriptionSwitchChange.bind(this);
this._onRecordAudioAndVideoSwitchChange = this._onRecordAudioAndVideoSwitchChange.bind(this);
this._onToggleShowOptions = this._onToggleShowOptions.bind(this);
this.state = {
showAdvancedOptions: false
};
}
/**
@@ -177,7 +218,7 @@ class AbstractStartRecordingDialogContent<P extends IProps> extends Component<P>
*
* @inheritdoc
*/
componentDidUpdate(prevProps: P) {
componentDidUpdate(prevProps: IProps) {
// Auto sign-out when the use chooses another recording service.
if (prevProps.selectedRecordingService === RECORDING_TYPES.DROPBOX
&& this.props.selectedRecordingService !== RECORDING_TYPES.DROPBOX && this.props.isTokenValid) {
@@ -185,6 +226,15 @@ class AbstractStartRecordingDialogContent<P extends IProps> extends Component<P>
}
}
/**
* Returns whether the advanced options should be rendered.
*
* @returns {boolean}
*/
_onToggleShowOptions() {
this.setState({ showAdvancedOptions: !this.state.showAdvancedOptions });
}
/**
* Whether the file sharing content should be rendered or not.
*
@@ -208,6 +258,15 @@ class AbstractStartRecordingDialogContent<P extends IProps> extends Component<P>
return true;
}
/**
* Whether the save transcription content should be rendered or not.
*
* @returns {boolean}
*/
_canStartTranscribing() {
return this.props._canStartTranscribing;
}
/**
* Whether the no integrations content should be rendered or not.
*
@@ -236,6 +295,26 @@ class AbstractStartRecordingDialogContent<P extends IProps> extends Component<P>
return true;
}
/**
* Handler for transcription switch change.
*
* @param {boolean} value - The new value.
* @returns {void}
*/
_onTranscriptionSwitchChange(value: boolean | undefined) {
this.props.onTranscriptionChange(value);
}
/**
* Handler for audio and video switch change.
*
* @param {boolean} value - The new value.
* @returns {void}
*/
_onRecordAudioAndVideoSwitchChange(value: boolean | undefined) {
this.props.onRecordAudioAndVideoChange(value);
}
/**
* Handler for onValueChange events from the Switch component.
*
@@ -339,6 +418,7 @@ export function mapStateToProps(state: IReduxState) {
return {
..._abstractMapStateToProps(state),
isVpaas: isVpaasMeeting(state),
_canStartTranscribing: canStartTranscribing(state),
_hideStorageWarning: Boolean(recordingService?.hideStorageWarning),
_isModerator: isLocalParticipantModerator(state),
_localRecordingAvailable,

View File

@@ -98,7 +98,16 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
* @returns {boolean}
*/
isStartRecordingDisabled() {
const { isTokenValid, selectedRecordingService } = this.state;
const {
isTokenValid,
selectedRecordingService,
shouldRecordAudioAndVideo,
shouldRecordTranscription
} = this.state;
if (!shouldRecordAudioAndVideo && !shouldRecordTranscription) {
return true;
}
// Start button is disabled if recording service is only shown;
// When validating dropbox token, if that is not enabled, we either always
@@ -125,6 +134,8 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
isValidating,
selectedRecordingService,
sharingEnabled,
shouldRecordAudioAndVideo,
shouldRecordTranscription,
spaceLeft,
userName
} = this.state;
@@ -142,9 +153,13 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
isTokenValid = { isTokenValid }
isValidating = { isValidating }
onChange = { this._onSelectedRecordingServiceChanged }
onRecordAudioAndVideoChange = { this._onRecordAudioAndVideoChange }
onSharingSettingChanged = { this._onSharingSettingChanged }
onTranscriptionChange = { this._onTranscriptionChange }
selectedRecordingService = { selectedRecordingService }
sharingSetting = { sharingEnabled }
shouldRecordAudioAndVideo = { shouldRecordAudioAndVideo }
shouldRecordTranscription = { shouldRecordTranscription }
spaceLeft = { spaceLeft }
userName = { userName } />
</JitsiScreen>

View File

@@ -4,16 +4,15 @@ import { Text } from 'react-native-paper';
import { connect } from 'react-redux';
import { translate } from '../../../../base/i18n/functions';
import Icon from '../../../../base/icons/components/Icon';
import { IconArrowDown, IconArrowRight } from '../../../../base/icons/svg';
import LoadingIndicator from '../../../../base/react/components/native/LoadingIndicator';
import Button from '../../../../base/ui/components/native/Button';
import Switch from '../../../../base/ui/components/native/Switch';
import { BUTTON_TYPES } from '../../../../base/ui/constants.native';
import { RECORDING_TYPES } from '../../../constants';
import { getRecordingDurationEstimation } from '../../../functions';
import AbstractStartRecordingDialogContent, {
IProps,
mapStateToProps
} from '../AbstractStartRecordingDialogContent';
import AbstractStartRecordingDialogContent, { mapStateToProps } from '../AbstractStartRecordingDialogContent';
import {
DROPBOX_LOGO,
ICON_CLOUD,
@@ -25,7 +24,7 @@ import {
/**
* The start recording dialog content for the mobile application.
*/
class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IProps> {
class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
/**
* Renders the component.
*
@@ -41,10 +40,86 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
{ this._renderFileSharingContent() }
{ this._renderUploadToTheCloudInfo() }
{ this._renderIntegrationsContent() }
{ this._renderAdvancedOptions() }
</View>
);
}
/**
* Renders the save transcription switch.
*
* @returns {React$Component}
*/
_renderAdvancedOptions() {
if (!this._canStartTranscribing()) {
return null;
}
const { showAdvancedOptions } = this.state;
const {
_dialogStyles,
_styles: styles,
shouldRecordAudioAndVideo,
shouldRecordTranscription,
t
} = this.props;
return (
<>
<View
style = { styles.header }>
<Text
style = {{
..._dialogStyles.text,
...styles.title
}}>
{ t('recording.showAdvancedOptions') }
</Text>
<Icon
ariaPressed = { showAdvancedOptions }
onClick = { this._onToggleShowOptions }
role = 'button'
size = { 24 }
src = { showAdvancedOptions ? IconArrowDown : IconArrowRight } />
</View>
{showAdvancedOptions && (
<>
<View
key = 'transcriptionSetting'
style = { styles.header }>
<Text
style = {{
..._dialogStyles.text,
...styles.title
}}>
{ t('recording.recordTranscription') }
</Text>
<Switch
checked = { shouldRecordTranscription }
onChange = { this._onTranscriptionSwitchChange }
style = { styles.switch } />
</View>
<View
key = 'audioVideoSetting'
style = { styles.header }>
<Text
style = {{
..._dialogStyles.text,
...styles.title
}}>
{ t('recording.recordAudioAndVideo') }
</Text>
<Switch
checked = { shouldRecordAudioAndVideo }
onChange = { this._onRecordAudioAndVideoSwitchChange }
style = { styles.switch } />
</View>
</>
)}
</>
);
}
/**
* Renders the content in case no integrations were enabled.
*
@@ -57,6 +132,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
integrationsEnabled,
isValidating,
selectedRecordingService,
shouldRecordAudioAndVideo,
t
} = this.props;
@@ -69,7 +145,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
? (
<Switch
checked = { selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE }
disabled = { isValidating }
disabled = { isValidating || !shouldRecordAudioAndVideo }
onChange = { this._onRecordingServiceSwitchChange }
style = { styles.switch } />
) : null;
@@ -109,6 +185,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
isValidating,
onSharingSettingChanged,
sharingSetting,
shouldRecordAudioAndVideo,
t
} = this.props;
@@ -128,7 +205,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
</Text>
<Switch
checked = { sharingSetting }
disabled = { isValidating }
disabled = { isValidating || !shouldRecordAudioAndVideo }
onChange = { onSharingSettingChanged }
style = { styles.switch } />
</View>
@@ -237,6 +314,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
isTokenValid,
isValidating,
selectedRecordingService,
shouldRecordAudioAndVideo,
t
} = this.props;
@@ -270,7 +348,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
switchContent = (
<Switch
checked = { selectedRecordingService === RECORDING_TYPES.DROPBOX }
disabled = { isValidating }
disabled = { isValidating || !shouldRecordAudioAndVideo }
onChange = { this._onDropboxSwitchChange }
style = { styles.switch } />
);

View File

@@ -12,3 +12,5 @@ export const ICON_CLOUD = 'images/icon-cloud.png';
export const ICON_INFO = 'images/icon-info.png';
export const ICON_USERS = 'images/icon-users.png';
export const ICON_OPTIONS = 'images/icon-info.png';

View File

@@ -28,7 +28,16 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
* @returns {boolean}
*/
isStartRecordingDisabled() {
const { isTokenValid, selectedRecordingService } = this.state;
const {
isTokenValid,
selectedRecordingService,
shouldRecordAudioAndVideo,
shouldRecordTranscription
} = this.state;
if (!shouldRecordAudioAndVideo && !shouldRecordTranscription) {
return true;
}
// Start button is disabled if recording service is only shown;
// When validating dropbox token, if that is not enabled, we either always
@@ -57,6 +66,8 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
localRecordingOnlySelf,
selectedRecordingService,
sharingEnabled,
shouldRecordAudioAndVideo,
shouldRecordTranscription,
spaceLeft,
userName
} = this.state;
@@ -82,9 +93,13 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
localRecordingOnlySelf = { localRecordingOnlySelf }
onChange = { this._onSelectedRecordingServiceChanged }
onLocalRecordingSelfChange = { this._onLocalRecordingSelfChange }
onRecordAudioAndVideoChange = { this._onRecordAudioAndVideoChange }
onSharingSettingChanged = { this._onSharingSettingChanged }
onTranscriptionChange = { this._onTranscriptionChange }
selectedRecordingService = { selectedRecordingService }
sharingSetting = { sharingEnabled }
shouldRecordAudioAndVideo = { shouldRecordAudioAndVideo }
shouldRecordTranscription = { shouldRecordTranscription }
spaceLeft = { spaceLeft }
userName = { userName } />
</Dialog>

View File

@@ -2,6 +2,8 @@ import React from 'react';
import { connect } from 'react-redux';
import { translate } from '../../../../base/i18n/functions';
import Icon from '../../../../base/icons/components/Icon';
import { IconArrowDown, IconArrowRight } from '../../../../base/icons/svg';
import Container from '../../../../base/react/components/web/Container';
import Image from '../../../../base/react/components/web/Image';
import LoadingIndicator from '../../../../base/react/components/web/LoadingIndicator';
@@ -11,10 +13,7 @@ import Switch from '../../../../base/ui/components/web/Switch';
import { BUTTON_TYPES } from '../../../../base/ui/constants.web';
import { RECORDING_TYPES } from '../../../constants';
import { getRecordingDurationEstimation } from '../../../functions';
import AbstractStartRecordingDialogContent, {
IProps,
mapStateToProps
} from '../AbstractStartRecordingDialogContent';
import AbstractStartRecordingDialogContent, { mapStateToProps } from '../AbstractStartRecordingDialogContent';
import {
DROPBOX_LOGO,
ICON_CLOUD,
@@ -30,7 +29,7 @@ const EMPTY_FUNCTION = () => {
/**
* The start recording dialog content for the mobile application.
*/
class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IProps> {
class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
/**
* Renders the component.
*
@@ -49,10 +48,72 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
</>
)}
{ this._renderLocalRecordingContent() }
{ this._renderAdvancedOptions() }
</Container>
);
}
/**
* Renders the switch for saving the transcription.
*
* @returns {React$Component}
*/
_renderAdvancedOptions() {
if (!this._canStartTranscribing()) {
return null;
}
const { showAdvancedOptions } = this.state;
const { shouldRecordAudioAndVideo, shouldRecordTranscription, t } = this.props;
return (
<>
<div className = 'recording-header-line' />
<div
className = 'recording-header'
onClick = { this._onToggleShowOptions }>
<label className = 'recording-title-no-space'>
{t('recording.showAdvancedOptions')}
</label>
<Icon
ariaPressed = { showAdvancedOptions }
onClick = { this._onToggleShowOptions }
role = 'button'
size = { 24 }
src = { showAdvancedOptions ? IconArrowDown : IconArrowRight } />
</div>
{showAdvancedOptions && (
<>
<div className = 'recording-header space-top'>
<label
className = 'recording-title'
htmlFor = 'recording-switch-transcription'>
{ t('recording.recordTranscription') }
</label>
<Switch
checked = { shouldRecordTranscription }
className = 'recording-switch'
id = 'recording-switch-transcription'
onChange = { this._onTranscriptionSwitchChange } />
</div>
<div className = 'recording-header space-top'>
<label
className = 'recording-title'
htmlFor = 'recording-switch-transcription'>
{ t('recording.recordAudioAndVideo') }
</label>
<Switch
checked = { shouldRecordAudioAndVideo }
className = 'recording-switch'
id = 'recording-switch-transcription'
onChange = { this._onRecordAudioAndVideoSwitchChange } />
</div>
</>
)}
</>
);
}
/**
* Renders the content in case no integrations were enabled.
*
@@ -78,7 +139,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
<Switch
checked = { selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE }
className = 'recording-switch'
disabled = { isValidating }
disabled = { isValidating || !this.props.shouldRecordAudioAndVideo }
id = 'recording-switch-jitsi'
onChange = { this._onRecordingServiceSwitchChange } />
) : null;
@@ -148,7 +209,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
<Switch
checked = { sharingSetting }
className = 'recording-switch'
disabled = { isValidating }
disabled = { isValidating || !this.props.shouldRecordAudioAndVideo }
id = 'recording-switch-share'
onChange = { onSharingSettingChanged } />
</Container>
@@ -294,7 +355,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
checked = { selectedRecordingService
=== RECORDING_TYPES.DROPBOX }
className = 'recording-switch'
disabled = { isValidating }
disabled = { isValidating || !this.props.shouldRecordAudioAndVideo }
id = 'recording-switch-integration'
onChange = { this._onDropboxSwitchChange } />
);
@@ -372,7 +433,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
checked = { selectedRecordingService
=== RECORDING_TYPES.LOCAL }
className = 'recording-switch'
disabled = { isValidating }
disabled = { isValidating || !this.props.shouldRecordAudioAndVideo }
id = 'recording-switch-local'
onChange = { this._onLocalRecordingSwitchChange } />
</Container>
@@ -396,7 +457,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
<Switch
checked = { Boolean(localRecordingOnlySelf) }
className = 'recording-switch'
disabled = { isValidating }
disabled = { isValidating || !this.props.shouldRecordAudioAndVideo }
id = 'recording-switch-myself'
onChange = { onLocalRecordingSelfChange ?? EMPTY_FUNCTION } />
</Container>

View File

@@ -17,6 +17,11 @@ interface IProps {
*/
_password?: string;
/**
* Number of digits used in the room-lock password.
*/
_passwordNumberOfDigits?: number;
/**
* The {@code JitsiConference} which requires a password.
*
@@ -88,6 +93,15 @@ class PasswordRequiredPrompt extends Component<IProps, IState> {
*/
render() {
const { password } = this.state;
const { _passwordNumberOfDigits } = this.props;
const textInputProps: any = {
secureTextEntry: true
};
if (_passwordNumberOfDigits) {
textInputProps.keyboardType = 'numeric';
textInputProps.maxLength = _passwordNumberOfDigits;
}
return (
<InputDialog
@@ -96,9 +110,7 @@ class PasswordRequiredPrompt extends Component<IProps, IState> {
messageKey = { password ? 'dialog.incorrectRoomLockPassword' : undefined }
onCancel = { this._onCancel }
onSubmit = { this._onSubmit }
textInputProps = {{
secureTextEntry: true
}}
textInputProps = { textInputProps }
titleKey = 'dialog.password' />
);
}
@@ -142,8 +154,11 @@ class PasswordRequiredPrompt extends Component<IProps, IState> {
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState) {
const { roomPasswordNumberOfDigits } = state['features/base/config'];
return {
_password: state['features/base/conference'].password
_password: state['features/base/conference'].password,
_passwordNumberOfDigits: roomPasswordNumberOfDigits
};
}

View File

@@ -34,18 +34,6 @@ export const REMOVE_TRANSCRIPT_MESSAGE = 'REMOVE_TRANSCRIPT_MESSAGE';
*/
export const UPDATE_TRANSCRIPT_MESSAGE = 'UPDATE_TRANSCRIPT_MESSAGE';
/**
* The type of (redux) action which indicates that a transcript with an
* given message_id to be added or updated is received.
*
* {
* type: UPDATE_TRANSLATION_LANGUAGE,
* transcriptMessageID: string,
* newTranscriptMessage: Object
* }
*/
export const UPDATE_TRANSLATION_LANGUAGE = 'UPDATE_TRANSLATION_LANGUAGE';
/**
* The type of (redux) action which indicates that the user pressed the
* ClosedCaption button, to either enable or disable subtitles based on the

View File

@@ -1,10 +1,11 @@
import { DEFAULT_LANGUAGE } from '../base/i18n/i18next';
import {
ENDPOINT_MESSAGE_RECEIVED,
REMOVE_TRANSCRIPT_MESSAGE,
SET_REQUESTING_SUBTITLES,
TOGGLE_REQUESTING_SUBTITLES,
UPDATE_TRANSCRIPT_MESSAGE,
UPDATE_TRANSLATION_LANGUAGE
UPDATE_TRANSCRIPT_MESSAGE
} from './actionTypes';
/**
@@ -80,29 +81,23 @@ export function toggleRequestingSubtitles() {
* Signals that the local user has enabled or disabled the subtitles.
*
* @param {boolean} enabled - The new state of the subtitles.
* @param {boolean} displaySubtitles - Whether to display subtitles or not.
* @param {string} language - The language of the subtitles.
* @returns {{
* type: SET_REQUESTING_SUBTITLES,
* enabled: boolean
* enabled: boolean,
* displaySubtitles: boolean,
* language: string
* }}
*/
export function setRequestingSubtitles(enabled: boolean) {
export function setRequestingSubtitles(
enabled: boolean,
displaySubtitles = true,
language: string | null = `translation-languages:${DEFAULT_LANGUAGE}`) {
return {
type: SET_REQUESTING_SUBTITLES,
enabled
};
}
/**
* Signals that the local user has selected language for the translation.
*
* @param {string} value - The selected language for translation.
* @returns {{
* type: UPDATE_TRANSLATION_LANGUAGE
* }}
*/
export function updateTranslationLanguage(value: string) {
return {
type: UPDATE_TRANSLATION_LANGUAGE,
value
displaySubtitles,
enabled,
language
};
}

View File

@@ -8,9 +8,7 @@ export * from './actions.any';
/**
* Signals that the local user has toggled the LanguageSelector button.
*
* @returns {{
* type: UPDATE_TRANSLATION_LANGUAGE
* }}
* @returns {Function}
*/
export function toggleLanguageSelectorDialog() {
return function(dispatch: IStore['dispatch']) {

View File

@@ -9,7 +9,12 @@ import { IReduxState } from '../../app/types';
export interface IAbstractCaptionsProps {
/**
* Whether local participant is requesting to see subtitles.
* Whether local participant is displaying subtitles.
*/
_displaySubtitles: boolean;
/**
* Whether local participant is requesting subtitles.
*/
_requestingSubtitles: boolean;
@@ -34,9 +39,9 @@ export class AbstractCaptions<P extends IAbstractCaptionsProps> extends Componen
* @returns {ReactElement}
*/
render(): any {
const { _requestingSubtitles, _transcripts } = this.props;
const { _displaySubtitles, _requestingSubtitles, _transcripts } = this.props;
if (!_requestingSubtitles || !_transcripts || !_transcripts.size) {
if (!_requestingSubtitles || !_displaySubtitles || !_transcripts || !_transcripts.size) {
return null;
}
@@ -95,7 +100,7 @@ function _constructTranscripts(state: IReduxState): Map<string, string> {
for (const [ id, transcriptMessage ] of _transcriptMessages) {
if (transcriptMessage) {
let text = `${transcriptMessage.participantName}: `;
let text = `${transcriptMessage.participant.name}: `;
if (transcriptMessage.final) {
text += transcriptMessage.final;
@@ -125,10 +130,11 @@ function _constructTranscripts(state: IReduxState): Map<string, string> {
* }}
*/
export function _abstractMapStateToProps(state: IReduxState) {
const { _requestingSubtitles } = state['features/subtitles'];
const { _displaySubtitles, _requestingSubtitles } = state['features/subtitles'];
const transcripts = _constructTranscripts(state);
return {
_displaySubtitles,
_requestingSubtitles,
// avoid re-renders by setting to prop new empty Map instances.

View File

@@ -2,13 +2,13 @@ import { createToolbarEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { IReduxState } from '../../app/types';
import { MEET_FEATURES } from '../../base/jwt/constants';
import { isLocalParticipantModerator } from '../../base/participants/functions';
import AbstractButton, { IProps as AbstractButtonProps } from '../../base/toolbox/components/AbstractButton';
import { maybeShowPremiumFeatureDialog } from '../../jaas/actions';
import { canStartTranscribing } from '../functions';
export interface IAbstractProps extends AbstractButtonProps {
_language: string;
_language: string | null;
/**
* Whether the local participant is currently requesting subtitles.
@@ -103,13 +103,10 @@ export class AbstractClosedCaptionButton
*/
export function _abstractMapStateToProps(state: IReduxState, ownProps: IAbstractProps) {
const { _requestingSubtitles, _language } = state['features/subtitles'];
const { transcription } = state['features/base/config'];
const { isTranscribing } = state['features/transcribing'];
// if the participant is moderator, it can enable transcriptions and if
// transcriptions are already started for the meeting, guests can just show them
const { visible = Boolean(transcription?.enabled
&& (isLocalParticipantModerator(state) || isTranscribing)) } = ownProps;
const { visible = canStartTranscribing(state) } = ownProps;
return {
_requestingSubtitles,

View File

@@ -1,4 +1,4 @@
import React, { ComponentType, useCallback, useEffect, useState } from 'react';
import React, { ComponentType, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
@@ -7,12 +7,12 @@ import {
TRANSLATION_LANGUAGES,
TRANSLATION_LANGUAGES_HEAD
} from '../../base/i18n/i18next';
import { setRequestingSubtitles, updateTranslationLanguage } from '../actions.any';
import { setRequestingSubtitles } from '../actions.any';
export interface IAbstractLanguageSelectorDialogProps {
dispatch: IStore['dispatch'];
language: string;
language: string | null;
listItems: Array<any>;
onLanguageSelected: (e: string) => void;
subtitles: string;
@@ -30,10 +30,10 @@ export interface IAbstractLanguageSelectorDialogProps {
const AbstractLanguageSelectorDialog = (Component: ComponentType<IAbstractLanguageSelectorDialogProps>) => () => {
const dispatch = useDispatch();
const { t } = useTranslation();
const off = 'transcribing.subtitlesOff';
const noLanguageLabel = 'transcribing.subtitlesOff';
const [ subtitles, setSubtiles ] = useState(off);
const language = useSelector((state: IReduxState) => state['features/subtitles']._language);
const subtitles = language ?? noLanguageLabel;
const transcription = useSelector((state: IReduxState) => state['features/base/config'].transcription);
const translationLanguagesHead = transcription?.translationLanguagesHead ?? TRANSLATION_LANGUAGES_HEAD;
@@ -42,7 +42,7 @@ const AbstractLanguageSelectorDialog = (Component: ComponentType<IAbstractLangua
// The off and the head languages are always on the top of the list. But once you are selecting
// a language from the translationLanguages, that language is moved under the fixedItems list,
// until a new languages is selected. FixedItems keep their positions.
const fixedItems = [ off, ...languagesHead ];
const fixedItems = [ noLanguageLabel, ...languagesHead ];
const translationLanguages = transcription?.translationLanguages ?? TRANSLATION_LANGUAGES;
const languages = translationLanguages
.map((lang: string) => `translation-languages:${lang}`)
@@ -58,14 +58,12 @@ const AbstractLanguageSelectorDialog = (Component: ComponentType<IAbstractLangua
};
});
useEffect(() => {
language ? setSubtiles(language) : setSubtiles(off);
}, []);
const onLanguageSelected = useCallback((value: string) => {
const selectedLanguage = value === noLanguageLabel ? null : value;
const enabled = Boolean(selectedLanguage);
const displaySubtitles = enabled;
const onLanguageSelected = useCallback((e: string) => {
setSubtiles(e);
dispatch(updateTranslationLanguage(e));
dispatch(setRequestingSubtitles(e !== off));
dispatch(setRequestingSubtitles(enabled, displaySubtitles, selectedLanguage));
}, [ language ]);
return (

View File

@@ -22,7 +22,7 @@ class ClosedCaptionButton
icon = IconSubtitles;
label = 'toolbar.startSubtitles';
labelProps = {
language: this.props.t(this.props._language),
language: this.props.t(this.props._language ?? 'transcribing.subtitlesOff'),
languages: this.props.t(this.props.languages ?? ''),
languagesHead: this.props.t(this.props.languagesHead ?? '')
};

View File

@@ -18,7 +18,7 @@ class ClosedCaptionButton
tooltip = 'transcribing.ccButtonTooltip';
label = 'toolbar.startSubtitles';
labelProps = {
language: this.props.t(this.props._language),
language: this.props.t(this.props._language ?? 'transcribing.subtitlesOff'),
languages: this.props.t(this.props.languages ?? ''),
languagesHead: this.props.t(this.props.languagesHead ?? '')
};

View File

@@ -0,0 +1,15 @@
import { IReduxState } from '../app/types';
import { isLocalParticipantModerator } from '../base/participants/functions';
/**
* Checks whether the participant can start the transcription.
*
* @param {IReduxState} state - The redux state.
* @returns {boolean} - True if the participant can start the transcription.
*/
export function canStartTranscribing(state: IReduxState) {
const { transcription } = state['features/base/config'];
const { isTranscribing } = state['features/transcribing'];
return Boolean(transcription?.enabled && (isLocalParticipantModerator(state) || isTranscribing));
}

View File

@@ -55,12 +55,15 @@ MiddlewareRegistry.register(store => next => action => {
case ENDPOINT_MESSAGE_RECEIVED:
return _endpointMessageReceived(store, next, action);
case TOGGLE_REQUESTING_SUBTITLES:
_requestingSubtitlesChange(store);
case TOGGLE_REQUESTING_SUBTITLES: {
const state = store.getState()['features/subtitles'];
const toggledValue = !state._requestingSubtitles;
_requestingSubtitlesChange(store, toggledValue, state._language);
break;
}
case SET_REQUESTING_SUBTITLES:
_requestingSubtitlesChange(store);
_requestingSubtitlesSet(store, action.enabled);
_requestingSubtitlesChange(store, action.enabled, action.language);
break;
}
@@ -91,23 +94,28 @@ function _endpointMessageReceived({ dispatch, getState }: IStore, next: Function
}
const state = getState();
const translationLanguage
const language
= state['features/base/conference'].conference
?.getLocalParticipantProperty(P_NAME_TRANSLATION_LANGUAGE);
try {
const transcriptMessageID = json.message_id;
const participantName = json.participant.name;
const { name, id, avatar_url: avatarUrl } = json.participant;
const participant = {
avatarUrl,
id,
name
};
if (json.type === JSON_TYPE_TRANSLATION_RESULT
&& json.language === translationLanguage) {
&& json.language === language) {
// Displays final results in the target language if translation is
// enabled.
const newTranscriptMessage = {
clearTimeOut: undefined,
final: json.text,
participantName
participant
};
_setClearerOnTranscriptMessage(dispatch,
@@ -115,8 +123,7 @@ function _endpointMessageReceived({ dispatch, getState }: IStore, next: Function
dispatch(updateTranscriptMessage(transcriptMessageID,
newTranscriptMessage));
} else if (json.type === JSON_TYPE_TRANSCRIPTION_RESULT
&& json.language.slice(0, 2) === translationLanguage) {
} else if (json.type === JSON_TYPE_TRANSCRIPTION_RESULT && json.language.slice(0, 2) === language) {
// Displays interim and final results without any translation if
// translations are disabled.
@@ -125,10 +132,11 @@ function _endpointMessageReceived({ dispatch, getState }: IStore, next: Function
// We update the previous transcript message with the same
// message ID or adds a new transcript message if it does not
// exist in the map.
const existingMessage = state['features/subtitles']._transcriptMessages.get(transcriptMessageID);
const newTranscriptMessage: any = {
...state['features/subtitles']._transcriptMessages
.get(transcriptMessageID)
|| { participantName }
clearTimeOut: existingMessage?.clearTimeOut,
language,
participant
};
_setClearerOnTranscriptMessage(dispatch,
@@ -144,7 +152,6 @@ function _endpointMessageReceived({ dispatch, getState }: IStore, next: Function
// stable field of the state and remove the previously
// unstable results
newTranscriptMessage.stable = text;
newTranscriptMessage.unstable = undefined;
} else {
// Otherwise, this result has an unstable result, which we
@@ -157,9 +164,13 @@ function _endpointMessageReceived({ dispatch, getState }: IStore, next: Function
// Notify the external API too.
if (typeof APP !== 'undefined') {
const sanitizedTranscriptMessage = { ...newTranscriptMessage };
delete sanitizedTranscriptMessage.clearTimeOut;
APP.API.notifyTranscriptionChunkReceived({
messageID: transcriptMessageID,
...newTranscriptMessage
...sanitizedTranscriptMessage
});
}
}
@@ -175,43 +186,27 @@ function _endpointMessageReceived({ dispatch, getState }: IStore, next: Function
* and Jigasi to decide whether the transcriber needs to be in the room.
*
* @param {Store} store - The redux store.
* @param {boolean} enabled - Whether subtitles should be enabled or not.
* @param {string} language - The language to use for translation.
* @private
* @returns {void}
*/
function _requestingSubtitlesChange({ getState }: IStore) {
const state = getState();
const { _language } = state['features/subtitles'];
const { conference } = state['features/base/conference'];
const requestingSubtitles = _language !== 'transcribing.subtitlesOff';
conference?.setLocalParticipantProperty(
P_NAME_REQUESTING_TRANSCRIPTION,
requestingSubtitles);
if (requestingSubtitles) {
conference?.setLocalParticipantProperty(
P_NAME_TRANSLATION_LANGUAGE,
_language.replace('translation-languages:', ''));
}
}
/**
* Set the local property 'requestingTranscription'. This will cause Jicofo
* and Jigasi to decide whether the transcriber needs to be in the room.
*
* @param {Store} store - The redux store.
* @param {boolean} enabled - The new state of the subtitles.
* @private
* @returns {void}
*/
function _requestingSubtitlesSet({ getState }: IStore, enabled: boolean) {
function _requestingSubtitlesChange(
{ getState }: IStore,
enabled: boolean,
language?: string | null) {
const state = getState();
const { conference } = state['features/base/conference'];
conference?.setLocalParticipantProperty(
P_NAME_REQUESTING_TRANSCRIPTION,
enabled);
if (enabled && language) {
conference?.setLocalParticipantProperty(
P_NAME_TRANSLATION_LANGUAGE,
language.replace('translation-languages:', ''));
}
}
/**

View File

@@ -2,16 +2,17 @@ import ReducerRegistry from '../base/redux/ReducerRegistry';
import {
REMOVE_TRANSCRIPT_MESSAGE,
SET_REQUESTING_SUBTITLES, UPDATE_TRANSCRIPT_MESSAGE, UPDATE_TRANSLATION_LANGUAGE
SET_REQUESTING_SUBTITLES, TOGGLE_REQUESTING_SUBTITLES, UPDATE_TRANSCRIPT_MESSAGE
} from './actionTypes';
/**
* Default State for 'features/transcription' feature.
*/
const defaultState = {
_displaySubtitles: true,
_transcriptMessages: new Map(),
_requestingSubtitles: false,
_language: 'transcribing.subtitlesOff'
_language: null
};
interface ITranscriptMessage {
@@ -22,7 +23,8 @@ interface ITranscriptMessage {
}
export interface ISubtitlesState {
_language: string;
_displaySubtitles: boolean;
_language: string | null;
_requestingSubtitles: boolean;
_transcriptMessages: Map<string, ITranscriptMessage> | any;
}
@@ -38,16 +40,18 @@ ReducerRegistry.register<ISubtitlesState>('features/subtitles', (
return _removeTranscriptMessage(state, action);
case UPDATE_TRANSCRIPT_MESSAGE:
return _updateTranscriptMessage(state, action);
case UPDATE_TRANSLATION_LANGUAGE:
return {
...state,
_language: action.value
};
case SET_REQUESTING_SUBTITLES:
return {
...state,
_displaySubtitles: action.displaySubtitles,
_language: action.language,
_requestingSubtitles: action.enabled
};
case TOGGLE_REQUESTING_SUBTITLES:
return {
...state,
_requestingSubtitles: !state._requestingSubtitles
};
}
return state;

View File

@@ -37,6 +37,7 @@ interface IProps {
const useStyles = makeStyles()(theme => {
return {
drawerMenuContainer: {
backgroundColor: 'rgba(0,0,0,0.6)',
height: '100dvh',
display: 'flex',
alignItems: 'flex-end'

View File

@@ -92,8 +92,14 @@ StateListenerRegistry.register(
isTileView: isLayoutTileView(state)
};
},
/* listener */({ clientHeight, isTileView }, store) => {
if (!isTileView) {
/* listener */({ clientHeight, isTileView: previousIsTileView }, store) => {
const state = store.getState();
if (!isLayoutTileView(state)) {
if (previousIsTileView) {
store.dispatch(setShiftUp(false));
}
return;
}
throttledCheckOverlap(clientHeight, store);

View File

@@ -17,3 +17,24 @@ export const UPDATE_VISITORS_COUNT = 'UPDATE_VISITORS_COUNT';
* }
*/
export const I_AM_VISITOR_MODE = 'I_AM_VISITOR_MODE';
/**
* The type of (redux) action which indicates that a promotion request was received from a visitor.
*
* {
* type: VISITOR_PROMOTION_REQUEST,
* nick: string,
* from: string
* }
*/
export const VISITOR_PROMOTION_REQUEST = 'VISITOR_PROMOTION_REQUEST';
/**
* The type of (redux) action which indicates that a promotion response denied was received.
*
* {
* type: CLEAR_VISITOR_PROMOTION_REQUEST,
* request: IPromotionRequest
* }
*/
export const CLEAR_VISITOR_PROMOTION_REQUEST = 'CLEAR_VISITOR_PROMOTION_REQUEST';

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