Compare commits

...

25 Commits

Author SHA1 Message Date
Hristo Terezov
1556f1b81a ref(responsive-ui): rename clientWidth to videoSpaceWidth.
Currently the clientWidth is not representing the window width but it is representing the available video space width since we are subtracting the width of the participants pane and chat area.
2025-05-06 09:40:54 -05:00
Saúl Ibarra Corretgé
598d3764dd fix(local-recordings) make sure we have a gDM audio stream 2025-05-06 14:42:29 +02:00
Saúl Ibarra Corretgé
cff91756d0 fix(local-recordings) tweak audio constraints for local recordings 2025-05-06 14:42:29 +02:00
Calin-Teodor
f1384eb117 feat(base/conference): add isReplaced, reason, params for KICKED conference event 2025-05-06 15:09:36 +03:00
Saúl Ibarra Corretgé
5c0c3c2e0d feat(recording) refactor consent dialog (#15985)
* feat(recording) refactor consent dialog

Offer 2 choices and add a configurable "learn more" link.

* hide dialog and display link conditionally

* native changes

---------

Co-authored-by: Mihaela Dumitru <mihdmt@gmail.com>
2025-05-06 15:02:39 +03:00
damencho
b123d140fa chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1982.0.0+cec2a2e6...v1984.0.0+dd4c41be
2025-05-06 10:28:52 +03:00
Andrei Gavrilescu
a4ffd8546e fix(popover): touch interaction closes overflow drawer without triggering action
* automatic drawer toolbox on mobile browser

* fix touch interaction on Popover
2025-05-06 10:04:08 +03:00
Дамян Минков
1ab3309323 feat(pre-join): Drops skip pre-join option. (#15989) 2025-05-05 08:35:16 -05:00
damencho
0b2db71a6d feat(tests): Prefer to generate token for dial in. 2025-05-05 08:35:04 -05:00
Matteo
087ca5e6e4 lang: Update Italian translation (#15991)
* Update Italian translation

- Added new translated strings
- Improved already translated strings

* Fix some other strings

- Fix some typos
2025-05-05 07:15:26 -05:00
Дамян Минков
f9927e4cd7 feat(tests): Adds invite test. (#15986)
* feat(tests): Adds invite test.

Tests dial-in, dial-out and inviting sip-jibri.

* squash: Extract duplicate code in a function.

* squash: Fixes comments.
2025-05-02 09:41:48 -05:00
damencho
f31f9e1979 feat(tests): Handle and final transcriptions. 2025-05-01 10:08:57 -05:00
damencho
25cbe888a1 feat(tests): Adds debug log for webhooks. 2025-05-01 10:08:57 -05:00
raduanastase8x8
6a43ecc1dc fix(settings,a11y) extract Test button outisde the radio button 2025-05-01 09:12:55 +02:00
Saúl Ibarra Corretgé
082c4c325d feat(recording) add ability to skip consent in-meeting
When turned on, the consent dialog won't be displayed for the users who
are already in the meeting, it will only be displayed to those who join
after the recording was started.
2025-04-30 15:58:58 +02:00
Saúl Ibarra Corretgé
4878874a68 fix(local-recordings) fix data loss when MediaRecorder is stopped
Flush the file after the 'stop' event is emitted, which happens _after_
the last 'dataavailable' has been emitted, and thus when the
MediaRecorder is really done.

In addition, lower the time slice as added precaution against crashes.
2025-04-30 15:57:18 +02:00
Saúl Ibarra Corretgé
178e87d408 fix(local-recordings) more resilient way to get local audio
It's OK if we don't have any local audio track, we'll add it to the
mixer later.

The original bug / limitation that prompted the previous code no longer
applies since we always have a MediaStream (with audio tracks) which
we are recording.
2025-04-30 15:57:18 +02:00
Saúl Ibarra Corretgé
5a4306ee38 fix(local-recordings) remove text mentioning time limit 2025-04-30 15:57:18 +02:00
Saúl Ibarra Corretgé
4fcab33afb feat(local-recordings) refactor how audio is captured
Capture the tab audio, which will include all participants and sound
effects, YouTube videos, anything playing in the tab.

This requires the `suppressLocalAudioPlayback` constraint since
otherwise the shared tab won't keep playing audio.

Local audio still needs to be injected seprarately, since it's not
played back to the local user.
2025-04-30 15:57:18 +02:00
Saúl Ibarra Corretgé
99669dc869 fix(local-recordings) style, for readability 2025-04-30 15:57:18 +02:00
Saúl Ibarra Corretgé
bf34c9ab19 fix(local-recording) require setCaptureHandleConfig 2025-04-30 15:57:18 +02:00
Saúl Ibarra Corretgé
f6f4ebf185 fix(recording) prevent multiple consent requests
A given recording should only trigger a single consent request.

The mechanism to notify about recording status updates may fire multiple
times since it's tied to XMPP presence and may send updates such as when
the live stream view URL is set.

Rather than trying to handle all possible corner cases to make sure we
only show the consent dialog once, keep track of the recording session
IDs for which we _have_ asked for consent and skip the dialog in case we
have done it already.
2025-04-30 15:30:51 +02:00
Calinteodor
b500c9dcde fix(base/connection/native): add a check for vpass meeting when we connect (#15978)
When we connect to a VPASS meeting on mobile we need to check for a couple of things.
2025-04-30 15:16:25 +03:00
Hristo Terezov
d5670a2b4f chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1980.0.0+34a32e86...v1982.0.0+cec2a2e6
2025-04-29 21:16:21 -05:00
Saúl Ibarra Corretgé
ee3f82bf0c feat(external_api,devices) drop use of isDeviceListAvailable
It's always true.
2025-04-29 19:37:55 +02:00
111 changed files with 1613 additions and 1009 deletions

View File

@@ -2069,8 +2069,7 @@ export default {
_initDeviceList(setDeviceListChangeHandler = false) {
const { mediaDevices } = JitsiMeetJS;
if (mediaDevices.isDeviceListAvailable()
&& mediaDevices.isDeviceChangeAvailable()) {
if (mediaDevices.isDeviceChangeAvailable()) {
if (setDeviceListChangeHandler) {
this.deviceChangeListener = devices =>
window.setTimeout(() => this._onDeviceListChanged(devices), 0);

View File

@@ -401,6 +401,10 @@ var config = {
// // If true, mutes audio and video when a recording begins and displays a dialog
// // explaining the effect of unmuting.
// // requireConsent: true,
// // If true consent will be skipped for users who are already in the meeting.
// // skipConsentInMeeting: true,
// // Link for the recording consent dialog's "Learn more" link.
// // consentLearnMoreLink: 'https://jitsi.org/meet/consent',
// },
// recordingService: {

View File

@@ -835,7 +835,6 @@
"or": "أو",
"premeeting": "ما قبل المُلتقى",
"screenSharingError": "خطأ في مشاركة الشاشة:",
"showScreen": "تفعيل واجهة ما قبل المُلتقى",
"startWithPhone": "البدء مع جهاز الصوت من الجوال",
"videoOnlyError": "خطأ في الفيديو:",
"videoTrackError": "لم نتمكن من إنشاء ملف الفيديو",

View File

@@ -842,7 +842,6 @@
"or": "o",
"premeeting": "Prereunió",
"screenSharingError": "Error en compartir la pantalla:",
"showScreen": "Activa la pantalla de prereunió",
"startWithPhone": "Comença amb àudio de telèfon",
"videoOnlyError": "Error del vídeo:",
"videoTrackError": "No s'ha pogut crear la pista de vídeo.",
@@ -916,7 +915,7 @@
"localRecordingStartWarningTitle": "Atura l'enregistrament per a desar-lo",
"localRecordingVideoStop": "Aturar el vídeo també aturarà l'enregistrament local. Segur que voleu continuar?",
"localRecordingVideoWarning": "Per a enregistrar el vostre vídeo, cal que ho feu en començar l'enregistrament",
"localRecordingWarning": "Assegureu-vos de triar la pestanya actual per a poder usar el vídeo i àudios correctes. L'enregistrament actualment està limitat a 1 GB. Això són, aproximadament, 100 minuts.",
"localRecordingWarning": "Assegureu-vos de triar la pestanya actual per a poder usar el vídeo i àudios correctes.",
"loggedIn": "Sessió iniciada com a {{userName}}",
"noStreams": "No s'ha detectat flux d'àudio ni vídeo.",
"off": "S'ha aturat l'enregistrament",

View File

@@ -976,7 +976,6 @@
"proceedAnyway": "Přesto pokračujte",
"recordingWarning": "Ostatní účastníci mohou tento hovor nahrávat",
"screenSharingError": "Chyba sdílení obrazovky:",
"showScreen": "Zapnout obrazovku před setkáním",
"startWithPhone": "Začít se zvukem přes telefon",
"unsafeRoomConsent": "Chápu rizika, chci se připojit k setkání",
"videoOnlyError": "Chyba videa:",
@@ -1047,7 +1046,7 @@
"localRecordingStartWarningTitle": "Zastavte záznam, abyste jej uložili",
"localRecordingVideoStop": "Zastavením videa se zastaví také místní nahrávání. Opravdu chcete pokračovat?",
"localRecordingVideoWarning": "Chcete-li nahrát video, musíte jej mít zapnutý při zahájení nahrávání",
"localRecordingWarning": "Ujistěte se, že jste vybrali aktuální kartu, abyste mohli použít správné video a zvuk. Záznam je aktuálně omezen na 1GB, což je kolem 100 minut.",
"localRecordingWarning": "Ujistěte se, že jste vybrali aktuální kartu, abyste mohli použít správné video a zvuk.",
"loggedIn": "Přihlášen/a jako {{userName}}",
"noMicPermission": "Kanál mikrofonu nelze vytvořit. Udělte prosím povolení k použití mikrofonu.",
"noStreams": "Nebyl zjištěn žádný audio nebo video stream.",

View File

@@ -984,7 +984,6 @@
"proceedAnyway": "Trotzdem fortsetzen",
"recordingWarning": "Diese Konferenz wird möglicherweise von anderen Personen aufgezeichnet",
"screenSharingError": "Fehler bei Bildschirmfreigabe:",
"showScreen": "Konferenzvorschau aktivieren",
"startWithPhone": "Mit Telefonaudio starten",
"unsafeRoomConsent": "Ich verstehe das Risiko und möchte der Konferenz beitreten",
"videoOnlyError": "Videofehler:",
@@ -1055,7 +1054,7 @@
"localRecordingStartWarningTitle": "Aufzeichnung zum Speichern beenden",
"localRecordingVideoStop": "Wenn Sie ihre Kamera abschalten wird auch die Aufnahme beendet. Sind Sie sicher, dass Sie fortfahren möchten?",
"localRecordingVideoWarning": "Um Ihr eigenes Kamerabild aufzuzeichnen, müssen Sie Ihre Kamera beim Start der Aufnahme einschalten",
"localRecordingWarning": "Bitte prüfen Sie, dass das aktuelle Tab auswählen, um Bild und Ton aufzuzeichnen. Die Länge der Aufzeichnung ist aktuell auf 1GB beschränkt, was ungefähr 100 Minuten entspricht.",
"localRecordingWarning": "Bitte prüfen Sie, dass das aktuelle Tab auswählen, um Bild und Ton aufzuzeichnen.",
"loggedIn": "Als {{userName}} angemeldet",
"noMicPermission": "Zugriff auf Mikrofon fehlgeschlagen. Bitte erlauben Sie den Zugriff auf das Mikrofon.",
"noStreams": "Kein Ton oder Video erkannt.",

View File

@@ -845,7 +845,6 @@
"or": "abo",
"premeeting": "naglěd",
"screenSharingError": "zmólenje pśi sobuźělenju monitora:",
"showScreen": "naglěd konferency aktiwěrowaś",
"startWithPhone": "zachopiś z telefonowym audio",
"videoOnlyError": "zmólenje wideo:",
"videoTrackError": "Sćažka wideo njejo mógła se załožyś.",
@@ -916,7 +915,7 @@
"localRecordingStartWarningTitle": "nagrawanje dokóńcowaś a zachowaś",
"localRecordingVideoStop": "Gaž kameru wušaltujośo, ga teke lokalne nagrawanje se dokóńcujo. Sćo-li napšawdu wěste, až to cośo?",
"localRecordingVideoWarning": "Aby bildu ze swójeje samskeje kamery nagrawali, musyśo swóju kameru na zachopjeńku nagrawanja zašaltowaś.",
"localRecordingWarning": "Pśespytujśo, lěc sćo aktuelnu kórtu wuzwólili, aby wideo a zuk nagrawali. Dłujkosć nagrawanja jo tuchylu na 1 GB wobgranicowana, což dosega za jadnab 100 minutow.",
"localRecordingWarning": "Pśespytujśo, lěc sćo aktuelnu kórtu wuzwólili, aby wideo a zuk nagrawali.",
"loggedIn": "ako {{userName}} zalogowany/-a",
"noMicPermission": "",
"noStreams": "Žeden zuk a žeden wideo njejo namakany.",

View File

@@ -862,7 +862,6 @@
"or": "ή",
"premeeting": "Προ σύσκεψη",
"screenSharingError": "Σφάλμα διαμοιρασμού οθόνης:",
"showScreen": "Ενεργοποίηση οθόνης προ σύσκεψης",
"startWithPhone": "Ξεκινήστε με ήχο τηλεφώνου",
"videoOnlyError": "Σφάλμα βίντεο:",
"videoTrackError": "Δεν ήταν δυνατή η δημιουργία κομματιού βίντεο.",
@@ -930,7 +929,7 @@
"localRecordingStartWarningTitle": "Σταματήστε τη καταγραφή για να την αποθηκεύσετε",
"localRecordingVideoStop": "Σταματώντας το βίντεο σας θα σταματήσει και η τοπική καταγραφή. Θέλετε να συνεχίσετε;",
"localRecordingVideoWarning": "Για να καταγράψετε το βίντεο σας πρέπει να είναι ήδη ενεργό όταν ξεκινήσει η καταγραφή",
"localRecordingWarning": "Βεβαιωθείτε ότι επιλέξατε τη τρέχουσα σελίδα ώστε να χρησιμοποιηθούν το σωστό βίντεο και ήχος. Αυτή τη στιγμή η καταγραφή είναι μέχρι 1GB, περίπου 100 λεπτά.",
"localRecordingWarning": "Βεβαιωθείτε ότι επιλέξατε τη τρέχουσα σελίδα ώστε να χρησιμοποιηθούν το σωστό βίντεο και ήχος.",
"loggedIn": "Συνδέθηκε ως {{userName}}",
"noMicPermission": "Δεν ήταν δυνατή η δημιουργία του κομματιού του μικροφώνου. Παρακαλώ δώστε την άδεια για χρήση του.",
"noStreams": "Δεν εντοπίστηκε ροή ήχου ή βίντεο.",

View File

@@ -935,7 +935,6 @@
"premeeting": "Antaŭkunveno",
"proceedAnyway": "Daŭrigi",
"screenSharingError": "Eraro kun la ekrandividado:",
"showScreen": "Ebligu antaŭkunvenon ekranon",
"startWithPhone": "Komencu kun la telefona sono",
"unsafeRoomConsent": "Akceptu la riskojn, kaj daŭrigi",
"videoOnlyError": "Eraro kun la videaĵo:",
@@ -1007,7 +1006,7 @@
"localRecordingStartWarningTitle": "Ĉesigu la registradon por konservi ĝin.",
"localRecordingVideoStop": "Ĉesigi vian videaĵon ankaŭ ĉesos la lokan registradon. Ĉu vi certas, ke vi volas daŭrigi?",
"localRecordingVideoWarning": "Por registri vian videon, ŝaltu la kameraon antaŭ vi komencas la registradon.",
"localRecordingWarning": "Certigu, ke vi elektas la nunan langeton por uzi la ĝustajn filmetojn kaj sonojn. La registrado estas nuntempe limigita al 1GB, kio estas proksimume 100 minutoj.",
"localRecordingWarning": "Certigu, ke vi elektas la nunan langeton por uzi la ĝustajn filmetojn kaj sonojn.",
"loggedIn": "Ensalutinta kiel {{userName}}",
"noMicPermission": "Mikrofono ne povis esti uzata. Bonvole donu permeson uzi la mikrofonon.",
"noStreams": "Neniu aŭdio aŭ videofluo detektita.",

View File

@@ -886,7 +886,6 @@
"premeeting": "Pre-reunión",
"proceedAnyway": "Continuar de todos modos",
"screenSharingError": "Error al compartir pantalla:",
"showScreen": "Habilitar pantalla pre-reunión",
"startWithPhone": "Iniciar con audio de llamada telefónica",
"unsafeRoomConsent": "Comprendo los riesgos, quiero unirme a la reunión",
"videoOnlyError": "Error con el vídeo:",
@@ -958,7 +957,7 @@
"localRecordingStartWarningTitle": "Detenga la grabación para guardarla",
"localRecordingVideoStop": "Detener su video también detendrá la grabación local. ¿Está seguro de querer continuar?",
"localRecordingVideoWarning": "Para grabar su video debe tenerlo encendido al iniciar la grabación",
"localRecordingWarning": "Asegúrese de seleccionar la pestaña actual para usar el video y audio correctos. La grabación está actualmente limitada a 1GB, que son aproximadamente 100 minutos.",
"localRecordingWarning": "Asegúrese de seleccionar la pestaña actual para usar el video y audio correctos.",
"loggedIn": "Sesión iniciada como {{userName}}",
"noMicPermission": "No se pudo crear la pista de micrófono. Por favor otorgue permiso para usar el micrófono.",
"noStreams": "",

View File

@@ -759,7 +759,6 @@
"or": "o",
"premeeting": "Pre-reunión",
"screenSharingError": "Error al compartir pantalla:",
"showScreen": "Habilitar pantalla pre-reunión",
"startWithPhone": "Iniciar con audio de llamada telefónica",
"videoOnlyError": "Error con el video:",
"videoTrackError": "No se pudo crear la pista de video.",

View File

@@ -646,7 +646,6 @@
"or": "edo",
"premeeting": "Aurre-bilera",
"screenSharingError": "Errorea pantaila partekatzean:",
"showScreen": "Aktibatu bileraren aurreko pantaila",
"startWithPhone": "Telefono diearen audioarekin hasi",
"videoOnlyError": "Errorea bideoan:",
"videoTrackError": "Ezin izan da bideo pista sortu.",

View File

@@ -894,7 +894,6 @@
"premeeting": "پیش‌جلسه",
"proceedAnyway": "در هر صورت انجام شود",
"screenSharingError": "خطا در هم‌رسانی صفحه:",
"showScreen": "فعال‌سازی صفحهٔ پیش‌جلسه",
"startWithPhone": "شروع با صدای گوشی",
"unsafeRoomConsent": "من خطر احتمالی را درک می‌کنم؛ می‌خواهم به جلسه بپیوندم",
"videoOnlyError": "خطای ویدیو:",

View File

@@ -976,7 +976,6 @@
"proceedAnyway": "Continuer quand même",
"recordingWarning": "D'autres participants peuvent enregistrer cet appel",
"screenSharingError": "Erreur de partage d'écran:",
"showScreen": "Activer l'écran de pré-séance",
"startWithPhone": "Commencez avec l'audio du téléphone",
"unsafeRoomConsent": "Je comprends les risques et je veux quand même rejoindre cette réunion",
"videoOnlyError": "Erreur vidéo:",
@@ -1047,7 +1046,7 @@
"localRecordingStartWarningTitle": "Arrêter lenregistrement pour le sauvegarder",
"localRecordingVideoStop": "Arrêter votre vidéo va aussi arrêter votre enregistrement local. Êtes-vous sûrs de vouloir continuer ?",
"localRecordingVideoWarning": "Pour enregistrer votre vidéo, vous devez avoir celle-ci active au moment de commencer lenregistrement.",
"localRecordingWarning": "Assurez-vous de sélectionner longlet courant pour utiliser le bon son et la bonne vidéo. Lenregistrement est pour le moment limité à 1Go, soit approximativement 100 minutes.",
"localRecordingWarning": "Assurez-vous de sélectionner longlet courant pour utiliser le bon son et la bonne vidéo.",
"loggedIn": "Connecté en tant que {{userName}}",
"noMicPermission": "La piste microphone ne peut pas être créée. Veuillez autoriser lutilisation du microphone.",
"noStreams": "Aucun flux audio ou vidéo détectés.",

View File

@@ -954,7 +954,6 @@
"premeeting": "Pré-séance",
"proceedAnyway": "Continuer quand même",
"screenSharingError": "Erreur de partage d'écran:",
"showScreen": "Activer l'écran de pré-séance",
"startWithPhone": "Commencez avec l'audio du téléphone",
"unsafeRoomConsent": "Je comprends les risques et je veux quand même rejoindre cette réunion",
"videoOnlyError": "Erreur vidéo:",
@@ -1026,7 +1025,7 @@
"localRecordingStartWarningTitle": "Arrêter lenregistrement pour le sauvegarder",
"localRecordingVideoStop": "Arrêter votre vidéo va aussi arrêter votre enregistrement local. Êtes-vous sûrs de vouloir continuer ?",
"localRecordingVideoWarning": "Pour enregistrer votre vidéo, vous devez avoir celle-ci active au moment de commencer lenregistrement.",
"localRecordingWarning": "Assurez-vous de sélectionner longlet courant pour utiliser le bon son et la bonne vidéo. Lenregistrement est pour le moment limité à 1Go, soit approximativement 100 minutes.",
"localRecordingWarning": "Assurez-vous de sélectionner longlet courant pour utiliser le bon son et la bonne vidéo.",
"loggedIn": "Connecté en tant que {{userName}}",
"noMicPermission": "La piste microphone ne peut pas être créée. Veuillez autoriser lutilisation du microphone.",
"noStreams": "Aucun flux audio ou vidéo détectés.",

View File

@@ -622,7 +622,6 @@
"or": "या",
"premeeting": "प्री मीटिंग",
"screenSharingError": "स्क्रीन शेयरिंग त्रुटि:",
"showScreen": "प्री मीटिंग स्क्रीन सक्षम करें",
"startWithPhone": "फोन ऑडियो से शुरू करें",
"videoOnlyError": "वीडियो त्रुटि:",
"videoTrackError": "वीडियो ट्रैक नहीं बना सका",

View File

@@ -840,7 +840,6 @@
"or": "ili",
"premeeting": "Predsastanak",
"screenSharingError": "Greška dijeljenja ekrana:",
"showScreen": "Uključi ekran predsastanka",
"startWithPhone": "Počni s telefonom",
"videoOnlyError": "Greška videa:",
"videoTrackError": "Nije bilo moguće stvoriti videosnimku.",
@@ -914,7 +913,7 @@
"localRecordingStartWarningTitle": "Prekini snimanje kako bi se spremilo",
"localRecordingVideoStop": "Prekidanje tvog videa će također prekinuti lokalno snimanje. Stvarno želiš nastaviti?",
"localRecordingVideoWarning": "Za snimanje tvog videa, on mora biti uključen kada započneš snimanje",
"localRecordingWarning": "Odaberi trenutačnu karticu za korištenje ispravnog videa i zvuka. Snimanje je trenutačno ograničeno na 1 GB, što je oko 100 minuta.",
"localRecordingWarning": "Odaberi trenutačnu karticu za korištenje ispravnog videa i zvuka.",
"loggedIn": "Prijava kao {{userName}}",
"noStreams": "Nema otkrivenih audio niti videoprijenosa.",
"off": "Snimanje prekinuto",

View File

@@ -824,7 +824,6 @@
"or": "abo",
"premeeting": "předstwa",
"screenSharingError": "zmylk při dopušćenju wužiwanja monitora:",
"showScreen": "konferencnu předstwu aktiwěrować",
"startWithPhone": "z telefoniskim awdijom startować",
"videoOnlyError": "widejowy zmylk:",
"videoTrackError": "widejowy trak njebě móžny",
@@ -892,7 +891,7 @@
"limitNotificationDescriptionWeb": "Wulkeje potrjeby dla je Waše nahrawanje na {{limit}} mjeń. wobmjezowane. Za njewobmjezowane nahrawanje wužiwajće prošu <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"linkGenerated": "link na nahrawanje bu kreěrowany",
"live": "LIVE",
"localRecordingWarning": "Kedźbujće na to, aktualny tab za prawe widejo a awdijo wužiwać. Nahrawanje je wokomiknje hač do 1GB - někak 100 mjeńšinow - móžne.",
"localRecordingWarning": "Kedźbujće na to, aktualny tab za prawe widejo a awdijo wužiwać.",
"loggedIn": "přizjewjeny/a jako {{userName}} ",
"off": "nahrawanje stopowane",
"offBy": "{{name}} stopuje nahrawanje",

View File

@@ -683,7 +683,6 @@
"or": "vagy",
"premeeting": "Csatlakozás előtt",
"screenSharingError": "Képernyő megosztás hiba:",
"showScreen": "Csatlakozás előtti kamerakép",
"startWithPhone": "Kezdés telefonhanggal",
"videoOnlyError": "Videó hiba:",
"videoTrackError": "Nem sikerült a videó megjelenítés.",
@@ -726,7 +725,7 @@
"localRecordingNoNotificationWarning": "A felvételt nem közöljük más résztvevőkkel. Értesítenie kell velük, hogy a találkozót rögzítették.",
"localRecordingStartWarning": "A megbeszélésből való kilépés előtt feltétlenül állítsa le a felvételt, hogy elmentse azt.",
"localRecordingStartWarningTitle": "Állítsa le a felvételt a mentéshez",
"localRecordingWarning": "Győződjön meg arról, hogy az aktuális lapot választotta a megfelelő videó és hang használatához. A felvétel jelenleg 1 GB-ra van korlátozva, ami körülbelül 100 perc.",
"localRecordingWarning": "Győződjön meg arról, hogy az aktuális lapot választotta a megfelelő videó és hang használatához.",
"loggedIn": "Belépve mint {{userName}}",
"off": "Felvétel leállítva",
"offBy": "{{name}} leállította a felvételt",

View File

@@ -955,7 +955,6 @@
"proceedAnyway": "Lanjutkan saja",
"recordingWarning": "Peserta lain mungkin sedang merekam panggilan ini",
"screenSharingError": "Kesalahan berbagi layar:",
"showScreen": "Aktifkan layar pra pertemuan",
"startWithPhone": "Mulai dengan audio ponsel",
"unsafeRoomConsent": "Saya memahami risikonya, saya ingin bergabung dengan pertemuan",
"videoOnlyError": "Kesalahan video:",
@@ -1026,7 +1025,7 @@
"localRecordingStartWarningTitle": "Hentikan rekaman untuk menyimpannya",
"localRecordingVideoStop": "Menghentikan video Anda juga akan menghentikan rekaman lokal. Apakah Anda yakin ingin melanjutkan?",
"localRecordingVideoWarning": "Untuk merekam video Anda, Anda harus mengaktifkannya saat memulai rekaman",
"localRecordingWarning": "Pastikan Anda memilih tab saat ini untuk menggunakan video dan audio yang tepat. Rekaman saat ini dibatasi hingga 1GB, sekitar 100 menit.",
"localRecordingWarning": "Pastikan Anda memilih tab saat ini untuk menggunakan video dan audio yang tepat.",
"loggedIn": "Masuk sebagai {{userName}}",
"noMicPermission": "Trek mikrofon tidak dapat dibuat. Harap izinkan penggunaan mikrofon.",
"noStreams": "Tidak ada aliran audio atau video yang terdeteksi.",

View File

@@ -936,7 +936,6 @@
"premeeting": "Á undan fundi",
"proceedAnyway": "Halda samt áfram",
"screenSharingError": "Villa í skjádeilingu:",
"showScreen": "Virkja skjá á undan fundi",
"startWithPhone": "Byrja með símahljóði",
"unsafeRoomConsent": "Ég skil áhættuna, ég vil taka þátt í fundinum",
"videoOnlyError": "Villa í myndmerki:",
@@ -1007,7 +1006,7 @@
"localRecordingStartWarningTitle": "Stöðvaðu upptöku til að vista hana",
"localRecordingVideoStop": "Ef þú stöðvar myndmerkið þitt stöðvast einnig upptakan á tölvunni. Ertu viss um að þú viljir halda áfram?",
"localRecordingVideoWarning": "Til að taka upp myndmerkið þitt þarf það að vera í gangi þegar upptakan er sett af stað",
"localRecordingWarning": "Gakktu úr skugga um að þú veljir fyrirliggjandi flipa til að nota rétt mynd- og hljóðmerki. Upptakan er takmörkuð við 1GB, sem gera í kringum 100 mínútur.",
"localRecordingWarning": "Gakktu úr skugga um að þú veljir fyrirliggjandi flipa til að nota rétt mynd- og hljóðmerki.",
"loggedIn": "Skráð inn sem {{userName}}",
"noMicPermission": "Ekki var hægt að útbúa hljóðnemarás. Veittu heimildir til að nota hljóðnemann.",
"noStreams": "Ekkert hljóð eða myndstreymi fannst",

File diff suppressed because it is too large Load Diff

View File

@@ -784,7 +784,6 @@
"or": "または",
"premeeting": "プレミーティング",
"screenSharingError": "画面共有のエラー:",
"showScreen": "プレミーティング画面を有効",
"startWithPhone": "音声通話を開始",
"videoOnlyError": "ビデオのエラー:",
"videoTrackError": "ビデオトラックを生成できませんでした。",
@@ -846,7 +845,7 @@
"localRecordingStartWarningTitle": "録画を停止して保存",
"localRecordingVideoStop": "ビデオを停止すると録画も停止されます。停止してもよろしいですか?",
"localRecordingVideoWarning": "ビデオを録画するためには録画開始前に有効化しておく必要があります",
"localRecordingWarning": "正しく録画をするために必ず現在のタブを選択してください。現在録画は1GBまで、約100分に制限されています。",
"localRecordingWarning": "正しく録画をするために必ず現在のタブを選択してください。",
"loggedIn": "{{userName}} としてログイン",
"noMicPermission": "マイクが認識できませんでした。マイクへのアクセス権を確認してください。",
"noStreams": "音声・映像が検出されませんでした。",

View File

@@ -736,7 +736,6 @@
"or": "neɣ",
"premeeting": "Timlilit tuzwirt",
"screenSharingError": "Tuccḍa deg beṭṭu n ugdil:",
"showScreen": "Rmed agdil n temlilit tuzwirt",
"startWithPhone": "Bdu s umeslaw n tiliɣri",
"videoOnlyError": "Tuccḍa deg tvidyut:",
"videoTrackError": "Asnulfu n track n tvidyut ulamek.",

View File

@@ -975,7 +975,6 @@
"proceedAnyway": "그래도 진행",
"recordingWarning": "다른 참가자가 이 통화를 녹화하고 있을 수 있습니다",
"screenSharingError": "화면 공유 오류:",
"showScreen": "회의 전 화면 활성화",
"startWithPhone": "전화 오디오로 시작",
"unsafeRoomConsent": "위험을 이해하며 회의에 참여하고 싶습니다",
"videoOnlyError": "비디오 오류:",
@@ -1046,7 +1045,7 @@
"localRecordingStartWarningTitle": "저장을 위해 녹화를 중지하세요",
"localRecordingVideoStop": "비디오를 중지하면 로컬 녹화도 중지됩니다. 계속하시겠습니까?",
"localRecordingVideoWarning": "비디오를 녹화하려면 녹화를 시작할 때 켜져 있어야 합니다",
"localRecordingWarning": "올바른 비디오 및 오디오를 사용하려면 현재 탭을 선택해야 합니다. 녹화는 현재 1GB로 제한되어 있으며, 이는 약 100분입니다.",
"localRecordingWarning": "올바른 비디오 및 오디오를 사용하려면 현재 탭을 선택해야 합니다.",
"loggedIn": "{{userName}}으로 로그인했습니다.",
"noMicPermission": "마이크 트랙을 생성할 수 없습니다. 마이크 사용 권한을 부여하세요.",
"noStreams": "오디오 또는 비디오 스트림이 감지되지 않았습니다.",

View File

@@ -982,7 +982,6 @@
"proceedAnyway": "Tik un tā turpināt",
"recordingWarning": "Citi dalībnieki var ierakstīt šo zvanu",
"screenSharingError": "Ekrāna koplietošanas kļūda:",
"showScreen": "Iespējot ekrānu pirms sapulces",
"startWithPhone": "Sākt ar tālruņa audio",
"unsafeRoomConsent": "Es saprotu riskus, vēlos pievienoties sapulcei",
"videoOnlyError": "Video kļūda:",
@@ -1053,7 +1052,7 @@
"localRecordingStartWarningTitle": "Apturiet ierakstīšanu, lai to saglabātu",
"localRecordingVideoStop": "Apturot vide, tiks apturēta arī lokālā ierakstīšana. Vai tiešām vēlaties turpināt?",
"localRecordingVideoWarning": "Lai ierakstītu video, tas ir jāieslēdz, uzsākot ierakstīšanu",
"localRecordingWarning": "Noteikti izvēlieties pašreizējo cilni, lai izmantotu pareizo video un audio. Ieraksts pašlaik ir ierobežots līdz 1 GB, kas ir aptuveni 100 minūtes.",
"localRecordingWarning": "Noteikti izvēlieties pašreizējo cilni, lai izmantotu pareizo video un audio.",
"loggedIn": "Pierakstījies kā {{userName}}",
"noMicPermission": "Mikrofona ierakstu nevarēja izveidot. Lūdzu, piešķiriet atļauju lietot mikrofonu.",
"noStreams": "Nav konstatēta audio vai video straume.",

View File

@@ -602,7 +602,6 @@
"or": "അല്ലെങ്കിൽ",
"premeeting": "പ്രീ മീറ്റിംഗ്",
"screenSharingError": "സ്ക്രീൻ പങ്കിടൽ പിശക്:",
"showScreen": "പ്രീ മീറ്റിംഗ് സ്ക്രീൻ പ്രാപ്തമാക്കുക",
"startWithPhone": "ഫോൺ ഓഡിയോ ഉപയോഗിച്ച് ആരംഭിക്കുക",
"videoOnlyError": "വീഡിയോ പിശക്:",
"videoTrackError": "വീഡിയോ ട്രാക്ക് സൃഷ്ടിക്കാൻ കഴിഞ്ഞില്ല.",

View File

@@ -872,7 +872,6 @@
"or": "эсвэл",
"premeeting": "Уулзалтын өмнө",
"screenSharingError": "Дэлгэц хуваалцахын алдаа:",
"showScreen": "Уулзалтын өмгөх дэлгэц идэвхижүүлэх",
"startWithPhone": "Утасны дуугаар холбогдох",
"videoOnlyError": "Видео дамжуулалтын алдаа:",
"videoTrackError": "Видео бичлэг үүсгэж чадсангүй.",
@@ -943,7 +942,7 @@
"localRecordingStartWarningTitle": "Хадгалахын тулд бичлэгээ зогсооно уу",
"localRecordingVideoStop": "Видео бичлэгээ зогсоовол өөр дээрээ файлаар хадгалах нь хамтдаа зогсоно. Та үргэлжлүүлэхдээ итгэлтэй байна уу?",
"localRecordingVideoWarning": "To record your video you must have it on when starting the recording",
"localRecordingWarning": "Дуу болон видео дамжуулалтын зөвшөөрлийг өгөхийн тулд энэ табыг сонгоно уу. Бичлэгийн дээд хэмжээ нь 1ГБ буюу ойролцоогоор The recording is currently limited to 1GB, which is around 100 minutes.",
"localRecordingWarning": "Дуу болон видео дамжуулалтын зөвшөөрлийг өгөхийн тулд энэ табыг сонгоно уу.",
"loggedIn": "{userName} нэвтэрнэ үү",
"noMicPermission": "Микрофон олдсонгүй. Микрофон ашиглах зөвшөөрлөө өгнө үү.",
"noStreams": "Дуу эсвэл видео дамжуулалт олдсонгүй.",

View File

@@ -976,7 +976,6 @@
"proceedAnyway": "Fortsett likevel",
"recordingWarning": "Andre deltakere kan ta opp denne samtalen",
"screenSharingError": "Feil ved skjermdeling:",
"showScreen": "Aktiver skjerm før møtet",
"startWithPhone": "Start med telefonlyd",
"unsafeRoomConsent": "Jeg forstår risikoen, jeg vil bli med i møtet",
"videoOnlyError": "Video feil:",
@@ -1047,7 +1046,7 @@
"localRecordingStartWarningTitle": "Stopp opptaket for å lagre det",
"localRecordingVideoStop": "Hvis du stopper videoen, vil også det lokale opptaket stoppe. Er du sikker på at du vil fortsette?",
"localRecordingVideoWarning": "For å ta opp videoen din, må du ha den på når opptaket starter",
"localRecordingWarning": "Pass på å velge den gjeldende fanen for å bruke riktig video og lyd. Opptaket er for øyeblikket begrenset til 1 GB, noe som tilsvarer rundt 100 minutter.",
"localRecordingWarning": "Pass på å velge den gjeldende fanen for å bruke riktig video og lyd.",
"loggedIn": "Logget inn som {{userName}}",
"noMicPermission": "Kunne ikke opprette mikrofonspor. Vennligst gi tillatelse til å bruke mikrofonen.",
"noStreams": "Ingen lyd- eller videostrøm oppdaget.",

View File

@@ -723,7 +723,6 @@
"or": "of",
"premeeting": "Voorbeeldscherm",
"screenSharingError": "Fout bij schermdeling:",
"showScreen": "Voorbeeldscherm inschakelen",
"startWithPhone": "Starten met telefoonaudio",
"videoOnlyError": "Videofout:",
"videoTrackError": "Kon videotrack niet aanmaken.",

View File

@@ -976,7 +976,6 @@
"proceedAnyway": "Fortsett likevel",
"recordingWarning": "Andre deltakere kan ta opp denne samtalen",
"screenSharingError": "Feil ved skjermdeling:",
"showScreen": "Aktiver skjerm før møtet",
"startWithPhone": "Start med telefonlyd",
"unsafeRoomConsent": "Jeg forstår risikoen, jeg vil bli med i møtet",
"videoOnlyError": "Video feil:",
@@ -1047,7 +1046,7 @@
"localRecordingStartWarningTitle": "Stopp opptaket for å lagre det",
"localRecordingVideoStop": "Hvis du stopper videoen, vil også det lokale opptaket stoppe. Er du sikker på at du vil fortsette?",
"localRecordingVideoWarning": "For å ta opp videoen din, må du ha den på når opptaket starter",
"localRecordingWarning": "Pass på å velge den gjeldende fanen for å bruke riktig video og lyd. Opptaket er for øyeblikket begrenset til 1 GB, noe som tilsvarer rundt 100 minutter.",
"localRecordingWarning": "Pass på å velge den gjeldende fanen for å bruke riktig video og lyd.",
"loggedIn": "Logget inn som {{userName}}",
"noMicPermission": "Kunne ikke opprette mikrofonspor. Vennligst gi tillatelse til å bruke mikrofonen.",
"noStreams": "Ingen lyd- eller videostrøm oppdaget.",

View File

@@ -976,7 +976,6 @@
"proceedAnyway": "Contunhar malgrat tot",
"recordingWarning": "D'autres participants pòdon enregistrar aquesta sonada",
"screenSharingError": "Error de partatge decran:",
"showScreen": "Activar l'ecran de prereünion",
"startWithPhone": "Començar amb làudio del telefòn",
"unsafeRoomConsent": "Compreni lo risc e vòli çaquelà participar a la reünion",
"videoOnlyError": "Error vidèo:",
@@ -1047,7 +1046,7 @@
"localRecordingStartWarningTitle": "Arrestar l'enregistrament per lo salvar",
"localRecordingVideoStop": "Arrestar la vidèo arrestarà tanben l'enregistrament local. Volètz vertadièrament contunhar?",
"localRecordingVideoWarning": "Per enregistrar vòstra vidèo, devètz l'aver activada en començant l'enregistrament",
"localRecordingWarning": "Asseguratz-vos de causir l'onglet actual per utilizar lo son e la vidèo corrècts. L'enregistrament es actualament limitat a 1Go, siá aproximativament 100minutas.",
"localRecordingWarning": "Asseguratz-vos de causir l'onglet actual per utilizar lo son e la vidèo corrècts.",
"loggedIn": "Session a {{userName}}",
"noMicPermission": "Se pòt pas crear la pista del microfòn. Volgatz autorizar l'utilizacion del microfòn.",
"noStreams": "Cap de flux àudio o vidèo pas detectat.",

View File

@@ -874,7 +874,6 @@
"premeeting": "Przed spotkaniem",
"proceedAnyway": "Kontynuuj mimo to",
"screenSharingError": "Błąd udostępniania ekranu:",
"showScreen": "Tryb osobistej poczekalni przed spotkaniem",
"startWithPhone": "Uruchom przez telefon",
"unsafeRoomConsent": "Rozumiem ryzyko, chcę dołączyć do spotkania",
"videoOnlyError": "Błąd wideo:",
@@ -946,7 +945,7 @@
"localRecordingStartWarningTitle": "Zatrzymaj nagrywanie, aby je zapisać",
"localRecordingVideoStop": "Zatrzymanie filmu spowoduje również zatrzymanie nagrywania lokalnego. Jesteś pewien, że chcesz kontynuować?",
"localRecordingVideoWarning": "Aby nagrać film, musisz go mieć na początku nagrywania",
"localRecordingWarning": "Upewnij się, że wybrałeś bieżącą kartę, aby użyć odpowiedniego obrazu i dźwięku. Nagranie jest obecnie ograniczone do 1 GB, czyli około 100 minut.",
"localRecordingWarning": "Upewnij się, że wybrałeś bieżącą kartę, aby użyć odpowiedniego obrazu i dźwięku.",
"loggedIn": "Zalogowano jako {{userName}}",
"noMicPermission": "Nie można utworzyć ścieżki mikrofonu. Zezwól na korzystanie z mikrofonu.",
"noStreams": "Nie wykryto strumienia audio lub wideo.",

View File

@@ -964,7 +964,6 @@
"proceedAnyway": "Continuar na mesma",
"recordingWarning": "Outros participantes podem estar a gravar esta chamada",
"screenSharingError": "Erro de partilha de ecrã:",
"showScreen": "Ativar o ecrã de pré-reunião",
"startWithPhone": "Iniciar com o áudio do telefone",
"unsafeRoomConsent": "Compreendo os riscos, quero participar na reunião",
"videoOnlyError": "Erro de vídeo:",
@@ -1035,7 +1034,7 @@
"localRecordingStartWarningTitle": "Parar a gravação para a salvar",
"localRecordingVideoStop": "A paragem do seu vídeo também irá parar a gravação local. Tem a certeza de que quer continuar?",
"localRecordingVideoWarning": "Para gravar o seu vídeo deve tê-lo ligado quando iniciar a gravação",
"localRecordingWarning": "Certifique-se de selecionar o separador actual a fim de utilizar o vídeo e áudio corretos. A gravação está actualmente limitada a 1 GB, o que é cerca de 100 minutos.",
"localRecordingWarning": "Certifique-se de selecionar o separador actual a fim de utilizar o vídeo e áudio corretos.",
"loggedIn": "Conectado como {{userName}}",
"noMicPermission": "Não foi possível criar a faixa de microfone. Por favor, conceda permissão para utilizar o microfone.",
"noStreams": "Não foi detetado nenhum sinal áudio ou vídeo.",

View File

@@ -935,7 +935,6 @@
"premeeting": "Pré-reunião",
"proceedAnyway": "Prosseguir mesmo assim",
"screenSharingError": "Erro de compartilhamento de tela:",
"showScreen": "Habilitar tela pré-reunião",
"startWithPhone": "Iniciar com o áudio da ligação",
"unsafeRoomConsent": "Eu entendo os riscos, desejo ingressar na reunião",
"videoOnlyError": "Erro de vídeo:",
@@ -1007,7 +1006,7 @@
"localRecordingStartWarningTitle": "Parar a gravação para salvá-la",
"localRecordingVideoStop": "Ao parar o seu vídeo a gravação local também será parada. Tem certeza que deseja continuar?",
"localRecordingVideoWarning": "Para gravar o seu vídeo você precisa ativá-lo antes de inicar a gravação",
"localRecordingWarning": "Tenha certeza de selecionar a aba atual para usar o áudio e vídeo corretos. A gravação está atualmente limitada a 1GB, que corresponde a aproximadamente 100 minutos.",
"localRecordingWarning": "Tenha certeza de selecionar a aba atual para usar o áudio e vídeo corretos.",
"loggedIn": "Conectado como {{userName}}",
"noMicPermission": "Trilha para o microfone não pôde ser criada. Por favor conceda permissão para usar o microfone.",
"noStreams": "Nenhum fluxo de áudio ou vídeo detectado.",

View File

@@ -950,7 +950,6 @@
"proceedAnyway": "Продолжить в любом случае",
"recordingWarning": "Другие участники могут записывать этот звонок",
"screenSharingError": "Ошибка показа экрана:",
"showScreen": "Включить экран перед подключением",
"startWithPhone": "Начать с телефонной связью",
"unsafeRoomConsent": "Я понимаю риски и хочу присоединиться к встрече",
"videoOnlyError": "Ошибка видео:",
@@ -1021,7 +1020,7 @@
"localRecordingStartWarningTitle": "Остановите запись для сохранения",
"localRecordingVideoStop": "Остановка вашего видео также остановит локальную запись. Вы уверены, что хотите продолжить?",
"localRecordingVideoWarning": "Чтобы записать ваше видео, оно должно быть включено при начале записи",
"localRecordingWarning": "Убедитесь, что вы выбрали текущую вкладку для использования правильного видео и аудио. Запись в настоящее время ограничена 1ГБ, что составляет около 100 минут.",
"localRecordingWarning": "Убедитесь, что вы выбрали текущую вкладку для использования правильного видео и аудио.",
"loggedIn": "Вошел как {{userName}}",
"noMicPermission": "Не удалось создать аудиодорожку микрофона. Пожалуйста, предоставьте разрешение на использование микрофона.",
"noStreams": "Аудио или видеопоток не обнаружен.",

View File

@@ -842,7 +842,6 @@
"or": "opuru",
"premeeting": "Pre-riunione",
"screenSharingError": "Faddina in sa cumpartzidura de s'ischermu",
"showScreen": "Ativa ischermu de pre-riunione",
"startWithPhone": "Avia imperende s'àudio de su telèfonu",
"videoOnlyError": "Faddina de vìdeu:",
"videoTrackError": "Impossìbile creare una rasta de vìdeu.",
@@ -916,7 +915,7 @@
"localRecordingStartWarningTitle": "Firma sa registratzione pro dda sarvare",
"localRecordingVideoStop": "Istudende su vìdeu as a firmare puru sa registratzione locale. Ses seguru de chèrrere sighire?",
"localRecordingVideoWarning": "Pro registrare unu vìdeu, depet èssere allutu prima de cumintzare",
"localRecordingWarning": "Assegura·ti chi seletzionas s'ischeda atuale in manera de impreare s'àudio e su vìdeu curretos. Sa registratzione est limitada a 1 GB, est a nàrrere, a s'inghìriu de 100 minutos.",
"localRecordingWarning": "Assegura·ti chi seletzionas s'ischeda atuale in manera de impreare s'àudio e su vìdeu curretos.",
"loggedIn": "Autenticatzione: {{userName}}",
"noStreams": "Nissunu flussu de vìdeu o de àudio rilevadu.",
"off": "Registratzione firmada",

View File

@@ -737,7 +737,6 @@
"or": "ali",
"premeeting": "Pred sestanek",
"screenSharingError": "Napaka deljenja zaslona:",
"showScreen": "Omogoči zaslon pred sestankom",
"startWithPhone": "Začni z zvokom telefona",
"videoOnlyError": "Napaka videa:",
"videoTrackError": "Ni bilo mogoče ustvariti videa.",

View File

@@ -975,7 +975,6 @@
"proceedAnyway": "Vazhdo, sido qoftë",
"recordingWarning": "Këtë thirrje pjesëmarrës të tjerë mund ta regjistrojnë",
"screenSharingError": "Gabim ndarjeje ekrani me të tjerë:",
"showScreen": "Aktivizoni skenë para takimit",
"startWithPhone": "Nise me audio telefoni",
"unsafeRoomConsent": "I kuptoj rreziqet, dëshiroj të marr pjesë te takimi",
"videoOnlyError": "Gabim video:",
@@ -1046,7 +1045,7 @@
"localRecordingStartWarningTitle": "Ndaleni regjistrimin që ta ruani",
"localRecordingVideoStop": "Ndalja e videos tuaj do të ndalë gjithashtu edhe regjistrimin vendor. Jeni i sigurt se doni të vazhdohet?",
"localRecordingVideoWarning": "Që ta regjistroni, videon tuaj duhet ta keni të hapur, kur niset regjistrimi",
"localRecordingWarning": "Sigurohuni se përzgjidhni skedën e tanishme, që të mund të përdoret videoja dhe audioja e saktë. Regjistrimi aktualisht është i kufizuar deri në 1GB, çka është aty afër 100 minutave.",
"localRecordingWarning": "Sigurohuni se përzgjidhni skedën e tanishme, që të mund të përdoret videoja dhe audioja e saktë.",
"loggedIn": "I futur si {{userName}}",
"noMicPermission": "Su krijua dot pistë mikrofoni. Ju lutemi, akordoni leje për përdorim të mikrofonit.",
"noStreams": "Su pikas rrjedhë audio ose video.",

View File

@@ -483,7 +483,6 @@
"or": "или",
"premeeting": "Пред придруживањем",
"screenSharingError": "Грешка дијељења екрана:",
"showScreen": "Укључити екран 'пред придруживњем'.",
"startWithPhone": "Започети са телефонском везом.",
"videoOnlyError": "Грешка видеа:",
"videoTrackError": "Креирање видео траке није успјело.",

View File

@@ -976,7 +976,6 @@
"proceedAnyway": "Fortsätt ändå",
"recordingWarning": "",
"screenSharingError": "Skärmdelningsfel:",
"showScreen": "Aktivera skärmen före mötet",
"startWithPhone": "Börja med telefonljud",
"unsafeRoomConsent": "Jag förstår riskerna, jag vill vara med på mötet",
"videoOnlyError": "Videofel:",
@@ -1047,7 +1046,7 @@
"localRecordingStartWarningTitle": "Stoppa inspelningen för att spara den",
"localRecordingVideoStop": "Om du stoppar din video stoppas även den lokala inspelningen. Är du säker på att du vill fortsätta?",
"localRecordingVideoWarning": "För att spela in din video måste du ha den på när du startar inspelningen",
"localRecordingWarning": "Se till att du väljer den aktuella fliken för att kunna använda rätt video och ljud. Inspelningen är för närvarande begränsad till 1 GB, vilket är cirka 100 minuter.",
"localRecordingWarning": "Se till att du väljer den aktuella fliken för att kunna använda rätt video och ljud.",
"loggedIn": "Inloggad som {{userName}}",
"noMicPermission": "Mikrofonspåret kunde inte skapas. Vänligen ge tillstånd att använda mikrofonen.",
"noStreams": "Ingen ljud- eller videoström upptäcktes.",

View File

@@ -634,7 +634,6 @@
"or": "లేదా",
"premeeting": "Pre meeting",
"screenSharingError": "Screen sharing error:",
"showScreen": "Enable pre meeting screen",
"startWithPhone": "Start with phone audio",
"videoOnlyError": "Video error:",
"videoTrackError": "Could not create video track.",

View File

@@ -970,7 +970,6 @@
"proceedAnyway": "Yine de devam et",
"recordingWarning": "Diğer katılımcılar bu çağrıyı kaydediyor olabilir",
"screenSharingError": "Ekran paylaşma hatası:",
"showScreen": "Toplantı öncesi ekranını etkinleştir",
"startWithPhone": "Telefon sesiyle başlayın",
"unsafeRoomConsent": "Riskleri anlıyorum, toplantıya katılmak istiyorum",
"videoOnlyError": "Video hatası:",
@@ -1041,7 +1040,7 @@
"localRecordingStartWarningTitle": "Kaydetmek için kaydı durdurun",
"localRecordingVideoStop": "Videonuzu durdurmak yerel kaydı da durduracaktır. Devam etmek istediğine emin misin?",
"localRecordingVideoWarning": "Videonuzu kaydetmek için kayda başlarken açmış olmanız gerekir",
"localRecordingWarning": "Doğru video ve sesi kullanmak için geçerli sekmeyi seçtiğinizden emin olun. Kayıt şu anda yaklaşık 100 dakika olan 1GB ile sınırlıdır.",
"localRecordingWarning": "Doğru video ve sesi kullanmak için geçerli sekmeyi seçtiğinizden emin olun.",
"loggedIn": "{{userName}} olarak giriş yapıldı",
"noMicPermission": "Mikrofon parçası oluşturulamadı. Lütfen mikrofonu kullanma izni verin.",
"noStreams": "Ses veya video akışı algılanmadı",

View File

@@ -870,7 +870,6 @@
"or": "або",
"premeeting": "Перед приєднанням",
"screenSharingError": "Помилка спільного перегляду екрана:",
"showScreen": "Увімкнути вхідну панель",
"startWithPhone": "Почати в режимі телефону",
"videoOnlyError": "Помилка відео:",
"videoTrackError": "Не вдалося створити трек відео.",
@@ -941,7 +940,7 @@
"localRecordingStartWarningTitle": "Зупиніть запис, щоб зберегти його",
"localRecordingVideoStop": "Вимикання камери також припинить локальний запис. Продовжити?",
"localRecordingVideoWarning": "Щоб записати ваше відео, камера має бути увімкнена до початку запису",
"localRecordingWarning": "Виберіть поточну вкладку веб-браузера, щоб використовувати правильні налаштування камери та мікрофона. Запис наразі обмежений 1 ГБ, що становить приблизно 100 хв.",
"localRecordingWarning": "Виберіть поточну вкладку веб-браузера, щоб використовувати правильні налаштування камери та мікрофона.",
"loggedIn": "Ви ввійшли як {{userName}}",
"noMicPermission": "Не вдалося створити мікрофонну доріжку. Надайте дозвіл на доступ до мікрофона.",
"noStreams": "Аудіо чи відео потік не виявлено.",

View File

@@ -948,7 +948,6 @@
"proceedAnyway": "Tiếp tục dù sao",
"recordingWarning": "Có thể có người tham gia khác đang ghi lại cuộc gọi này",
"screenSharingError": "Lỗi chia sẻ màn hình:",
"showScreen": "Kích hoạt màn hình trước cuộc họp",
"startWithPhone": "Bắt đầu với âm thanh điện thoại",
"unsafeRoomConsent": "Tôi hiểu rủi ro, tôi muốn tham gia cuộc họp",
"videoOnlyError": "Lỗi video:",
@@ -1019,7 +1018,7 @@
"localRecordingStartWarningTitle": "Dừng ghi âm để lưu lại",
"localRecordingVideoStop": "Dừng video của bạn cũng sẽ dừng ghi âm cục bộ. Bạn có chắc chắn muốn tiếp tục không?",
"localRecordingVideoWarning": "Để ghi lại video của bạn, bạn phải bật nó khi bắt đầu ghi âm",
"localRecordingWarning": "Đảm bảo bạn chọn tab hiện tại để sử dụng video và âm thanh đúng cách. Hiện tại, ghi âm bị giới hạn chỉ là 1GB, tương đương với khoảng 100 phút.",
"localRecordingWarning": "Đảm bảo bạn chọn tab hiện tại để sử dụng video và âm thanh đúng cách.",
"loggedIn": "Đã đăng nhập dưới tên {{userName}}",
"noMicPermission": "Không thể tạo track microphone. Vui lòng cấp quyền sử dụng microphone.",
"noStreams": "Không phát hiện luồng âm thanh hoặc video nào.",

View File

@@ -917,7 +917,6 @@
"premeeting": "会前",
"proceedAnyway": "仍然继续",
"screenSharingError": "共享屏幕错误:",
"showScreen": "开启会前屏幕",
"startWithPhone": "以电话音频开始",
"unsafeRoomConsent": "我了解风险,我想加入会议",
"videoOnlyError": "视频错误:",
@@ -989,7 +988,7 @@
"localRecordingStartWarningTitle": "停止录制以保存内容",
"localRecordingVideoStop": "停止视频也会停止本地录制,你确定要继续吗?",
"localRecordingVideoWarning": "要录制视频,你必须在开始录制时保持视频开启",
"localRecordingWarning": "请确保你选择当前的标签页,以便录制正确的视频和音频。录制目前限制在1GB大约100分钟。",
"localRecordingWarning": "请确保你选择当前的标签页,以便录制正确的视频和音频。",
"loggedIn": "以{{userName}}登录",
"noMicPermission": "无法创建麦克风轨道,请允许使用麦克风。",
"noStreams": "未检测到音频或视频。",

View File

@@ -934,7 +934,6 @@
"premeeting": "會議前",
"proceedAnyway": "仍然繼續",
"screenSharingError": "螢幕分享錯誤:",
"showScreen": "啟用會議前螢幕",
"startWithPhone": "使用手機音訊開始",
"unsafeRoomConsent": "我了解風險,我想要加入會議",
"videoOnlyError": "視訊錯誤:",
@@ -1006,7 +1005,7 @@
"localRecordingStartWarningTitle": "停用錄製以保存",
"localRecordingVideoStop": "關閉您的視訊也將停止本機錄製,確定繼續嗎?",
"localRecordingVideoWarning": "錄製視訊必須在開始時啟用",
"localRecordingWarning": "確保選擇目前的分頁以錄製正確的視訊和音訊。錄製目前限制為1GB約可錄製100分鐘。",
"localRecordingWarning": "確保選擇目前的分頁以錄製正確的視訊和音訊。",
"loggedIn": "以 {{userName}} 登入",
"noMicPermission": "無法建立麥克風軌,請允許使用麥克風的權限。",
"noStreams": "未檢測到音訊或視訊。",

View File

@@ -272,7 +272,8 @@
"Remove": "Remove",
"Share": "Share",
"Submit": "Submit",
"Understand": "I understand",
"Understand": "I understand, keep me muted for now",
"UnderstandAndUnmute": "I understand, please unmute me",
"WaitForHostMsg": "The conference has not yet started because no moderators have yet arrived. If you'd like to become a moderator please log-in. Otherwise, please wait.",
"WaitForHostNoAuthMsg": "The conference has not yet started because no moderators have yet arrived. Please wait.",
"WaitingForHostButton": "Wait for moderator",
@@ -309,6 +310,7 @@
"conferenceReloadMsg": "We're trying to fix this. Reconnecting in {{seconds}} sec…",
"conferenceReloadTitle": "Unfortunately, something went wrong.",
"confirm": "Confirm",
"confirmBack": "Back",
"confirmNo": "No",
"confirmYes": "Yes",
"connectError": "Oops! Something went wrong and we couldn't connect to the conference.",
@@ -346,6 +348,7 @@
"kickParticipantTitle": "Kick this participant?",
"kickSystemTitle": "Ouch! You were kicked out of the meeting",
"kickTitle": "Ouch! {{participantDisplayName}} kicked you out of the meeting",
"learnMore": "learn more",
"linkMeeting": "Link meeting",
"linkMeetingTitle": "Link meeting to Salesforce",
"liveStreaming": "Live Streaming",
@@ -403,7 +406,9 @@
"recentlyUsedObjects": "Your recently used objects",
"recording": "Recording",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Not possible while a live stream is active",
"recordingInProgressDescription": "This meeting is being recorded. Your audio and video have been muted. If you choose to unmute, you consent to being recorded.",
"recordingInProgressDescription": "This meeting is being recorded and analyzed by AI{{learnMore}}. Your audio and video have been muted. If you choose to unmute, you consent to being recorded.",
"recordingInProgressDescriptionFirstHalf": "This meeting is being recorded and analyzed by AI",
"recordingInProgressDescriptionSecondHalf": ". Your audio and video have been muted. If you choose to unmute, you consent to being recorded.",
"recordingInProgressTitle": "Recording in progress",
"rejoinNow": "Rejoin now",
"remoteControlAllowedMessage": "{{user}} accepted your remote control request!",
@@ -993,7 +998,6 @@
"proceedAnyway": "Proceed anyway",
"recordingWarning": "Other participants may be recording this call",
"screenSharingError": "Screen sharing error:",
"showScreen": "Enable pre meeting screen",
"startWithPhone": "Start with phone audio",
"unsafeRoomConsent": "I understand the risks, I want to join the meeting",
"videoOnlyError": "Video error:",
@@ -1064,7 +1068,7 @@
"localRecordingStartWarningTitle": "Stop the recording to save it",
"localRecordingVideoStop": "Stopping your video will also stop the local recording. Are you sure you want to continue?",
"localRecordingVideoWarning": "To record your video you must have it on when starting the recording",
"localRecordingWarning": "Make sure you select the current tab in order to use the right video and audio. The recording is currently limited to 1GB, which is around 100 minutes.",
"localRecordingWarning": "Make sure you select the current tab in order to use the right video and audio.",
"loggedIn": "Logged in as {{userName}}",
"noMicPermission": "Microphone track could not be created. Please grant permission to use the microphone.",
"noStreams": "No audio or video stream detected.",

View File

@@ -11,7 +11,6 @@ import {
getAvailableDevices,
getCurrentDevices,
isDeviceChangeAvailable,
isDeviceListAvailable,
isMultipleAudioInputSupported,
setAudioInputDevice,
setAudioOutputDevice,
@@ -989,10 +988,15 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* Returns Promise that resolves with true if the device list is available
* and with false if not.
*
* @deprecated
*
* @returns {Promise}
*/
isDeviceListAvailable() {
return isDeviceListAvailable(this._transport);
console.warn('isDeviceListAvailable is deprecated and will be removed in the future. '
+ 'It always returns true');
return Promise.resolve(true);
}
/**

View File

@@ -56,21 +56,6 @@ export function isDeviceChangeAvailable(transport, deviceType) {
});
}
/**
* Returns Promise that resolves with true if the device list is available
* and with false if not.
*
* @param {Transport} transport - The @code{Transport} instance responsible for
* the external communication.
* @returns {Promise}
*/
export function isDeviceListAvailable(transport) {
return transport.sendRequest({
type: 'devices',
name: 'isDeviceListAvailable'
});
}
/**
* Returns Promise that resolves with true if multiple audio input is supported
* and with false if not.

10
package-lock.json generated
View File

@@ -61,7 +61,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/v1980.0.0+34a32e86/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1984.0.0+dd4c41be/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -16982,8 +16982,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1980.0.0+34a32e86/lib-jitsi-meet.tgz",
"integrity": "sha512-NmjVrkhBgUhAHe84oEVGi5keXmO92RtVznchdPep6vJz9O2A6GPN9Ap+DZOoiK693bm9lRdzDIEIFn5GnlLfQg==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1984.0.0+dd4c41be/lib-jitsi-meet.tgz",
"integrity": "sha512-2kGN8tLIg1U2K8TWD0tIksvBKScEydp6CikN3j2pnYpQ20Lh3EXc/p0AA6XtyhteHtqwPGAtSKIwTB+VKNnUIQ==",
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.2.1",
@@ -37440,8 +37440,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1980.0.0+34a32e86/lib-jitsi-meet.tgz",
"integrity": "sha512-NmjVrkhBgUhAHe84oEVGi5keXmO92RtVznchdPep6vJz9O2A6GPN9Ap+DZOoiK693bm9lRdzDIEIFn5GnlLfQg==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1984.0.0+dd4c41be/lib-jitsi-meet.tgz",
"integrity": "sha512-2kGN8tLIg1U2K8TWD0tIksvBKScEydp6CikN3j2pnYpQ20Lh3EXc/p0AA6XtyhteHtqwPGAtSKIwTB+VKNnUIQ==",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",

View File

@@ -67,7 +67,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1980.0.0+34a32e86/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1984.0.0+dd4c41be/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -1,6 +1,7 @@
import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { IReduxState, IStore } from '../../app/types';
import { readyToClose } from '../../mobile/external-api/actions';
import { transcriberJoined, transcriberLeft } from '../../transcribing/actions';
import { setIAmVisitor } from '../../visitors/actions';
import { iAmVisitor } from '../../visitors/functions';
@@ -26,7 +27,7 @@ import {
participantSourcesUpdated,
participantUpdated
} from '../participants/actions';
import { getNormalizedDisplayName, getParticipantByIdOrUndefined } from '../participants/functions';
import { getLocalParticipant, getNormalizedDisplayName, getParticipantByIdOrUndefined } from '../participants/functions';
import { IJitsiParticipant } from '../participants/types';
import { toState } from '../redux/functions';
import {
@@ -143,7 +144,24 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
conference.on(
JitsiConferenceEvents.KICKED,
(participant: any) => dispatch(kickedOut(conference, participant)));
(participant: any, reason: any, isReplaced: boolean) => {
if (isReplaced) {
const localParticipant = getLocalParticipant(state);
dispatch(participantUpdated({
conference,
// @ts-ignore
id: localParticipant.id,
isReplaced
}));
dispatch(readyToClose());
} else {
dispatch(kickedOut(conference, participant));
}
});
conference.on(
JitsiConferenceEvents.PARTICIPANT_KICKED,

View File

@@ -542,10 +542,12 @@ export interface IConfig {
};
recordingSharingUrl?: string;
recordings?: {
consentLearnMoreLink?: string;
recordAudioAndVideo?: boolean;
requireConsent?: boolean;
showPrejoinWarning?: boolean;
showRecordingLink?: boolean;
skipConsentInMeeting?: boolean;
suggestRecording?: boolean;
};
remoteVideoMenu?: {

View File

@@ -1,10 +1,13 @@
import { appNavigate } from '../../app/actions.native';
import { IStore } from '../../app/types';
import { getCustomerDetails } from '../../jaas/actions.any';
import { isVpaasMeeting, getJaasJWT } from '../../jaas/functions';
import { navigateRoot } from '../../mobile/navigation/rootNavigationContainerRef';
import { screen } from '../../mobile/navigation/routes';
import { setJWT } from '../jwt/actions';
import { JitsiConnectionErrors } from '../lib-jitsi-meet';
import { _connectInternal } from './actions.any';
import { _connectInternal } from './actions.native';
export * from './actions.any';
@@ -16,12 +19,32 @@ export * from './actions.any';
* @returns {Function}
*/
export function connect(id?: string, password?: string) {
return (dispatch: IStore['dispatch']) => dispatch(_connectInternal(id, password))
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { jwt } = state['features/base/jwt'];
if (isVpaasMeeting(state)) {
return dispatch(getCustomerDetails())
.then(() => {
if (!jwt) {
return getJaasJWT(state);
}
})
.then(j => {
j && dispatch(setJWT(j));
return dispatch(_connectInternal(id, password));
});
}
dispatch(_connectInternal(id, password))
.catch(error => {
if (error === JitsiConnectionErrors.NOT_LIVE_ERROR) {
navigateRoot(screen.visitorsQueue);
}
});
};
}
/**

View File

@@ -150,8 +150,7 @@ export function getAvailableDevices() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => new Promise(resolve => {
const { mediaDevices } = JitsiMeetJS;
if (mediaDevices.isDeviceListAvailable()
&& mediaDevices.isDeviceChangeAvailable()) {
if (mediaDevices.isDeviceChangeAvailable()) {
mediaDevices.enumerateDevices((devices: MediaDeviceInfo[]) => {
const { filteredDevices, ignoredDevices } = filterIgnoredDevices(devices);
const oldDevices = flattenAvailableDevices(getState()['features/base/devices'].availableDevices);

View File

@@ -40,6 +40,7 @@ export default class AbstractDialog<P extends IProps, S extends IState = IState>
super(props);
// Bind event handlers so they are only bound once per instance.
this._onBack = this._onBack.bind(this);
this._onCancel = this._onCancel.bind(this);
this._onSubmit = this._onSubmit.bind(this);
this._onSubmitFulfilled = this._onSubmitFulfilled.bind(this);
@@ -75,6 +76,14 @@ export default class AbstractDialog<P extends IProps, S extends IState = IState>
return this.props.dispatch(hideDialog());
}
_onBack() {
const { backDisabled = false, onBack } = this.props;
if (!backDisabled && (!onBack || onBack())) {
this._hide();
}
}
/**
* Dispatches a redux action to hide this dialog when it's canceled.
*

View File

@@ -16,6 +16,11 @@ import styles from './styles';
*/
interface IProps extends AbstractProps, WithTranslation {
/**
* The i18n key of the text label for the back button.
*/
backLabel?: string;
/**
* The i18n key of the text label for the cancel button.
*/
@@ -36,6 +41,11 @@ interface IProps extends AbstractProps, WithTranslation {
*/
descriptionKey?: string | { key: string; params: string; };
/**
* Whether the back button is hidden.
*/
isBackHidden?: Boolean;
/**
* Whether the cancel button is hidden.
*/
@@ -55,6 +65,11 @@ interface IProps extends AbstractProps, WithTranslation {
* Dialog title.
*/
title?: string;
/**
* Renders buttons vertically.
*/
verticalButtons?: boolean;
}
/**
@@ -102,14 +117,17 @@ class ConfirmDialog extends AbstractDialog<IProps> {
*/
override render() {
const {
backLabel,
cancelLabel,
children,
confirmLabel,
isBackHidden = true,
isCancelHidden,
isConfirmDestructive,
isConfirmHidden,
t,
title
title,
verticalButtons
} = this.props;
const dialogButtonStyle
@@ -119,6 +137,7 @@ class ConfirmDialog extends AbstractDialog<IProps> {
return (
<Dialog.Container
coverScreen = { false }
verticalButtons = { verticalButtons }
visible = { true }>
{
title && <Dialog.Title>
@@ -127,6 +146,12 @@ class ConfirmDialog extends AbstractDialog<IProps> {
}
{ this._renderDescription() }
{ children }
{
!isBackHidden && <Dialog.Button
label = { t(backLabel || 'dialog.confirmBack') }
onPress = { this._onBack }
style = { styles.dialogButton } />
}
{
!isCancelHidden && <Dialog.Button
label = { t(cancelLabel || 'dialog.confirmNo') }

View File

@@ -2,6 +2,16 @@ import { ReactNode } from 'react';
export type DialogProps = {
/**
* Whether back button is disabled. Enabled by default.
*/
backDisabled?: boolean;
/**
* Optional i18n key to change the back button title.
*/
backKey?: string;
/**
* Whether cancel button is disabled. Enabled by default.
*/
@@ -27,6 +37,11 @@ export type DialogProps = {
*/
okKey?: string;
/**
* The handler for onBack event.
*/
onBack?: Function;
/**
* The handler for onCancel event.
*/

View File

@@ -176,6 +176,7 @@ class Popover extends Component<IProps, IState> {
this._setContextMenuStyle = this._setContextMenuStyle.bind(this);
this._getCustomDialogStyle = this._getCustomDialogStyle.bind(this);
this._onOutsideClick = this._onOutsideClick.bind(this);
this._onOutsideTouchStart = this._onOutsideTouchStart.bind(this);
}
/**
@@ -185,7 +186,7 @@ class Popover extends Component<IProps, IState> {
* @returns {void}
*/
override componentDidMount() {
window.addEventListener('touchstart', this._onTouchStart);
window.addEventListener('touchstart', this._onOutsideTouchStart);
if (this.props.trigger === 'click') {
// @ts-ignore
window.addEventListener('click', this._onOutsideClick);
@@ -199,7 +200,7 @@ class Popover extends Component<IProps, IState> {
* @returns {void}
*/
override componentWillUnmount() {
window.removeEventListener('touchstart', this._onTouchStart);
window.removeEventListener('touchstart', this._onOutsideTouchStart);
if (this.props.trigger === 'click') {
// @ts-ignore
window.removeEventListener('click', this._onOutsideClick);
@@ -261,6 +262,7 @@ class Popover extends Component<IProps, IState> {
id = { id }
onClick = { this._onClick }
onKeyPress = { this._onKeyPress }
onTouchStart = { this._onTouchStart }
{ ...(trigger === 'hover' ? {
onMouseEnter: this._onShowDialog,
onMouseLeave: this._onHideDialog
@@ -337,7 +339,7 @@ class Popover extends Component<IProps, IState> {
* @private
* @returns {void}
*/
_onTouchStart(event: TouchEvent) {
_onOutsideTouchStart(event: TouchEvent) {
if (this.props.visible
&& !this.props.overflowDrawer
&& !this._contextMenuRef?.contains?.(event.target as Node)
@@ -401,6 +403,24 @@ class Popover extends Component<IProps, IState> {
}
}
/**
* Stops propagation of touchstart events originating from the Popover's trigger container.
* This prevents the window's 'touchstart' listener (_onOutsideTouchStart) from
* immediately closing the Popover if the touch begins on the trigger area itself.
* Without this, the subsequent synthesized 'click' event will not execute
* because the Popover would already be closing or removed, breaking interactions
* within the Popover on touch devices.
*
* e.g. On a mobile device overflow buttons don't execute their click actions.
*
* @param {React.TouchEvent} event - The touch start event.
* @private
* @returns {void}
*/
_onTouchStart(event: React.TouchEvent) {
event.stopPropagation();
}
/**
* KeyPress handler for accessibility.
*

View File

@@ -59,9 +59,10 @@ export function clientResized(clientWidth: number, clientHeight: number) {
dispatch({
type: CLIENT_RESIZED,
clientHeight,
clientWidth: availableWidth
clientWidth,
videoSpaceWidth: availableWidth
});
dispatch(setAspectRatio(clientWidth, clientHeight));
dispatch(setAspectRatio(availableWidth, clientHeight));
});
};
}

View File

@@ -29,9 +29,9 @@ MiddlewareRegistry.register(store => next => action => {
break;
case CONFERENCE_JOINED: {
const { clientHeight = 0, clientWidth = 0 } = store.getState()['features/base/responsive-ui'];
const { clientHeight = 0, clientWidth = 0, videoSpaceWidth = 0 } = store.getState()['features/base/responsive-ui'];
if (!clientHeight && !clientWidth) {
if (!clientHeight && !clientWidth && !videoSpaceWidth) {
const {
innerHeight,
innerWidth

View File

@@ -25,7 +25,8 @@ const DEFAULT_STATE = {
clientWidth: innerWidth,
isNarrowLayout: false,
reducedUI: false,
contextMenuOpened: false
contextMenuOpened: false,
videoSpaceWidth: innerWidth
};
export interface IResponsiveUIState {
@@ -41,6 +42,7 @@ export interface IResponsiveUIState {
right: number;
top: number;
};
videoSpaceWidth: number;
}
ReducerRegistry.register<IResponsiveUIState>('features/base/responsive-ui',
@@ -50,7 +52,8 @@ ReducerRegistry.register<IResponsiveUIState>('features/base/responsive-ui',
return {
...state,
clientWidth: action.clientWidth,
clientHeight: action.clientHeight
clientHeight: action.clientHeight,
videoSpaceWidth: action.videoSpaceWidth
};
}

View File

@@ -1,7 +1,5 @@
import { IStore } from '../../app/types';
import { PREJOIN_INITIALIZED } from '../../prejoin/actionTypes';
import { setPrejoinPageVisibility } from '../../prejoin/actions';
import { APP_WILL_MOUNT } from '../app/actionTypes';
import { getJwtName } from '../jwt/functions';
import { MEDIA_TYPE } from '../media/constants';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
@@ -26,9 +24,6 @@ MiddlewareRegistry.register(store => next => action => {
const result = next(action);
switch (action.type) {
case APP_WILL_MOUNT:
_initializeShowPrejoin(store);
break;
case PREJOIN_INITIALIZED:
_maybeUpdateDisplayName(store);
break;
@@ -40,21 +35,6 @@ MiddlewareRegistry.register(store => next => action => {
return result;
});
/**
* Overwrites the showPrejoin flag based on cached used selection for showing prejoin screen.
*
* @param {Store} store - The redux store.
* @private
* @returns {void}
*/
function _initializeShowPrejoin({ dispatch, getState }: IStore) {
const { userSelectedSkipPrejoin } = getState()['features/base/settings'];
if (userSelectedSkipPrejoin) {
dispatch(setPrejoinPageVisibility(false));
}
}
/**
* Updates the display name to the one in JWT if there is one.
*

View File

@@ -48,8 +48,7 @@ const DEFAULT_STATE: ISettingsState = {
userSelectedNotifications: {
'notify.chatMessages': true
},
userSelectedMicDeviceLabel: undefined,
userSelectedSkipPrejoin: undefined
userSelectedMicDeviceLabel: undefined
};
export interface ISettingsState {
@@ -88,7 +87,6 @@ export interface ISettingsState {
userSelectedNotifications?: {
[key: string]: boolean;
};
userSelectedSkipPrejoin?: boolean;
videoSettingsVisible?: boolean;
visible?: boolean;
}

View File

@@ -173,16 +173,16 @@ const DialogWithTabs = ({
const [ selectedTab, setSelectedTab ] = useState<string | undefined>(defaultTab ?? tabs[0].name);
const [ userSelected, setUserSelected ] = useState(false);
const [ tabStates, setTabStates ] = useState(tabs.map(tab => tab.props));
const clientWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].clientWidth);
const videoSpaceWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].videoSpaceWidth);
const [ isMobile, setIsMobile ] = useState(false);
useEffect(() => {
if (clientWidth <= MOBILE_BREAKPOINT) {
if (videoSpaceWidth <= MOBILE_BREAKPOINT) {
!isMobile && setIsMobile(true);
} else {
isMobile && setIsMobile(false);
}
}, [ clientWidth, isMobile ]);
}, [ videoSpaceWidth, isMobile ]);
useEffect(() => {
if (isMobile) {

View File

@@ -163,7 +163,8 @@ export function processExternalDeviceRequest( // eslint-disable-line max-params
switch (request.name) {
case 'isDeviceListAvailable':
responseCallback(JitsiMeetJS.mediaDevices.isDeviceListAvailable());
// TODO(saghul): remove this, eventually.
responseCallback(true);
break;
case 'isDeviceChangeAvailable':
responseCallback(

View File

@@ -180,6 +180,7 @@ export interface IDynamicBrandingState {
requireRecordingConsent?: boolean;
sharedVideoAllowedURLDomains?: Array<string>;
showGiphyIntegration?: boolean;
skipRecordingConsentInMeeting?: boolean;
supportUrl?: string;
useDynamicBrandingData: boolean;
virtualBackgrounds: Array<Image>;
@@ -206,9 +207,10 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
muiBrandedTheme,
pollCreationRequiresPermission,
premeetingBackground,
requireRecordingConsent,
sharedVideoAllowedURLDomains,
showGiphyIntegration,
requireRecordingConsent,
skipRecordingConsentInMeeting,
supportUrl,
virtualBackgrounds
} = action.value;
@@ -228,9 +230,10 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
muiBrandedTheme,
pollCreationRequiresPermission,
premeetingBackground,
requireRecordingConsent,
sharedVideoAllowedURLDomains,
showGiphyIntegration,
requireRecordingConsent,
skipRecordingConsentInMeeting,
supportUrl,
customizationFailed: false,
customizationReady: true,

View File

@@ -83,7 +83,7 @@ export function resizeFilmStrip(width: number) {
export function setTileViewDimensions() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { clientHeight, videoSpaceWidth } = state['features/base/responsive-ui'];
const {
disableResponsiveTiles,
disableTileEnlargement,
@@ -101,7 +101,7 @@ export function setTileViewDimensions() {
} = disableResponsiveTiles
? calculateNonResponsiveTileViewDimensions(state)
: calculateResponsiveTileViewDimensions({
clientWidth,
clientWidth: videoSpaceWidth,
clientHeight,
disableTileEnlargement,
maxColumns,
@@ -112,7 +112,7 @@ export function setTileViewDimensions() {
const availableHeight = clientHeight - TILE_VIEW_GRID_VERTICAL_MARGIN;
const hasScroll = availableHeight < thumbnailsTotalHeight;
const filmstripWidth
= Math.min(clientWidth - TILE_VIEW_GRID_HORIZONTAL_MARGIN,
= Math.min(videoSpaceWidth - TILE_VIEW_GRID_HORIZONTAL_MARGIN,
(columns ?? 1) * (TILE_HORIZONTAL_MARGIN + (width ?? 0)))
+ (hasScroll ? SCROLL_SIZE : 0);
const filmstripHeight = Math.min(availableHeight, thumbnailsTotalHeight);
@@ -144,7 +144,7 @@ export function setTileViewDimensions() {
export function setVerticalViewDimensions() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { clientHeight = 0, clientWidth = 0 } = state['features/base/responsive-ui'];
const { clientHeight = 0, videoSpaceWidth = 0 } = state['features/base/responsive-ui'];
const { width: filmstripWidth } = state['features/filmstrip'];
const disableSelfView = getHideSelfView(state);
const resizableFilmstrip = isFilmstripResizable(state);
@@ -207,7 +207,7 @@ export function setVerticalViewDimensions() {
width: widthOfFilmstrip
};
} else {
thumbnails = calculateThumbnailSizeForVerticalView(clientWidth, filmstripWidth.current ?? 0,
thumbnails = calculateThumbnailSizeForVerticalView(videoSpaceWidth, filmstripWidth.current ?? 0,
resizableFilmstrip);
remoteVideosContainerWidth
@@ -254,11 +254,11 @@ export function setVerticalViewDimensions() {
export function setHorizontalViewDimensions() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { clientHeight = 0, clientWidth = 0 } = state['features/base/responsive-ui'];
const { clientHeight = 0, videoSpaceWidth = 0 } = state['features/base/responsive-ui'];
const disableSelfView = getHideSelfView(state);
const thumbnails = calculateThumbnailSizeForHorizontalView(clientHeight);
const remoteVideosContainerWidth
= clientWidth - (disableSelfView ? 0 : thumbnails?.local?.width) - HORIZONTAL_FILMSTRIP_MARGIN;
= videoSpaceWidth - (disableSelfView ? 0 : thumbnails?.local?.width) - HORIZONTAL_FILMSTRIP_MARGIN;
const remoteVideosContainerHeight
= thumbnails?.local?.height + TILE_VERTICAL_MARGIN + STAGE_VIEW_THUMBNAIL_VERTICAL_BORDER + SCROLL_SIZE;
const numberOfRemoteParticipants = getRemoteParticipantCountWithFake(state);
@@ -288,7 +288,7 @@ export function setHorizontalViewDimensions() {
export function setStageFilmstripViewDimensions() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { clientHeight, videoSpaceWidth } = state['features/base/responsive-ui'];
const {
tileView = {}
} = state['features/base/config'];
@@ -296,7 +296,7 @@ export function setStageFilmstripViewDimensions() {
const verticalWidth = visible ? getVerticalViewMaxWidth(state) : 0;
const { numberOfVisibleTiles = MAX_ACTIVE_PARTICIPANTS } = tileView;
const numberOfParticipants = state['features/filmstrip'].activeParticipants.length;
const availableWidth = clientWidth - verticalWidth;
const availableWidth = videoSpaceWidth - verticalWidth;
const maxColumns = getMaxColumnCount(state, {
width: availableWidth,
disableResponsiveTiles: false,
@@ -322,7 +322,7 @@ export function setStageFilmstripViewDimensions() {
const thumbnailsTotalHeight = (rows ?? 1) * (TILE_VERTICAL_MARGIN + (height ?? 0));
const hasScroll = clientHeight < thumbnailsTotalHeight;
const filmstripWidth
= Math.min(clientWidth - TILE_VIEW_GRID_HORIZONTAL_MARGIN,
= Math.min(videoSpaceWidth - TILE_VIEW_GRID_HORIZONTAL_MARGIN,
(columns ?? 1) * (TILE_HORIZONTAL_MARGIN + (width ?? 0)))
+ (hasScroll ? SCROLL_SIZE : 0);
const filmstripHeight = Math.min(clientHeight - TILE_VIEW_GRID_VERTICAL_MARGIN, thumbnailsTotalHeight);
@@ -543,10 +543,10 @@ export function clearStageParticipants() {
export function setScreensharingTileDimensions() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { clientHeight, videoSpaceWidth } = state['features/base/responsive-ui'];
const { visible, topPanelHeight, topPanelVisible } = state['features/filmstrip'];
const verticalWidth = visible ? getVerticalViewMaxWidth(state) : 0;
const availableWidth = clientWidth - verticalWidth;
const availableWidth = videoSpaceWidth - verticalWidth;
const topPanel = isStageFilmstripTopPanel(state) && topPanelVisible;
const availableHeight = clientHeight - (topPanel ? topPanelHeight?.current || TOP_FILMSTRIP_HEIGHT : 0);

View File

@@ -891,12 +891,12 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
const { isOpen: shiftRight } = state['features/chat'];
const disableSelfView = getHideSelfView(state);
const { clientWidth, clientHeight } = state['features/base/responsive-ui'];
const { videoSpaceWidth, clientHeight } = state['features/base/responsive-ui'];
const filmstripDisabled = isFilmstripDisabled(state);
const collapseTileView = reduceHeight
&& isMobileBrowser()
&& clientWidth <= ASPECT_RATIO_BREAKPOINT;
&& videoSpaceWidth <= ASPECT_RATIO_BREAKPOINT;
const shouldReduceHeight = reduceHeight && isMobileBrowser();
const _topPanelVisible = isStageFilmstripTopPanel(state) && topPanelVisible;
@@ -929,7 +929,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
_isVerticalFilmstrip,
_localScreenShareId: localScreenShare?.id,
_mainFilmstripVisible: notDisabled,
_maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
_maxFilmstripWidth: videoSpaceWidth - MIN_STAGE_VIEW_WIDTH,
_maxTopPanelHeight: clientHeight - MIN_STAGE_VIEW_HEIGHT,
_remoteParticipantsLength: _remoteParticipants?.length ?? 0,
_topPanelHeight: topPanelHeight.current,

View File

@@ -123,7 +123,7 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
let gridDimensions = dimensions;
let _hasScroll = false;
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { clientHeight, videoSpaceWidth } = state['features/base/responsive-ui'];
const availableSpace = clientHeight - Number(filmstripHeight);
let filmstripPadding = 0;
@@ -139,7 +139,7 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
const collapseTileView = reduceHeight
&& isMobileBrowser()
&& clientWidth <= ASPECT_RATIO_BREAKPOINT;
&& videoSpaceWidth <= ASPECT_RATIO_BREAKPOINT;
const shouldReduceHeight = reduceHeight && (
isMobileBrowser() || (_currentLayout !== LAYOUTS.VERTICAL_FILMSTRIP_VIEW

View File

@@ -119,7 +119,7 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
} = state['features/filmstrip'].stageFilmstripDimensions;
const gridDimensions = dimensions;
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { clientHeight, videoSpaceWidth } = state['features/base/responsive-ui'];
const availableSpace = clientHeight - Number(filmstripHeight);
let filmstripPadding = 0;
@@ -135,7 +135,7 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
const collapseTileView = reduceHeight
&& isMobileBrowser()
&& clientWidth <= ASPECT_RATIO_BREAKPOINT;
&& videoSpaceWidth <= ASPECT_RATIO_BREAKPOINT;
const remoteFilmstripHeight = Number(filmstripHeight) - (
collapseTileView && filmstripPadding > 0 ? filmstripPadding : 0);

View File

@@ -158,7 +158,7 @@ export function calculateThumbnailSizeForHorizontalView(clientHeight = 0) {
/**
* Calculates the size for thumbnails when in vertical view layout.
*
* @param {number} clientWidth - The height of the app window.
* @param {number} clientWidth - The available video space width.
* @param {number} filmstripWidth - The width of the filmstrip.
* @param {boolean} isResizable - Whether the filmstrip is resizable or not.
* @returns {{local: {height, width}, remote: {height, width}}}
@@ -186,7 +186,7 @@ export function calculateThumbnailSizeForVerticalView(clientWidth = 0, filmstrip
/**
* Returns the minimum height of a thumbnail.
*
* @param {number} clientWidth - The width of the window.
* @param {number} clientWidth - The available width for rendering thumbnails.
* @returns {number} The minimum height of a thumbnail.
*/
export function getThumbnailMinHeight(clientWidth: number) {
@@ -198,7 +198,7 @@ export function getThumbnailMinHeight(clientWidth: number) {
*
* @param {boolean} disableResponsiveTiles - Indicates whether the responsive tiles functionality is disabled.
* @param {boolean} disableTileEnlargement - Indicates whether the tiles enlargement functionality is disabled.
* @param {number} clientWidth - The width of the window.
* @param {number} clientWidth - The available video space width.
* @returns {number} The default aspect ratio for a tile.
*/
export function getTileDefaultAspectRatio(disableResponsiveTiles: boolean,
@@ -236,13 +236,13 @@ export function getNumberOfPartipantsForTileView(state: IReduxState) {
* @returns {Object} - The dimensions.
*/
export function calculateNonResponsiveTileViewDimensions(state: IReduxState) {
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { clientHeight, videoSpaceWidth } = state['features/base/responsive-ui'];
const { disableTileEnlargement } = state['features/base/config'];
const { columns: c, minVisibleRows, rows: r } = getNotResponsiveTileViewGridDimensions(state);
const size = calculateThumbnailSizeForTileView({
columns: c,
minVisibleRows,
clientWidth,
clientWidth: videoSpaceWidth,
clientHeight,
disableTileEnlargement,
disableResponsiveTiles: true
@@ -250,10 +250,10 @@ export function calculateNonResponsiveTileViewDimensions(state: IReduxState) {
if (typeof size === 'undefined') { // The columns don't fit into the screen. We will have horizontal scroll.
const aspectRatio = disableTileEnlargement
? getTileDefaultAspectRatio(true, disableTileEnlargement, clientWidth)
? getTileDefaultAspectRatio(true, disableTileEnlargement, videoSpaceWidth)
: TILE_PORTRAIT_ASPECT_RATIO;
const height = getThumbnailMinHeight(clientWidth);
const height = getThumbnailMinHeight(videoSpaceWidth);
return {
height,

View File

@@ -85,13 +85,13 @@ MiddlewareRegistry.register(store => next => action => {
if (isFilmstripResizable(state)) {
const { width: filmstripWidth, topPanelHeight } = state['features/filmstrip'];
const { clientWidth, clientHeight } = action;
const { clientHeight, videoSpaceWidth } = action;
let height, width;
if ((filmstripWidth.current ?? 0) > clientWidth - MIN_STAGE_VIEW_WIDTH) {
width = Math.max(clientWidth - MIN_STAGE_VIEW_WIDTH, DEFAULT_FILMSTRIP_WIDTH);
if ((filmstripWidth.current ?? 0) > videoSpaceWidth - MIN_STAGE_VIEW_WIDTH) {
width = Math.max(videoSpaceWidth - MIN_STAGE_VIEW_WIDTH, DEFAULT_FILMSTRIP_WIDTH);
} else {
width = Math.min(clientWidth - MIN_STAGE_VIEW_WIDTH, filmstripWidth.userSet ?? 0);
width = Math.min(videoSpaceWidth - MIN_STAGE_VIEW_WIDTH, filmstripWidth.userSet ?? 0);
}
if (width !== filmstripWidth.current) {
store.dispatch(setFilmstripWidth(width));

View File

@@ -60,12 +60,12 @@ StateListenerRegistry.register(
*/
StateListenerRegistry.register(
/* selector */ state => {
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { clientHeight, videoSpaceWidth } = state['features/base/responsive-ui'];
return {
layout: getCurrentLayout(state),
height: clientHeight,
width: clientWidth
width: videoSpaceWidth
};
},
/* listener */ ({ layout }, store) => {
@@ -131,7 +131,7 @@ StateListenerRegistry.register(
* Listens for changes in the client width to determine whether the overflow menu(s) should be displayed as drawers.
*/
StateListenerRegistry.register(
/* selector */ state => state['features/base/responsive-ui'].clientWidth < DISPLAY_DRAWER_THRESHOLD,
/* selector */ state => state['features/base/responsive-ui'].videoSpaceWidth < DISPLAY_DRAWER_THRESHOLD,
/* listener */ (widthBelowThreshold, store) => {
store.dispatch(setOverflowDrawer(widthBelowThreshold));
store.dispatch(setNarrowLayout(widthBelowThreshold));
@@ -141,7 +141,7 @@ StateListenerRegistry.register(
* Gracefully hide/show the filmstrip when going past threshold.
*/
StateListenerRegistry.register(
/* selector */ state => state['features/base/responsive-ui'].clientWidth < ASPECT_RATIO_BREAKPOINT,
/* selector */ state => state['features/base/responsive-ui'].videoSpaceWidth < ASPECT_RATIO_BREAKPOINT,
/* listener */ (widthBelowThreshold, store) => {
const state = store.getState();
const { disableFilmstripAutohiding } = state['features/base/config'];
@@ -179,7 +179,7 @@ StateListenerRegistry.register(
length: state['features/filmstrip'].activeParticipants.length,
width: state['features/filmstrip'].width?.current,
visible: state['features/filmstrip'].visible,
clientWidth: state['features/base/responsive-ui'].clientWidth,
clientWidth: state['features/base/responsive-ui'].videoSpaceWidth,
clientHeight: state['features/base/responsive-ui'].clientHeight,
tileView: state['features/video-layout'].tileViewEnabled,
height: state['features/filmstrip'].topPanelHeight?.current
@@ -212,7 +212,7 @@ StateListenerRegistry.register(
/* selector */ state => {
return {
length: state['features/video-layout'].remoteScreenShares.length,
clientWidth: state['features/base/responsive-ui'].clientWidth,
clientWidth: state['features/base/responsive-ui'].videoSpaceWidth,
clientHeight: state['features/base/responsive-ui'].clientHeight,
height: state['features/filmstrip'].topPanelHeight?.current,
width: state['features/filmstrip'].width?.current,

View File

@@ -101,7 +101,7 @@ function GifsMenu({ columns = 2, parent }: IProps) {
const { t } = useTranslation();
const isInOverflowMenu
= parent === IReactionsMenuParent.OverflowDrawer || parent === IReactionsMenuParent.OverflowMenu;
const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
const { videoSpaceWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
const rating = useSelector(getGifRating);
const fetchGifs = useCallback(async (offset = 0) => {
@@ -225,7 +225,7 @@ function GifsMenu({ columns = 2, parent }: IProps) {
onGifClick = { handleGifClick }
onGifKeyPress = { handleGifKeyPress }
width = { parent === IReactionsMenuParent.OverflowDrawer
? clientWidth - (2 * OVERFLOW_DRAWER_PADDING) - SCROLL_SIZE
? videoSpaceWidth - (2 * OVERFLOW_DRAWER_PADDING) - SCROLL_SIZE
: parent === IReactionsMenuParent.OverflowMenu ? 201 : 320
} />
</div>

View File

@@ -182,10 +182,9 @@ export function isPrejoinPageVisible(state: IReduxState): boolean {
*/
export function shouldAutoKnock(state: IReduxState): boolean {
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
const { userSelectedSkipPrejoin } = state['features/base/settings'];
const { autoKnock } = getLobbyConfig(state);
return Boolean(((isPrejoinEnabledInConfig(state) && !userSelectedSkipPrejoin)
return Boolean(((isPrejoinEnabledInConfig(state))
|| autoKnock || (iAmRecorder && iAmSipGateway))
&& !state['features/lobby'].knocking);
}

View File

@@ -8,6 +8,16 @@
*/
export const CLEAR_RECORDING_SESSIONS = 'CLEAR_RECORDING_SESSIONS';
/**
* The type of Redux action which marks a session ID as consent requested.
*
* {
* type: MARK_CONSENT_REQUESTED,
* sessionId: string
* }
*/
export const MARK_CONSENT_REQUESTED = 'MARK_CONSENT_REQUESTED';
/**
* The type of Redux action which updates the current known state of a recording
* session.

View File

@@ -20,6 +20,7 @@ import { isRecorderTranscriptionsRunning } from '../transcribing/functions';
import {
CLEAR_RECORDING_SESSIONS,
MARK_CONSENT_REQUESTED,
RECORDING_SESSION_UPDATED,
SET_MEETING_HIGHLIGHT_BUTTON_STATE,
SET_PENDING_RECORDING_NOTIFICATION_UID,
@@ -476,3 +477,17 @@ export function showStartRecordingNotificationWithCallback(openRecordingDialog:
}, NOTIFICATION_TIMEOUT_TYPE.EXTRA_LONG));
};
}
/**
* Marks the given session as consent requested. No further consent requests will be
* made for this session.
*
* @param {string} sessionId - The session id.
* @returns {Object}
*/
export function markConsentRequested(sessionId: string) {
return {
type: MARK_CONSENT_REQUESTED,
sessionId
};
}

View File

@@ -1,4 +1,3 @@
import i18next from 'i18next';
import { v4 as uuidV4 } from 'uuid';
import { IStore } from '../../../app/types';
@@ -126,16 +125,7 @@ const LocalRecordingManager: ILocalRecordingManager = {
* @returns {void}
* */
stopLocalRecording() {
if (this.recorder) {
this.recorder.stop();
this.recorder = undefined;
this.audioContext = undefined;
this.audioDestination = undefined;
this.writableStream?.close().then(() => {
this.fileHandle = undefined;
this.writableStream = undefined;
});
}
this.recorder?.stop();
},
/**
@@ -160,8 +150,7 @@ const LocalRecordingManager: ILocalRecordingManager = {
this.fileHandle = await window.showSaveFilePicker(options);
this.writableStream = await this.fileHandle?.createWritable();
// @ts-ignore
const supportsCaptureHandle = Boolean(navigator.mediaDevices.setCaptureHandleConfig) && !isEmbedded();
const supportsCaptureHandle = !isEmbedded();
const tabId = uuidV4();
this.selfRecording.on = onlySelf;
@@ -169,25 +158,19 @@ const LocalRecordingManager: ILocalRecordingManager = {
const tracks = getTrackState(getState());
if (onlySelf) {
let audioTrack: MediaStreamTrack | undefined = getLocalTrack(tracks, MEDIA_TYPE.AUDIO)?.jitsiTrack?.track;
const audioTrack: MediaStreamTrack | undefined = getLocalTrack(tracks, MEDIA_TYPE.AUDIO)?.jitsiTrack?.track;
let videoTrack: MediaStreamTrack | undefined = getLocalTrack(tracks, MEDIA_TYPE.VIDEO)?.jitsiTrack?.track;
if (!audioTrack) {
APP.conference.muteAudio(false);
setTimeout(() => APP.conference.muteAudio(true), 100);
await new Promise(resolve => {
setTimeout(resolve, 100);
});
}
if (videoTrack && videoTrack.readyState !== 'live') {
videoTrack = undefined;
}
audioTrack = getLocalTrack(getTrackState(getState()), MEDIA_TYPE.AUDIO)?.jitsiTrack?.track;
if (!audioTrack && !videoTrack) {
throw new Error('NoLocalStreams');
}
this.selfRecording.withVideo = Boolean(videoTrack);
const localTracks = [];
const localTracks: MediaStreamTrack[] = [];
audioTrack && localTracks.push(audioTrack);
videoTrack && localTracks.push(videoTrack);
@@ -200,58 +183,56 @@ const LocalRecordingManager: ILocalRecordingManager = {
permittedOrigins: [ '*' ]
});
}
const localAudioTrack = getLocalTrack(tracks, MEDIA_TYPE.AUDIO)?.jitsiTrack?.track;
// Starting chrome 107, the recorder does not record any data if the audio stream has no tracks
// To fix this we create a track for the local user(muted track)
if (!localAudioTrack) {
APP.conference.muteAudio(false);
setTimeout(() => APP.conference.muteAudio(true), 100);
await new Promise(resolve => {
setTimeout(resolve, 100);
});
}
// handle no mic permission
if (!getLocalTrack(getTrackState(getState()), MEDIA_TYPE.AUDIO)?.jitsiTrack?.track) {
throw new Error('NoMicTrack');
}
const currentTitle = document.title;
document.title = i18next.t('localRecording.selectTabTitle');
// @ts-ignore
gdmStream = await navigator.mediaDevices.getDisplayMedia({
video: { displaySurface: 'browser',
frameRate: 30 },
audio: false, // @ts-ignore
preferCurrentTab: true
video: {
displaySurface: 'browser',
frameRate: 30
},
audio: {
autoGainControl: false,
channelCount: 2,
echoCancellation: false,
noiseSuppression: false,
// @ts-ignore
restrictOwnAudio: false,
// @ts-ignore
suppressLocalAudioPlayback: false,
},
// @ts-ignore
preferCurrentTab: true,
surfaceSwitching: 'exclude'
});
document.title = currentTitle;
const isBrowser = gdmStream.getVideoTracks()[0].getSettings().displaySurface === 'browser';
const gdmVideoTrack = gdmStream.getVideoTracks()[0];
const isBrowser = gdmVideoTrack.getSettings().displaySurface === 'browser';
const matchesHandle = (supportsCaptureHandle // @ts-ignore
&& gdmVideoTrack.getCaptureHandle()?.handle === `JitsiMeet-${tabId}`);
if (!isBrowser || (supportsCaptureHandle // @ts-ignore
&& gdmStream.getVideoTracks()[0].getCaptureHandle()?.handle !== `JitsiMeet-${tabId}`)) {
if (!isBrowser || !matchesHandle) {
gdmStream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
throw new Error('WrongSurfaceSelected');
}
this.initializeAudioMixer();
const allTracks = getTrackState(getState());
const gdmAudioTrack = gdmStream.getAudioTracks()[0];
allTracks.forEach((track: any) => {
if (track.mediaType === MEDIA_TYPE.AUDIO) {
const audioTrack = track?.jitsiTrack?.track;
if (!gdmAudioTrack) {
throw new Error('NoAudioTrackFound');
}
this.addAudioTrackToLocalRecording(gdmAudioTrack);
const localAudioTrack = getLocalTrack(tracks, MEDIA_TYPE.AUDIO)?.jitsiTrack?.track;
if (localAudioTrack) {
this.addAudioTrackToLocalRecording(localAudioTrack);
}
this.addAudioTrackToLocalRecording(audioTrack);
}
});
this.stream = new MediaStream([
...this.audioDestination?.stream.getAudioTracks() || [],
gdmStream.getVideoTracks()[0]
gdmVideoTrack
]);
}
@@ -259,18 +240,29 @@ const LocalRecordingManager: ILocalRecordingManager = {
mimeType: this.mediaType,
videoBitsPerSecond: VIDEO_BIT_RATE
});
this.recorder.addEventListener('dataavailable', async e => {
if (this.recorder && e.data && e.data.size > 0) {
await this.writableStream?.write(e.data);
}
});
if (!onlySelf) {
this.recorder.addEventListener('stop', () => {
this.stream?.getTracks().forEach((track: MediaStreamTrack) => track.stop());
gdmStream?.getTracks().forEach((track: MediaStreamTrack) => track.stop());
});
this.recorder.addEventListener('stop', () => {
this.stream?.getTracks().forEach((track: MediaStreamTrack) => track.stop());
gdmStream?.getTracks().forEach((track: MediaStreamTrack) => track.stop());
// The stop event is emitted when the recorder is done, and _after_ the last buffered
// data has been handed over to the dataavailable event.
this.recorder = undefined;
this.audioContext = undefined;
this.audioDestination = undefined;
this.writableStream?.close().then(() => {
this.fileHandle = undefined;
this.writableStream = undefined;
});
});
if (!onlySelf) {
gdmStream?.addEventListener('inactive', () => {
dispatch(stopLocalVideoRecording());
});
@@ -280,7 +272,7 @@ const LocalRecordingManager: ILocalRecordingManager = {
});
}
this.recorder.start(5000);
this.recorder.start(1000);
},
/**
@@ -294,6 +286,8 @@ const LocalRecordingManager: ILocalRecordingManager = {
&& !browser.isReactNative()
&& !isMobileBrowser()
// @ts-expect-error
&& Boolean(navigator.mediaDevices.setCaptureHandleConfig)
// @ts-expect-error
&& typeof window.showSaveFilePicker !== 'undefined'
&& MediaRecorder.isTypeSupported(PREFERRED_MEDIA_TYPE);

View File

@@ -1,8 +1,13 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import Dialog from 'react-native-dialog';
import ConfirmDialog from '../../../../base/dialog/components/native/ConfirmDialog';
import { setAudioUnmutePermissions, setVideoUnmutePermissions } from '../../../../base/media/actions';
import Link from '../../../../base/react/components/native/Link';
import { setAudioMuted, setAudioUnmutePermissions, setVideoMuted, setVideoUnmutePermissions } from '../../../../base/media/actions';
import { IReduxState } from '../../../../app/types';
import styles from '../styles.native';
/**
* Component that renders the dialog for explicit consent for recordings.
@@ -11,6 +16,10 @@ import { setAudioUnmutePermissions, setVideoUnmutePermissions } from '../../../.
*/
export default function RecordingConsentDialog() {
const dispatch = useDispatch();
const { t } = useTranslation();
const { recordings } = useSelector((state: IReduxState) => state['features/base/config']);
const { consentLearnMoreLink } = recordings ?? {};
const consent = useCallback(() => {
dispatch(setAudioUnmutePermissions(false, true));
@@ -19,12 +28,36 @@ export default function RecordingConsentDialog() {
return true;
}, []);
const consentAndUnmute = useCallback(() => {
dispatch(setAudioUnmutePermissions(false, true));
dispatch(setVideoUnmutePermissions(false, true));
dispatch(setAudioMuted(false));
dispatch(setVideoMuted(false));
return true;
}, []);
return (
<ConfirmDialog
backLabel = { 'dialog.UnderstandAndUnmute' }
confirmLabel = { 'dialog.Understand' }
descriptionKey = { 'dialog.recordingInProgressDescription' }
isBackHidden = { false }
isCancelHidden = { true }
onBack = { consentAndUnmute }
onSubmit = { consent }
title = { 'dialog.recordingInProgressTitle' } />
title = { 'dialog.recordingInProgressTitle' }
verticalButtons = { true }>
<Dialog.Description>
{t('dialog.recordingInProgressDescriptionFirstHalf')}
{consentLearnMoreLink && (
<Link
style = { styles.learnMoreLink }
url = { consentLearnMoreLink }>
{`(${t('dialog.learnMore')})`}
</Link>
)}
{t('dialog.recordingInProgressDescriptionSecondHalf')}
</Dialog.Description>
</ConfirmDialog>
);
}

View File

@@ -94,8 +94,11 @@ export default {
highlightDialogButtonsSpace: {
height: 16,
width: '100%'
},
learnMoreLink: {
color: BaseTheme.palette.link01,
fontWeight: 'bold'
}
};
/**

View File

@@ -1,9 +1,17 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { batch, useDispatch, useSelector } from 'react-redux';
import { setAudioUnmutePermissions, setVideoUnmutePermissions } from '../../../../base/media/actions';
import { IReduxState } from '../../../../app/types';
import { translateToHTML } from '../../../../base/i18n/functions';
import {
setAudioMuted,
setAudioUnmutePermissions,
setVideoMuted,
setVideoUnmutePermissions
} from '../../../../base/media/actions';
import Dialog from '../../../../base/ui/components/web/Dialog';
import { hideDialog } from '../../../../base/dialog/actions';
/**
* Component that renders the dialog for explicit consent for recordings.
@@ -13,14 +21,34 @@ import Dialog from '../../../../base/ui/components/web/Dialog';
export default function RecordingConsentDialog() {
const { t } = useTranslation();
const dispatch = useDispatch();
const { recordings } = useSelector((state: IReduxState) => state['features/base/config']);
const { consentLearnMoreLink } = recordings ?? {};
const learnMore = ` (<a href="${consentLearnMoreLink}" target="_blank" rel="noopener noreferrer">${t('dialog.learnMore')}</a>)`;
const consent = useCallback(() => {
dispatch(setAudioUnmutePermissions(false, true));
dispatch(setVideoUnmutePermissions(false, true));
batch(() => {
dispatch(setAudioUnmutePermissions(false, true));
dispatch(setVideoUnmutePermissions(false, true));
});
}, []);
const consentAndUnmute = useCallback(() => {
batch(() => {
dispatch(setAudioUnmutePermissions(false, true));
dispatch(setVideoUnmutePermissions(false, true));
dispatch(setAudioMuted(false));
dispatch(setVideoMuted(false));
dispatch(hideDialog());
});
}, []);
return (
<Dialog
back = {{
hidden: false,
onClick: consentAndUnmute,
translationKey: 'dialog.UnderstandAndUnmute'
}}
cancel = {{ hidden: true }}
disableBackdropClose = { true }
disableEscape = { true }
@@ -28,9 +56,7 @@ export default function RecordingConsentDialog() {
ok = {{ translationKey: 'dialog.Understand' }}
onSubmit = { consent }
titleKey = 'dialog.recordingInProgressTitle'>
<div>
{t('dialog.recordingInProgressDescription')}
</div>
{ translateToHTML(t, 'dialog.recordingInProgressDescription', { learnMore }) }
</Dialog>
);
}

View File

@@ -439,9 +439,12 @@ export function isLiveStreamingButtonVisible({
* @returns {boolean}
*/
export function shouldRequireRecordingConsent(recorderSession: any, state: IReduxState) {
const { requireRecordingConsent } = state['features/dynamic-branding'] || {};
const { requireConsent } = state['features/base/config'].recordings || {};
const { requireRecordingConsent, skipRecordingConsentInMeeting }
= state['features/dynamic-branding'] || {};
const { conference } = state['features/base/conference'] || {};
const { requireConsent, skipConsentInMeeting } = state['features/base/config'].recordings || {};
const { iAmRecorder } = state['features/base/config'];
const { consentRequested } = state['features/recording'];
if (iAmRecorder) {
return false;
@@ -455,10 +458,22 @@ export function shouldRequireRecordingConsent(recorderSession: any, state: IRedu
return false;
}
if (!recorderSession.getInitiator()
|| recorderSession.getStatus() === JitsiRecordingConstants.status.OFF) {
if (consentRequested.has(recorderSession.getID())) {
return false;
}
return recorderSession.getInitiator() !== getLocalParticipant(state)?.id;
// If we join a meeting that has an ongoing recording `conference` will be undefined since
// we get the recording state through the initial presence which happens in between the
// WILL_JOIN and JOINED events.
if (conference && (skipConsentInMeeting || skipRecordingConsentInMeeting)) {
return false;
}
const initiator = recorderSession.getInitiator();
if (!initiator || recorderSession.getStatus() === JitsiRecordingConstants.status.OFF) {
return false;
}
return initiator !== getLocalParticipant(state)?.id;
}

View File

@@ -36,6 +36,7 @@ import { isRecorderTranscriptionsRunning } from '../transcribing/functions';
import { RECORDING_SESSION_UPDATED, START_LOCAL_RECORDING, STOP_LOCAL_RECORDING } from './actionTypes';
import {
clearRecordingSessions,
markConsentRequested,
hidePendingRecordingNotification,
showPendingRecordingNotification,
showRecordingError,
@@ -307,7 +308,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
case TRACK_ADDED: {
const { track } = action;
if (LocalRecordingManager.isRecordingLocally() && track.mediaType === MEDIA_TYPE.AUDIO) {
if (LocalRecordingManager.isRecordingLocally()
&& track.mediaType === MEDIA_TYPE.AUDIO && track.local) {
const audioTrack = track.jitsiTrack.track;
LocalRecordingManager.addAudioTrackToLocalRecording(audioTrack);
@@ -420,6 +422,7 @@ function _showExplicitConsentDialog(recorderSession: any, dispatch: IStore['disp
}
batch(() => {
dispatch(markConsentRequested(recorderSession.getID()));
dispatch(setAudioUnmutePermissions(true, true));
dispatch(setVideoUnmutePermissions(true, true));
dispatch(setAudioMuted(true));

View File

@@ -2,6 +2,7 @@ import ReducerRegistry from '../base/redux/ReducerRegistry';
import {
CLEAR_RECORDING_SESSIONS,
MARK_CONSENT_REQUESTED,
RECORDING_SESSION_UPDATED,
SET_MEETING_HIGHLIGHT_BUTTON_STATE,
SET_PENDING_RECORDING_NOTIFICATION_UID,
@@ -11,6 +12,7 @@ import {
} from './actionTypes';
const DEFAULT_STATE = {
consentRequested: new Set(),
disableHighlightMeetingMoment: false,
pendingNotificationUids: {},
selectedRecordingService: '',
@@ -29,6 +31,7 @@ export interface ISessionData {
}
export interface IRecordingState {
consentRequested: Set<any>;
disableHighlightMeetingMoment: boolean;
pendingNotificationUids: {
[key: string]: string | undefined;
@@ -57,6 +60,15 @@ ReducerRegistry.register<IRecordingState>(STORE_NAME,
sessionDatas: []
};
case MARK_CONSENT_REQUESTED:
return {
...state,
consentRequested: new Set([
...state.consentRequested,
action.sessionId
])
};
case RECORDING_SESSION_UPDATED:
return {
...state,

View File

@@ -132,14 +132,6 @@ export function submitMoreTab(newState: any) {
const state = getState();
const currentState = getMoreTabProps(state);
const showPrejoinPage = newState.showPrejoinPage;
if (showPrejoinPage !== currentState.showPrejoinPage) {
dispatch(updateSettings({
userSelectedSkipPrejoin: !showPrejoinPage
}));
}
if (newState.maxStageParticipants !== currentState.maxStageParticipants) {
dispatch(updateSettings({ maxStageParticipants: Number(newState.maxStageParticipants) }));
}

View File

@@ -11,7 +11,6 @@ import { updateSettings } from '../../../base/settings/actions';
import Switch from '../../../base/ui/components/native/Switch';
import { navigate } from '../../../mobile/navigation/components/settings/SettingsNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { isPrejoinEnabledInConfig } from '../../../prejoin/functions.native';
import FormRow from './FormRow';
import FormSection from './FormSection';
@@ -23,24 +22,14 @@ const GeneralSection = () => {
const dispatch = useDispatch();
const {
disableSelfView,
userSelectedSkipPrejoin
} = useSelector((state: IReduxState) => state['features/base/settings']);
const showPrejoinPage = !userSelectedSkipPrejoin;
const showPrejoinSettings = useSelector(isPrejoinEnabledInConfig);
const { language = DEFAULT_LANGUAGE } = i18next;
const onSelfViewToggled = useCallback((enabled?: boolean) =>
dispatch(updateSettings({ disableSelfView: enabled }))
, [ dispatch, updateSettings ]);
const onShowPejoinToggled = useCallback((enabled?: boolean) => {
dispatch(updateSettings({ userSelectedSkipPrejoin: !enabled }));
}
, [ dispatch, updateSettings ]);
const navigateToLanguageSelect = useCallback(() => {
navigate(screen.settings.language);
}, [ navigate, screen ]);
@@ -52,11 +41,6 @@ const GeneralSection = () => {
checked = { Boolean(disableSelfView) }
onChange = { onSelfViewToggled } />
</FormRow>
{showPrejoinSettings && <FormRow label = 'prejoin.showScreen'>
<Switch
checked = { showPrejoinPage }
onChange = { onShowPejoinToggled } />
</FormRow>}
<FormRow label = 'settings.language'>
<View style = { styles.languageButtonContainer as ViewStyle }>
<TouchableHighlight onPress = { navigateToLanguageSelect }>

View File

@@ -73,16 +73,6 @@ export interface IProps extends AbstractDialogTabProps, WithTranslation {
*/
showModeratorSettings: boolean;
/**
* Whether or not to show prejoin screen.
*/
showPrejoinPage: boolean;
/**
* Whether or not to display the prejoin settings section.
*/
showPrejoinSettings: boolean;
/**
* Whether or not to show subtitles on stage.
*/
@@ -132,7 +122,6 @@ class MoreTab extends AbstractDialogTab<IProps, any> {
super(props);
// Bind event handler so it is only bound once for every instance.
this._onShowPrejoinPageChanged = this._onShowPrejoinPageChanged.bind(this);
this._renderMaxStageParticipantsSelect = this._renderMaxStageParticipantsSelect.bind(this);
this._onMaxStageParticipantsSelect = this._onMaxStageParticipantsSelect.bind(this);
this._onHideSelfViewChanged = this._onHideSelfViewChanged.bind(this);
@@ -149,7 +138,6 @@ class MoreTab extends AbstractDialogTab<IProps, any> {
override render() {
const {
areClosedCaptionsEnabled,
showPrejoinSettings,
disableHideSelfView,
iAmVisitor,
hideSelfView,
@@ -163,10 +151,6 @@ class MoreTab extends AbstractDialogTab<IProps, any> {
<div
className = { clsx('more-tab', classes.container) }
key = 'more'>
{showPrejoinSettings && <>
{this._renderPrejoinScreenSettings()}
<hr className = { classes.divider } />
</>}
{this._renderMaxStageParticipantsSelect()}
{!disableHideSelfView && !iAmVisitor && (
<Checkbox
@@ -187,18 +171,6 @@ class MoreTab extends AbstractDialogTab<IProps, any> {
);
}
/**
* Callback invoked to select if the lobby
* should be shown.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onShowPrejoinPageChanged({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) {
super._onChange({ showPrejoinPage: checked });
}
/**
* Callback invoked to select a max number of stage participants from the select dropdown.
*
@@ -247,24 +219,6 @@ class MoreTab extends AbstractDialogTab<IProps, any> {
super._onChange({ currentLanguage: language });
}
/**
* Returns the React Element for modifying prejoin screen settings.
*
* @private
* @returns {ReactElement}
*/
_renderPrejoinScreenSettings() {
const { t, showPrejoinPage } = this.props;
return (
<Checkbox
checked = { showPrejoinPage }
label = { t('prejoin.showScreen') }
name = 'show-prejoin-page'
onChange = { this._onShowPrejoinPageChanged } />
);
}
/**
* Returns the React Element for the max stage participants dropdown.
*

View File

@@ -315,7 +315,6 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
...newProps,
currentLanguage: tabState?.currentLanguage,
hideSelfView: tabState?.hideSelfView,
showPrejoinPage: tabState?.showPrejoinPage,
showSubtitlesOnStage: tabState?.showSubtitlesOnStage,
maxStageParticipants: tabState?.maxStageParticipants
};

View File

@@ -142,10 +142,10 @@ function AudioSettingsPopup({
* @returns {Object}
*/
function mapStateToProps(state: IReduxState) {
const { clientWidth } = state['features/base/responsive-ui'];
const { videoSpaceWidth } = state['features/base/responsive-ui'];
return {
popupPlacement: clientWidth <= Number(SMALL_MOBILE_WIDTH) ? 'auto' : 'top-end',
popupPlacement: videoSpaceWidth <= Number(SMALL_MOBILE_WIDTH) ? 'auto' : 'top-end',
currentMicDeviceId: getCurrentMicDeviceId(state),
currentOutputDeviceId: getCurrentOutputDeviceId(state),
isOpen: Boolean(getAudioSettingsVisibility(state)),

View File

@@ -138,34 +138,36 @@ const SpeakerEntry = (props: IProps) => {
/* eslint-disable react/jsx-no-bind */
return (
<li
aria-checked = { isSelected }
aria-posinset = { index + 1 } // Add one to offset the 0 based index.
aria-setsize = { length }
<span
className = { classes.container }
onClick = { _onClick }
onKeyPress = { _onKeyPress }
role = 'radio'
tabIndex = { 0 }>
<ContextMenuItem
accessibilityLabel = { children }
icon = { isSelected ? IconCheck : undefined }
overflowType = { TEXT_OVERFLOW_TYPES.SCROLL_ON_HOVER }
selected = { isSelected }
text = { children }
textClassName = { cx(classes.entryText, 'entryText', !isSelected && 'left-margin') }>
<Button
className = { cx(classes.testButton, 'testButton') }
label = 'Test'
onClick = { _onTestButtonClick }
onKeyPress = { _onTestButtonClick }
type = { BUTTON_TYPES.SECONDARY } />
</ContextMenuItem>
<audio
preload = 'auto'
ref = { audioRef }
src = { TEST_SOUND_PATH } />
</li>
role = 'presentation'>
<li
aria-checked = { isSelected }
aria-posinset = { index + 1 } // Add one to offset the 0 based index.
aria-setsize = { length }
onClick = { _onClick }
onKeyPress = { _onKeyPress }
role = 'radio'
tabIndex = { 0 }>
<ContextMenuItem
accessibilityLabel = { children }
icon = { isSelected ? IconCheck : undefined }
overflowType = { TEXT_OVERFLOW_TYPES.SCROLL_ON_HOVER }
selected = { isSelected }
text = { children }
textClassName = { cx(classes.entryText, 'entryText', !isSelected && 'left-margin') } />
<audio
preload = 'auto'
ref = { audioRef }
src = { TEST_SOUND_PATH } />
</li>
<Button
className = { cx(classes.testButton, 'testButton') }
label = 'Test'
onClick = { _onTestButtonClick }
onKeyPress = { _onTestButtonClick }
type = { BUTTON_TYPES.SECONDARY } />
</span>
);
};

View File

@@ -109,12 +109,12 @@ function VideoSettingsPopup({
* @returns {Object}
*/
function mapStateToProps(state: IReduxState) {
const { clientWidth } = state['features/base/responsive-ui'];
const { videoSpaceWidth } = state['features/base/responsive-ui'];
return {
currentCameraDeviceId: getCurrentCameraDeviceId(state),
isOpen: Boolean(getVideoSettingsVisibility(state)),
popupPlacement: clientWidth <= Number(SMALL_MOBILE_WIDTH) ? 'auto' : 'top-end',
popupPlacement: videoSpaceWidth <= Number(SMALL_MOBILE_WIDTH) ? 'auto' : 'top-end',
videoDeviceIds: getVideoDeviceIds(state) ?? []
};
}

View File

@@ -10,7 +10,6 @@ import { getHideSelfView } from '../base/settings/functions.any';
import { parseStandardURIString } from '../base/util/uri';
import { isStageFilmstripEnabled } from '../filmstrip/functions';
import { isFollowMeActive, isFollowMeRecorderActive } from '../follow-me/functions';
import { isPrejoinEnabledInConfig } from '../prejoin/functions';
import { isReactionsEnabled } from '../reactions/functions.any';
import { areClosedCaptionsEnabled } from '../subtitles/functions.any';
import { iAmVisitor } from '../visitors/functions';
@@ -116,8 +115,6 @@ export function getMoreTabProps(stateful: IStateful) {
languages: LANGUAGES,
maxStageParticipants: state['features/base/settings'].maxStageParticipants,
showLanguageSettings: configuredTabs.includes('language'),
showPrejoinPage: !state['features/base/settings'].userSelectedSkipPrejoin,
showPrejoinSettings: isPrejoinEnabledInConfig(state),
showSubtitlesOnStage: state['features/base/settings'].showSubtitlesOnStage,
stageFilmstripEnabled
};

View File

@@ -163,14 +163,14 @@ class SharedVideo extends Component<IProps> {
*/
function _mapStateToProps(state: IReduxState) {
const { videoUrl } = state['features/shared-video'];
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { clientHeight, videoSpaceWidth } = state['features/base/responsive-ui'];
const { visible, isResizing } = state['features/filmstrip'];
const onStage = getLargeVideoParticipant(state)?.fakeParticipant === FakeParticipant.SharedVideo;
const isVideoShared = isVideoPlaying(state);
return {
clientHeight,
clientWidth,
clientWidth: videoSpaceWidth,
filmstripVisible: visible,
filmstripWidth: getVerticalViewMaxWidth(state),
isEnabled: isSharedVideoEnabled(state),

View File

@@ -199,9 +199,9 @@ const EMOTIONS_LEGEND = [
const SpeakerStats = () => {
const { faceLandmarks } = useSelector((state: IReduxState) => state['features/base/config']);
const { showFaceExpressions } = useSelector((state: IReduxState) => state['features/speaker-stats']);
const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
const displaySwitch = faceLandmarks?.enableDisplayFaceExpressions && clientWidth > DISPLAY_SWITCH_BREAKPOINT;
const displayLabels = clientWidth > MOBILE_BREAKPOINT;
const { videoSpaceWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
const displaySwitch = faceLandmarks?.enableDisplayFaceExpressions && videoSpaceWidth > DISPLAY_SWITCH_BREAKPOINT;
const displayLabels = videoSpaceWidth > MOBILE_BREAKPOINT;
const dispatch = useDispatch();
const { classes } = useStyles();
const { t } = useTranslation();
@@ -217,7 +217,7 @@ const SpeakerStats = () => {
useEffect(() => {
showFaceExpressions && !displaySwitch && dispatch(toggleFaceExpressions());
}, [ clientWidth ]);
}, [ videoSpaceWidth ]);
useEffect(() => () => {
dispatch(resetSearchCriteria());

View File

@@ -52,7 +52,7 @@ interface IProps {
* @returns {ReactElement}
*/
function DialogPortal({ children, className, style, getRef, setSize, targetSelector, onVisible }: IProps) {
const clientWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].clientWidth);
const videoSpaceWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].videoSpaceWidth);
const [ portalTarget ] = useState(() => {
const portalDiv = document.createElement('div');
@@ -115,7 +115,7 @@ function DialogPortal({ children, className, style, getRef, setSize, targetSelec
document.body.removeChild(portalTarget);
}
};
}, [ clientWidth ]);
}, [ videoSpaceWidth ]);
return ReactDOM.createPortal(
children,

View File

@@ -74,7 +74,7 @@ export default function Toolbox({
const conference = useSelector((state: IReduxState) => state['features/base/conference'].conference);
const isNarrowLayout = useSelector((state: IReduxState) => state['features/base/responsive-ui'].isNarrowLayout);
const clientWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].clientWidth);
const videoSpaceWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].videoSpaceWidth);
const isModerator = useSelector(isLocalParticipantModerator);
const customToolbarButtons = useSelector(
(state: IReduxState) => state['features/base/config'].customToolbarButtons);
@@ -229,7 +229,7 @@ export default function Toolbox({
allButtons,
buttonsWithNotifyClick,
toolbarButtons: toolbarButtonsToUse,
clientWidth,
clientWidth: videoSpaceWidth,
jwtDisabledButtons,
mainToolbarButtonsThresholds
});

View File

@@ -82,12 +82,12 @@ const throttledCheckOverlap = throttle(checkToolboxOverlap, 100, {
*/
StateListenerRegistry.register(
/* selector */ state => {
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { clientHeight, videoSpaceWidth } = state['features/base/responsive-ui'];
return {
participantCount: getParticipantCount(state),
clientHeight,
clientWidth,
clientWidth: videoSpaceWidth,
isTileView: isLayoutTileView(state)
};
},

View File

@@ -38,8 +38,8 @@ export function getMaxColumnCount(state: IReduxState, options: {
disableResponsiveTiles = configDisableResponsiveTiles,
disableTileEnlargement = configDisableTileEnlargement
} = options;
const { clientWidth } = state['features/base/responsive-ui'];
const widthToUse = width || clientWidth;
const { videoSpaceWidth } = state['features/base/responsive-ui'];
const widthToUse = width || videoSpaceWidth;
const configuredMax = interfaceConfig.TILE_VIEW_MAX_COLUMNS;
if (disableResponsiveTiles) {

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