mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-02-28 00:30:22 +00:00
Compare commits
3 Commits
virtual-ba
...
fix/consol
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
303f81f427 | ||
|
|
23a65fbf2a | ||
|
|
5fee521938 |
@@ -96,53 +96,23 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
|
||||
public static void addTopBottomInsets(@NonNull Window w, @NonNull View v) {
|
||||
|
||||
// Enable edge-to-edge mode
|
||||
androidx.core.view.WindowCompat.setDecorFitsSystemWindows(w, false);
|
||||
|
||||
// Make system bars transparent so content is visible underneath
|
||||
w.setStatusBarColor(android.graphics.Color.TRANSPARENT);
|
||||
w.setNavigationBarColor(android.graphics.Color.TRANSPARENT);
|
||||
|
||||
View decorView = w.getDecorView();
|
||||
|
||||
// Get display metrics for calculating density-independent caps
|
||||
final android.util.DisplayMetrics metrics = v.getContext().getResources().getDisplayMetrics();
|
||||
final int screenHeight = metrics.heightPixels;
|
||||
final float density = metrics.density;
|
||||
|
||||
// Listen for window inset changes
|
||||
// when system bars visibility is toggled or when the device rotates
|
||||
ViewCompat.setOnApplyWindowInsetsListener(decorView, (view, windowInsets) -> {
|
||||
|
||||
// Get the actual inset values reported by the system
|
||||
int statusBarInset = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).top;
|
||||
int navBarInset = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
|
||||
|
||||
// Calculate maximum allowed inset values to prevent device-specific bugs
|
||||
final int maxTopInset = Math.min((int)(60 * density), (int)(screenHeight * 0.10));
|
||||
final int maxBottomInset = Math.min((int)(120 * density), (int)(screenHeight * 0.10));
|
||||
|
||||
int topInset = Math.min(statusBarInset, maxTopInset);
|
||||
int bottomInset = Math.min(navBarInset, maxBottomInset);
|
||||
|
||||
// Apply calculated insets
|
||||
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
|
||||
|
||||
// Update margins only if they've changed
|
||||
if (params.topMargin != topInset || params.bottomMargin != bottomInset) {
|
||||
params.topMargin = topInset;
|
||||
params.bottomMargin = bottomInset;
|
||||
decorView.post(() -> {
|
||||
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(decorView);
|
||||
if (insets != null) {
|
||||
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
|
||||
params.topMargin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top;
|
||||
params.bottomMargin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;
|
||||
v.setLayoutParams(params);
|
||||
|
||||
decorView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
|
||||
view.setBackgroundColor(JitsiMeetView.BACKGROUND_COLOR);
|
||||
|
||||
return windowInsets;
|
||||
});
|
||||
}
|
||||
|
||||
view.setBackgroundColor(JitsiMeetView.BACKGROUND_COLOR);
|
||||
|
||||
// Return CONSUMED to prevent double-application of margins
|
||||
return WindowInsetsCompat.CONSUMED;
|
||||
});
|
||||
|
||||
// Manually trigger the inset listener to apply margins immediately
|
||||
ViewCompat.requestApplyInsets(decorView);
|
||||
}
|
||||
|
||||
// Overrides
|
||||
@@ -166,11 +136,10 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
|
||||
setContentView(R.layout.activity_jitsi_meet);
|
||||
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM
|
||||
&& getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||
addTopBottomInsets(getWindow(), findViewById(android.R.id.content));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM
|
||||
&& getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||
addTopBottomInsets(getWindow(), findViewById(android.R.id.content));
|
||||
}
|
||||
|
||||
this.jitsiView = findViewById(R.id.jitsiView);
|
||||
|
||||
|
||||
14
config.js
14
config.js
@@ -511,15 +511,6 @@ var config = {
|
||||
// // ./src/react/features/transcribing/transcriber-langs.json.
|
||||
// preferredLanguage: 'en-US',
|
||||
|
||||
// Allows extending the list of supported transcription languages.
|
||||
// Useful for custom transcription backends (e.g. Vosk).
|
||||
//
|
||||
// Example:
|
||||
// customLanguages: {
|
||||
// 'hsb-DE': 'Upper Sorbian (Germany)',
|
||||
// 'dsb-DE': 'Lower Sorbian (Germany)'
|
||||
// },
|
||||
|
||||
// // Enables automatic turning on transcribing when recording is started
|
||||
// autoTranscribeOnRecord: false,
|
||||
|
||||
@@ -1601,13 +1592,14 @@ var config = {
|
||||
// - electron=true (when web is loaded in electron app)
|
||||
// If there is a logout service you can specify its URL with:
|
||||
// tokenLogoutUrl: 'https://myservice.com/logout'
|
||||
// You can enable tokenAuthUrlAutoRedirect which will detect that you have logged in successfully before
|
||||
// and will automatically redirect to the token service to get the token for the meeting.
|
||||
// tokenAuthUrlAutoRedirect: false
|
||||
// An option to respect the context.tenant jwt field compared to the current tenant from the url
|
||||
// tokenRespectTenant: false,
|
||||
// An option to get for user info (name, picture, email) in the token outside the user context.
|
||||
// Can be used with Firebase tokens.
|
||||
// tokenGetUserInfoOutOfContext: false,
|
||||
// An option to pass the token in the iframe API directly instead of using the redirect flow.
|
||||
// tokenAuthInline: false,
|
||||
|
||||
// You can put an array of values to target different entity types in the invite dialog.
|
||||
// Valid values are "phone", "room", "sip", "user", "videosipgw" and "email"
|
||||
|
||||
@@ -45,7 +45,7 @@ body {
|
||||
|
||||
.jitsi-icon {
|
||||
&-default svg {
|
||||
fill: var(--icon-svg-fill, white);
|
||||
fill: var(--icon-default-color, white);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
.reactions-menu {
|
||||
width: 330px;
|
||||
background: var(--reactions-menu-background, #242528);
|
||||
box-shadow: 0px 3px 16px var(--reactions-menu-box-shadow-1, rgba(0, 0, 0, 0.6)), 0px 0px 4px 1px var(--reactions-menu-box-shadow-2, rgba(0, 0, 0, 0.25));
|
||||
background: #242528;
|
||||
box-shadow: 0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
top: 3px;
|
||||
|
||||
& .toolbox-icon.toggled {
|
||||
background-color: var(--reactions-menu-button-toggled, #000000);
|
||||
background-color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
}
|
||||
|
||||
#preview {
|
||||
background: var(--prejoin-preview-background, #040404);
|
||||
background: #040404;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
2
debian/control
vendored
2
debian/control
vendored
@@ -34,7 +34,7 @@ Description: Configuration for web serving of Jitsi Meet
|
||||
|
||||
Package: jitsi-meet-prosody
|
||||
Architecture: all
|
||||
Depends: openssl, prosody (>= 0.12.0) | prosody-trunk | prosody-0.12 | prosody-13.0, ca-certificates-java, lua-sec, lua-basexx, lua-luaossl, lua-cjson, lua-inspect
|
||||
Depends: openssl, prosody (>= 0.12.0) | prosody-trunk | prosody-0.12 | prosody-13.0, lua-sec, lua-basexx, lua-luaossl, lua-cjson, lua-inspect
|
||||
Replaces: jitsi-meet-tokens
|
||||
Description: Prosody configuration for Jitsi Meet
|
||||
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||
|
||||
12
debian/jitsi-meet-prosody.postinst
vendored
12
debian/jitsi-meet-prosody.postinst
vendored
@@ -284,17 +284,13 @@ case "$1" in
|
||||
# and drop the wait and the prosody restart
|
||||
sleep 1
|
||||
invoke-rc.d prosody restart || true
|
||||
fi
|
||||
;;
|
||||
triggered)
|
||||
for trigger in $2; do
|
||||
if [ "$trigger" = "update-ca-certificates-java" ]; then
|
||||
echo "Java certificates updated, restarting Jitsi components..."
|
||||
|
||||
# In case we had updated the certificates and restarted prosody, let's restart and the bridge and jicofo if possible
|
||||
if [ -d /run/systemd/system ] && [ "$CERT_ADDED_TO_TRUST" = "true" ]; then
|
||||
systemctl restart jitsi-videobridge2.service >/dev/null || true
|
||||
systemctl restart jicofo.service >/dev/null || true
|
||||
fi
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
|
||||
1
debian/jitsi-meet-prosody.triggers
vendored
1
debian/jitsi-meet-prosody.triggers
vendored
@@ -1 +0,0 @@
|
||||
interest-noawait update-ca-certificates-java
|
||||
@@ -227,9 +227,6 @@
|
||||
"video_ssrc": "Video-SSRC:",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"customPanel": {
|
||||
"close": "Schließen"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Früher",
|
||||
"today": "Heute",
|
||||
@@ -592,7 +589,6 @@
|
||||
"newFileNotification": "{{ participantName }} hat Datei '{{ fileName }}' hochgeladen",
|
||||
"removeFile": "Entfernen",
|
||||
"removeFileSuccess": "Datei erfolgreich entfernt",
|
||||
"uploadDisabled": "Keine Berechtigung, Dateien hochzuladen. Bitte bei der Moderation anfragen.",
|
||||
"uploadFailedDescription": "Bitte versuchen Sie es erneut.",
|
||||
"uploadFailedTitle": "Dateiupload fehlgeschlagen",
|
||||
"uploadFile": "Datei hochladen"
|
||||
@@ -1319,7 +1315,6 @@
|
||||
"chat": "Chatfenster öffnen / schließen",
|
||||
"clap": "Klatschen",
|
||||
"closeChat": "Chat schließen",
|
||||
"closeCustomPanel": "Schließen",
|
||||
"closeMoreActions": "„Weitere Einstellungen“ schließen",
|
||||
"closeParticipantsPane": "Anwesendenliste schließen",
|
||||
"closedCaptions": "Untertitel",
|
||||
@@ -1425,11 +1420,9 @@
|
||||
"chat": "Chat öffnen / schließen",
|
||||
"clap": "Klatschen",
|
||||
"closeChat": "Chat schließen",
|
||||
"closeCustomPanel": "Schließen",
|
||||
"closeParticipantsPane": "Anwesenheitsliste schließen",
|
||||
"closeReactionsMenu": "Interaktionsmenü schließen",
|
||||
"closedCaptions": "Untertitel",
|
||||
"copilot": "Copilot",
|
||||
"disableNoiseSuppression": "Rauschunterdrückung deaktivieren",
|
||||
"disableReactionSounds": "Sie können die Interaktionstöne für diese Konferenz deaktivieren",
|
||||
"documentClose": "Geteiltes Dokument schließen",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1209
lang/main-fi.json
1209
lang/main-fi.json
File diff suppressed because it is too large
Load Diff
@@ -114,9 +114,6 @@
|
||||
"error": "Erreur : votre message n'a pas été envoyé. Raison : {{error}}",
|
||||
"everyone": "Tout le monde",
|
||||
"fieldPlaceHolder": "Tapez votre message ici",
|
||||
"fileAccessibleTitle": "{{user}} a téléversé un fichier",
|
||||
"fileAccessibleTitleMe": "j’ai téléversé un fichier",
|
||||
"fileDeleted": "Un fichier a été supprimé",
|
||||
"guestsChatIndicator": "(invité)",
|
||||
"lobbyChatMessageTo": "Message de salle d'attente à {{recipient}}",
|
||||
"message": "Message",
|
||||
@@ -126,16 +123,8 @@
|
||||
"messagebox": "Envoyer un message",
|
||||
"newMessages": "Nouveaux messages",
|
||||
"nickname": {
|
||||
"featureChat": "chat",
|
||||
"featureClosedCaptions": "sous-titres",
|
||||
"featureFileSharing": "partage de fichiers",
|
||||
"featurePolls": "sondages",
|
||||
"popover": "Choisissez un pseudonyme",
|
||||
"title": "Entrez un pseudonyme pour utiliser le chat",
|
||||
"titleWith1Features": "Entrez un pseudonyme pour utiliser {{feature1}}",
|
||||
"titleWith2Features": "Entrez un pseudonyme pour utiliser {{feature1}} et {{feature2}}",
|
||||
"titleWith3Features": "Entrez un pseudonyme pour utiliser {{feature1}}, {{feature2}} et {{feature3}}",
|
||||
"titleWith4Features": "Entrez un pseudonyme pour utiliser {{feature1}}, {{feature2}}, {{feature3}} et {{feature4}}",
|
||||
"titleWithCC": "Entrez un pseudonyme pour utiliser le chat et les sous-titres",
|
||||
"titleWithPolls": "Entrez un pseudonyme pour utiliser le chat et les sondages",
|
||||
"titleWithPollsAndCC": "Entrez un pseudonyme pour utiliser le chat, les sondages et les sous-titres",
|
||||
@@ -227,9 +216,6 @@
|
||||
"video_ssrc": "Video SSRC :",
|
||||
"yes": "oui"
|
||||
},
|
||||
"customPanel": {
|
||||
"close": "Fermer"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Plus tôt",
|
||||
"today": "Aujourd'hui",
|
||||
@@ -536,7 +522,6 @@
|
||||
"tokenAuthFailedWithReasons": "Désolé, vous n’êtes pas autorisé à rejoindre l’appel. La raison possible : {{reason}}.",
|
||||
"tokenAuthUnsupported": "Token URL n'est pas supporté.",
|
||||
"transcribing": "Transcription",
|
||||
"unauthenticatedAccessDisabled": "Cet appel nécessite une authentification. Veuillez vous connecter pour continuer.",
|
||||
"unlockRoom": "Supprimer le $t(lockRoomPassword) de la réunion",
|
||||
"user": "Utilisateur",
|
||||
"userIdentifier": "Identifiant utilisateur",
|
||||
@@ -584,12 +569,10 @@
|
||||
"downloadStarted": "Téléchargement de fichier démarré",
|
||||
"dragAndDrop": "Glissez et déposez des fichiers ici ou n'importe où sur l'écran",
|
||||
"fileAlreadyUploaded": "Le fichier a déjà été téléchargé vers cette réunion.",
|
||||
"fileRemovedByOther": "Votre fichier « {{ fileName }} » a été supprimé",
|
||||
"fileTooLargeDescription": "Veuillez vous assurer que le fichier ne dépasse pas {{ maxFileSize }}.",
|
||||
"fileTooLargeTitle": "Le fichier sélectionné est trop volumineux",
|
||||
"fileUploadProgress": "Progression du téléchargement de fichier",
|
||||
"fileUploadedSuccessfully": "Fichier téléchargé avec succès",
|
||||
"newFileNotification": "{{ participantName }} a partagé « {{ fileName }} »",
|
||||
"removeFile": "Supprimer",
|
||||
"removeFileSuccess": "Fichier supprimé avec succès",
|
||||
"uploadFailedDescription": "Veuillez réessayer.",
|
||||
@@ -981,9 +964,6 @@
|
||||
"by": "Par {{ name }}",
|
||||
"closeButton": "Fermer le sondage",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "Envoyer le sondage"
|
||||
},
|
||||
"addOption": "Ajouter une option",
|
||||
"answerPlaceholder": "Option {{index}}",
|
||||
"cancel": "Annuler",
|
||||
@@ -992,7 +972,8 @@
|
||||
"pollQuestion": "Question du sondage",
|
||||
"questionPlaceholder": "Poser une question",
|
||||
"removeOption": "Supprimer l'option",
|
||||
"save": "Enregistrer"
|
||||
"save": "Enregistrer",
|
||||
"send": "Envoyer"
|
||||
},
|
||||
"errors": {
|
||||
"notUniqueOption": "Les options doivent être uniques"
|
||||
@@ -1318,7 +1299,6 @@
|
||||
"chat": "Afficher / Masquer la discussion instantanée",
|
||||
"clap": "Applaudir",
|
||||
"closeChat": "Fermer la discussion instantanée",
|
||||
"closeCustomPanel": "Fermer",
|
||||
"closeMoreActions": "Fermer le menu plus d'actions",
|
||||
"closeParticipantsPane": "Fermer le panneau des participants",
|
||||
"closedCaptions": "Sous-titres",
|
||||
@@ -1424,11 +1404,9 @@
|
||||
"chat": "Ouvrir / Fermer le chat",
|
||||
"clap": "Applaudir",
|
||||
"closeChat": "Fermer le chat",
|
||||
"closeCustomPanel": "Fermer",
|
||||
"closeParticipantsPane": "Fermer le panneau des participants",
|
||||
"closeReactionsMenu": "Fermer le menu réactions",
|
||||
"closedCaptions": "Sous-titres",
|
||||
"copilot": "Copilot",
|
||||
"disableNoiseSuppression": "Arrêter la suppression du bruit",
|
||||
"disableReactionSounds": "Vous pouvez interdire les réactions sonores à cette réunion",
|
||||
"documentClose": "Fermer le document partagé",
|
||||
@@ -1443,7 +1421,6 @@
|
||||
"exitFullScreen": "Quitter le mode plein écran",
|
||||
"exitTileView": "Quitter le mode mosaïque",
|
||||
"feedback": "Laisser des commentaires",
|
||||
"fileSharing": "Partage de fichiers",
|
||||
"giphy": "Activer/désactiver le menu GIPHY",
|
||||
"hangup": "Quitter",
|
||||
"help": "Aide",
|
||||
@@ -1479,7 +1456,6 @@
|
||||
"openReactionsMenu": "Ouvrir le menu Réactions",
|
||||
"participants": "Participants",
|
||||
"pip": "Entrer en mode Picture-in-Picture",
|
||||
"polls": "Sondages",
|
||||
"privateMessage": "Envoyer un message privé",
|
||||
"profile": "Éditer votre profil",
|
||||
"raiseHand": "Lever / Baisser la main",
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
{
|
||||
"addPeople": {
|
||||
"accessibilityLabel": {
|
||||
"meetingLink": "मीटिंग लिंक: {{url}}"
|
||||
},
|
||||
"add": "आमंत्रित करें",
|
||||
"addContacts": "संपर्क सूची से आमंत्रित करे",
|
||||
"contacts": "संपर्क",
|
||||
"copyInvite": "मीटिंग के आमंत्रण कि प्रतिलिपि बनाये",
|
||||
"copyLink": "मीटिंग कि लिंक कि प्रतिलिपि बनाये",
|
||||
"copyStream": "सीधे प्रसारण कि लिंक कि प्रतिलिपि बनाये",
|
||||
"copyStream": "सीधे प्रसारण कि लिंक कि प्रीतिलिपि बनाये",
|
||||
"countryNotSupported": "अभी हम इस गतव्य के लिये सक्षम नही है ।",
|
||||
"countryReminder": "यू.एस. के बाहर से काल कर रहे है तो कृपया सुनिश्चित करे कि अपने देश के कोड़ से प्रारंभ कर रहे है !",
|
||||
"defaultEmail": "अपना ई-मेल पता लिखें",
|
||||
@@ -39,7 +35,6 @@
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "ब्लूटूथ",
|
||||
"car": "कार ऑडियो",
|
||||
"headphones": "हेडफ़ोन",
|
||||
"none": "कोई ऑडियो डिवाइस उपलब्ध नहीं",
|
||||
"phone": "फ़ोन",
|
||||
@@ -48,47 +43,9 @@
|
||||
"audioOnly": {
|
||||
"audioOnly": "लो बैंडविड्थ"
|
||||
},
|
||||
"bandwidthSettings": {
|
||||
"assumedBandwidthBps": "उदाहरण: 10 Mbps के लिए 10000000",
|
||||
"assumedBandwidthBpsWarning": "अधिक मान नेटवर्क समस्याएँ पैदा कर सकते हैं।",
|
||||
"customValue": "कस्टम मान",
|
||||
"customValueEffect": "वास्तविक bps मान सेट करने के लिए",
|
||||
"leaveEmpty": "खाली छोड़ें",
|
||||
"leaveEmptyEffect": "अनुमान लगाने की अनुमति देने के लिए",
|
||||
"possibleValues": "संभावित मान",
|
||||
"setAssumedBandwidthBps": "अनुमानित बैंडविड्थ (bps)",
|
||||
"title": "बैंडविड्थ सेटिंग्स",
|
||||
"zeroEffect": "वीडियो अक्षम करने के लिए"
|
||||
},
|
||||
"breakoutRooms": {
|
||||
"actions": {
|
||||
"add": "ब्रेकआउट रूम जोड़ें",
|
||||
"autoAssign": "ब्रेकआउट रूम में स्वतः असाइन करें",
|
||||
"close": "बंद करें",
|
||||
"join": "जॉइन करें",
|
||||
"leaveBreakoutRoom": "ब्रेकआउट रूम छोड़ें",
|
||||
"more": "और",
|
||||
"remove": "हटाएँ",
|
||||
"rename": "नाम बदलें",
|
||||
"renameBreakoutRoom": "ब्रेकआउट रूम का नाम बदलें",
|
||||
"sendToBreakoutRoom": "प्रतिभागी को भेजें:"
|
||||
},
|
||||
"breakoutList": "ब्रेकआउट सूची",
|
||||
"buttonLabel": "ब्रेकआउट रूम",
|
||||
"defaultName": "ब्रेकआउट रूम #{{index}}",
|
||||
"hideParticipantList": "प्रतिभागी सूची छिपाएँ",
|
||||
"mainRoom": "मुख्य रूम",
|
||||
"notifications": {
|
||||
"joined": "\"{{name}}\" ब्रेकआउट रूम में शामिल हो रहे हैं",
|
||||
"joinedMainRoom": "मुख्य रूम में शामिल हो रहे हैं",
|
||||
"joinedTitle": "ब्रेकआउट रूम"
|
||||
},
|
||||
"showParticipantList": "प्रतिभागी सूची दिखाएँ",
|
||||
"title": "ब्रेकआउट रूम"
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "एक मीटिंग लिंक जोड़ें",
|
||||
"confirmAddLink": "क्या आप इस इवेंट में एक जित्सी लिंक जोड़ना चाहते हैं?",
|
||||
"confirmAddLink": "क्या आप इस इवेंट में एक Jitsi लिंक जोड़ना चाहते हैं?",
|
||||
"error": {
|
||||
"appConfiguration": "कैलेंडर एकीकरण ठीक से कॉन्फ़िगर नहीं किया गया है।",
|
||||
"generic": "एक त्रुटि हुई है। कृपया अपनी कैलेंडर सेटिंग जांचें या कैलेंडर को रीफ़्रेश करने का प्रयास करें।",
|
||||
@@ -104,72 +61,28 @@
|
||||
"refresh": "कैलेंडर रीफ़्रेश करें",
|
||||
"today": "आज"
|
||||
},
|
||||
"carmode": {
|
||||
"actions": {
|
||||
"selectSoundDevice": "साउंड डिवाइस चुनें"
|
||||
},
|
||||
"labels": {
|
||||
"buttonLabel": "कार मोड",
|
||||
"title": "कार मोड",
|
||||
"videoStopped": "आपका वीडियो बंद है"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"disabled": "चैट संदेश भेजना अक्षम है।",
|
||||
"enter": "चैट रूम में प्रवेश करें",
|
||||
"error": "त्रुटि: आपका संदेश नहीं भेजा गया । कारण: {{error}}",
|
||||
"everyone": "सभी",
|
||||
"fieldPlaceHolder": "अपना संदेश यहां लिखें",
|
||||
"fileAccessibleTitle": "{{user}} ने एक फ़ाइल अपलोड की",
|
||||
"fileAccessibleTitleMe": "मैंने एक फ़ाइल अपलोड की",
|
||||
"fileDeleted": "एक फ़ाइल हटा दी गई",
|
||||
"guestsChatIndicator": "(अतिथि)",
|
||||
"messageTo": "{{recipient}} के लिए निजी संदेश",
|
||||
"messagebox": "एक संदेश टाइप करें",
|
||||
"newMessages": "नए संदेश",
|
||||
"nickname": {
|
||||
"featureChat": "चैट",
|
||||
"featureClosedCaptions": "बंद कैप्शन",
|
||||
"featureFileSharing": "फ़ाइल साझा करना",
|
||||
"featurePolls": "पोल",
|
||||
"popover": "एक उपनाम चुनें",
|
||||
"title": "चैट का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWith1Features": "{{feature1}} का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWith2Features": "{{feature1}} और {{feature2}} का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWith3Features": "{{feature1}}, {{feature2}} और {{feature3}} का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWith4Features": "{{feature1}}, {{feature2}}, {{feature3}} और {{feature4}} का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWithCC": "चैट और बंद कैप्शन का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWithPolls": "चैट और पोल का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWithPollsAndCC": "चैट, पोल और बंद कैप्शन का उपयोग करने के लिए एक उपनाम दर्ज करें",
|
||||
"titleWithPollsAndCCAndFileSharing": "चैट, पोल, बंद कैप्शन और फ़ाइलों का उपयोग करने के लिए एक उपनाम दर्ज करें"
|
||||
"titleWithPolls": "चैट का उपयोग करने के लिए एक उपनाम दर्ज करें"
|
||||
},
|
||||
"noMessagesMessage": "अभी तक मीटिंग में कोई संदेश नहीं आया है। वार्तालाप प्रारंभ करें!",
|
||||
"privateNotice": "{{recipient}} के लिए निजी संदेश",
|
||||
"sendButton": "भेजें",
|
||||
"smileysPanel": "इमोजी पैनल",
|
||||
"systemDisplayName": "सिस्टम",
|
||||
"tabs": {
|
||||
"chat": "चैट",
|
||||
"closedCaptions": "सीसी",
|
||||
"fileSharing": "फ़ाइलें",
|
||||
"polls": "पोल"
|
||||
},
|
||||
"title": "चैट",
|
||||
"titleWithCC": "सीसी",
|
||||
"titleWithFeatures": "चैट और",
|
||||
"titleWithFileSharing": "फ़ाइलें",
|
||||
"titleWithPolls": "पोल",
|
||||
"titleWithPolls": "चैट",
|
||||
"you": "आप"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"buttonText": "क्रोम एक्सटेंशन इंस्टॉल करें",
|
||||
"buttonTextEdge": "Edge एक्सटेंशन इंस्टॉल करें",
|
||||
"close": "बंद करें",
|
||||
"dontShowAgain": "मुझे यह फिर से न दिखाएं"
|
||||
},
|
||||
"closedCaptionsTab": {
|
||||
"emptyState": "मॉडरेटर द्वारा शुरू किए जाने पर बंद कैप्शन की सामग्री उपलब्ध होगी",
|
||||
"startClosedCaptionsButton": "बंद कैप्शन शुरू करें"
|
||||
"dontShowAgain": "मुझे यह फिर से न दिखाएं",
|
||||
"installExtensionText": ",गूगल कैलेंडर और ऑफिस 365 एकीकरण के लिए एक्सटेंशन इंस्टॉल करें"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "आपको आपकी मीटिंग से कनेक्ट किया जा रहा है…"
|
||||
@@ -197,7 +110,6 @@
|
||||
"bridgeCount": "सर्वर गणना: ",
|
||||
"codecs": "कोडेक (ए/वी): ",
|
||||
"connectedTo": "से जुड़ा हुआ है :",
|
||||
"e2eeVerified": "E2EE सत्यापित:",
|
||||
"framerate": "फ्रेम दर:",
|
||||
"less": "कम दिखाएं",
|
||||
"localaddress": "स्थानीय पता:",
|
||||
@@ -224,8 +136,7 @@
|
||||
"status": "सम्पर्क:",
|
||||
"transport": "ट्रांसपोर्ट :",
|
||||
"transport_plural": "ट्रांसपोर्ट्स:",
|
||||
"video_ssrc": "वीडियो एस.आर.सी.सी.:",
|
||||
"yes": "हाँ"
|
||||
"video_ssrc": "वीडियो एस.आर.सी.सी.:"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "पिछला कल",
|
||||
@@ -235,25 +146,14 @@
|
||||
"deepLinking": {
|
||||
"appNotInstalled": "आपको अपने फ़ोन पर इस मीटिंग में शामिल होने के लिए {{app}} मोबाइल ऐप की आवश्यकता है। ",
|
||||
"description": "हमने आपकी मीटिंग {{app}} डेस्कटॉप ऐप में लॉन्च करने की कोशिश की। कुछ नहीं हुआ? फिर से कोशिश करें या {{app}} वेब ऐप में लॉन्च करें।",
|
||||
"descriptionNew": "कुछ नहीं हुआ? हमने आपकी मीटिंग को {{app}} डेस्कटॉप ऐप में खोलने की कोशिश की। <br /><br /> आप दोबारा प्रयास कर सकते हैं या इसे वेब पर खोल सकते हैं।",
|
||||
"descriptionWithoutWeb": "हमने आपकी मीटिंग {{app}} डेस्कटॉप ऐप में लॉन्च करने की कोशिश की। कुछ नहीं हुआ?",
|
||||
"downloadApp": "एप्लिकेशन डाउनलोड करें",
|
||||
"downloadMobileApp": "App Store से डाउनलोड करें",
|
||||
"ifDoNotHaveApp": "यदि आपके पास अभी तक ऐप नहीं है:",
|
||||
"ifHaveApp": "यदि आपके पास पहले से ही ऐप है:",
|
||||
"joinInApp": "ऐप का उपयोग करके इस मीटिंग में शामिल हों",
|
||||
"joinInAppNew": "ऐप में जुड़ें",
|
||||
"joinInBrowser": "ब्राउज़र में जुड़ें",
|
||||
"launchMeetingLabel": "आप इस मीटिंग में कैसे जुड़ना चाहते हैं?",
|
||||
"launchWebButton": "वेब में लॉन्च करे",
|
||||
"noDesktopApp": "क्या आपके पास ऐप नहीं है?",
|
||||
"noMobileApp": "क्या आपके पास ऐप नहीं है?",
|
||||
"or": "या",
|
||||
"termsAndConditions": "जारी रखने पर आप हमारी <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>नियम और शर्तों</a> से सहमत होते हैं।",
|
||||
"title": "{{app}} में आपकी मीटिंग शुरू की जा रही हैं…",
|
||||
"titleNew": "आपकी मीटिंग शुरू की जा रही है…",
|
||||
"tryAgainButton": "डेस्कटॉप में फिर से प्रयास करें",
|
||||
"unsupportedBrowser": "ऐसा लगता है कि आप ऐसे ब्राउज़र का उपयोग कर रहे हैं जिसे हम सपोर्ट नहीं करते।"
|
||||
"tryAgainButton": "डेस्कटॉप में फिर से प्रयास करें"
|
||||
},
|
||||
"defaultLink": "उदाहरण {{url}}",
|
||||
"defaultNickname": "उदा. सतीष कुमार",
|
||||
@@ -264,12 +164,6 @@
|
||||
"microphonePermission": "माइक्रोफ़ोन अनुमति प्राप्त करने में त्रुटि"
|
||||
},
|
||||
"deviceSelection": {
|
||||
"hid": {
|
||||
"callControl": "कॉल नियंत्रण",
|
||||
"connectedDevices": "कनेक्टेड डिवाइस:",
|
||||
"deleteDevice": "डिवाइस हटाएँ",
|
||||
"pairDevice": "डिवाइस जोड़ें"
|
||||
},
|
||||
"noPermission": "अनुमति नहीं दी गई",
|
||||
"previewUnavailable": "पूर्वदर्शन अनुपलब्ध",
|
||||
"selectADevice": "डिवाइस का चयन करें",
|
||||
@@ -284,23 +178,16 @@
|
||||
"IamHost": "मैं मेजबान हूँ",
|
||||
"Ok": "ठीक है",
|
||||
"Remove": "निकालें",
|
||||
"Share": "साझा करें",
|
||||
"Share": "Share",
|
||||
"Submit": "सबमिट करें",
|
||||
"Understand": "मैं समझता/समझती हूँ, अभी मुझे म्यूट रखें",
|
||||
"UnderstandAndUnmute": "मैं समझता/समझती हूँ, कृपया मुझे अनम्यूट करें",
|
||||
"WaitForHostMsg": "सम्मेलन अभी तक शुरू नहीं हुआ है। यदि आप मेजबान हैं तो कृपया प्रमाणित करें। अन्यथा, कृपया मेजबान के आने की प्रतीक्षा करें।",
|
||||
"WaitingForHostButton": "मॉडरेटर की प्रतीक्षा करें",
|
||||
"WaitingForHostTitle": "होस्ट की प्रतीक्षा कर रहा है…",
|
||||
"Yes": "हाँ",
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "सीधा प्रसारण"
|
||||
},
|
||||
"add": "जोड़ें",
|
||||
"addMeetingNote": "इस मीटिंग के बारे में एक नोट जोड़ें",
|
||||
"addOptionalNote": "एक नोट जोड़ें (वैकल्पिक):",
|
||||
"allow": "अनुमति दें",
|
||||
"allowToggleCameraDialog": "क्या आप {{initiatorName}} को आपके कैमरे का फ़ेसिंग मोड बदलने की अनुमति देते हैं?",
|
||||
"allowToggleCameraTitle": "कैमरा बदलने की अनुमति दें?",
|
||||
"alreadySharedVideoMsg": "एक अन्य प्रतिभागी पहले से ही वीडियो साझा कर रहा है। यह सम्मेलन एक समय में केवल एक साझा की अनुमति देता है।",
|
||||
"alreadySharedVideoTitle": "एक समय में केवल एक साझा वीडियो की अनुमति है",
|
||||
"applicationWindow": "एप्लिकेशन विंडो",
|
||||
@@ -325,7 +212,7 @@
|
||||
"connectErrorWithMsg": "उफ़! कुछ गड़बड़ हो गई और हम सम्मेलन से नहीं जुड़ सके: {{msg}}",
|
||||
"connecting": "संपर्क जोड़ा जा रहा है ",
|
||||
"contactSupport": "सहयोग के लिए संपर्क करें",
|
||||
"copied": "प्रतिलिपि बनाई गयी",
|
||||
"copied": "प्रतिलिपि बनाई गयी ",
|
||||
"copy": "प्रतिलिपि बनाये",
|
||||
"dismiss": "खारिज करें",
|
||||
"displayNameRequired": "नमस्ते! आपका नाम क्या है?",
|
||||
@@ -392,7 +279,7 @@
|
||||
"readMore": "अधिक",
|
||||
"recording": "रिकॉर्डिंग",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "संभव नहीं है जब एक लाइव स्ट्रीम सक्रिय है",
|
||||
"recordingDisabledTooltip": "रिकॉर्डिंग शुरू करना अक्षम करें|",
|
||||
"recordingDisabledTooltip": "रिकॉर्डिंग शुरू करना अक्षम करें.",
|
||||
"rejoinNow": "पुनः जुड़े",
|
||||
"remoteControlAllowedMessage": "{{user}} ने आपका रिमोट कंट्रोल अनुरोध स्वीकार कर लिया!",
|
||||
"remoteControlDeniedMessage": "{{user}} ने आपका रिमोट कंट्रोल अनुरोध अस्वीकार कर दिया!",
|
||||
@@ -408,7 +295,7 @@
|
||||
"reservationErrorMsg": "Error code: {{code}}, message: {{msg}}",
|
||||
"retry": "पुनः प्रयास करें",
|
||||
"screenSharingAudio": "ऑडियो साझा करें",
|
||||
"screenSharingFailed": "स्क्रीन शेयरिंग शुरू नहीं हो पाई।",
|
||||
"screenSharingFailed": "उफ़! कुछ गड़बड़ हो गई, हम स्क्रीन शेयरिंग शुरू करने में सक्षम नहीं थे!",
|
||||
"screenSharingFailedTitle": "स्क्रीन साझा करना विफल हुआ!",
|
||||
"screenSharingPermissionDeniedError": "उफ़! आपकी स्क्रीन शेयरिंग अनुमतियों में कुछ गड़बड़ हो गई है। कृपया पुनः लोड करें और पुनः प्रयास करें।",
|
||||
"sendPrivateMessage": "आपने हाल ही में एक निजी संदेश प्राप्त किया है। क्या आप उसका निजी रूप से जवाब देने का इरादा रखते हैं? या आप अपना संदेश समूह को भेजना चाहते हैं?",
|
||||
@@ -460,34 +347,6 @@
|
||||
"veryBad": "बहुत बुरा",
|
||||
"veryGood": "बहुत अच्छा"
|
||||
},
|
||||
"fileSharing": {
|
||||
"downloadFailedDescription": "कृपया फिर से प्रयास करें।",
|
||||
"downloadFailedTitle": "डाउनलोड विफल",
|
||||
"downloadFile": "डाउनलोड करें",
|
||||
"downloadStarted": "फ़ाइल डाउनलोड शुरू हो गया",
|
||||
"dragAndDrop": "फ़ाइलों को यहाँ या स्क्रीन पर कहीं भी खींचें और छोड़ें",
|
||||
"fileAlreadyUploaded": "फ़ाइल पहले ही इस मीटिंग में अपलोड हो चुकी है।",
|
||||
"fileRemovedByOther": "आपकी फ़ाइल '{{ fileName }}' हटा दी गई",
|
||||
"fileTooLargeDescription": "कृपया सुनिश्चित करें कि फ़ाइल {{ maxFileSize }} से अधिक नहीं है।",
|
||||
"fileTooLargeTitle": "चयनित फ़ाइल बहुत बड़ी है",
|
||||
"fileUploadProgress": "फ़ाइल अपलोड प्रगति",
|
||||
"fileUploadedSuccessfully": "फ़ाइल सफलतापूर्वक अपलोड हो गई",
|
||||
"newFileNotification": "{{ participantName }} ने '{{ fileName }}' साझा की",
|
||||
"removeFile": "हटाएँ",
|
||||
"removeFileSuccess": "फ़ाइल सफलतापूर्वक हटा दी गई",
|
||||
"uploadFailedDescription": "कृपया फिर से प्रयास करें।",
|
||||
"uploadFailedTitle": "अपलोड विफल",
|
||||
"uploadFile": "फ़ाइल साझा करें"
|
||||
},
|
||||
"filmstrip": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "वीडियो थंबनेल"
|
||||
}
|
||||
},
|
||||
"giphy": {
|
||||
"noResults": "कोई परिणाम नहीं मिला :(",
|
||||
"search": "GIPHY खोजें"
|
||||
},
|
||||
"helpView": {
|
||||
"title": "सहायता केंद्र"
|
||||
},
|
||||
@@ -503,7 +362,6 @@
|
||||
"addPassword": "$t(lockRoomPassword)जोड़ें",
|
||||
"cancelPassword": "$t(lockRoomPassword)रद्द करें",
|
||||
"conferenceURL": "लिंक:",
|
||||
"copyNumber": "नंबर कॉपी करें",
|
||||
"country": "देश",
|
||||
"dialANumber": "अपनी मीटिंग में शामिल होने के लिए, इनमें से किसी एक नंबर को डायल करें और फिर पिन डालें।",
|
||||
"dialInConferenceID": "पिन:",
|
||||
@@ -526,16 +384,11 @@
|
||||
"noRoom": "डायल-इन करने के लिए कोई कक्ष निर्दिष्ट नहीं किया गया।",
|
||||
"numbers": "डायल-इन नंबर",
|
||||
"password": "$t(lockRoomPasswordUppercase):",
|
||||
"reachedLimit": "आप अपने प्लान की सीमा तक पहुँच चुके हैं।",
|
||||
"sip": "SIP पता",
|
||||
"sipAudioOnly": "केवल ऑडियो SIP पता",
|
||||
"title": "साझा करें",
|
||||
"tooltip": "इस मीटिंग के लिए लिंक और डायल-इन जानकारी साझा करें",
|
||||
"upgradeOptions": "कृपया अपग्रेड विकल्पों की जाँच करें",
|
||||
"whiteboardError": "व्हाइटबोर्ड लोड करने में त्रुटि। कृपया बाद में पुनः प्रयास करें।"
|
||||
"tooltip": "इस मीटिंग के लिए लिंक और डायल-इन जानकारी साझा करें"
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "कुछ समस्या हुई।",
|
||||
"msg": "We stumbled a bit.",
|
||||
"retry": "पुनः प्रयास करें",
|
||||
"support": "सहायता",
|
||||
"supportMsg": "ऐसा बार बार हो रहा हो, तो सम्पर्क करे "
|
||||
@@ -564,10 +417,6 @@
|
||||
"toggleShortcuts": "कीबोर्ड शॉर्टकट दिखाएं या छिपाएं",
|
||||
"videoMute": "अपना कैमरा प्रारंभ या बंद करें"
|
||||
},
|
||||
"largeVideo": {
|
||||
"screenIsShared": "आप अपनी स्क्रीन साझा कर रहे हैं",
|
||||
"showMeWhatImSharing": "मुझे दिखाएँ कि मैं क्या साझा कर रहा हूँ"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "हम स्ट्रीमिंग संसाधनों को मुक्त करने पर काम कर रहे हैं। कृपया कुछ मिनटों में पुनः प्रयास करें।",
|
||||
"busyTitle": "सभी स्ट्रीमर वर्तमान में व्यस्त हैं",
|
||||
@@ -612,7 +461,7 @@
|
||||
"emailField": "अपना ईमेल पता दर्ज करें",
|
||||
"enableDialogPasswordField": "पासवर्ड सेट करें (वैकल्पिक)",
|
||||
"enableDialogSubmit": "सक्षम करें",
|
||||
"enableDialogText": "लॉबी मोड से आप अपनी मीटिंग को सुरक्षित रख सकते हैं। केवल मॉडरेटर की अनुमति मिलने के बाद ही लोग इसमें शामिल हो पाएंगे।",
|
||||
"enableDialogText": "Lobby mode lets you protect your meeting by only allowing people to enter after a formal approval by a moderator.",
|
||||
"enterPasswordButton": "मीटिंग पासवर्ड दर्ज करें",
|
||||
"enterPasswordTitle": "मीटिंग में शामिल होने के लिए पासवर्ड दर्ज करें",
|
||||
"invalidPassword": "अमान्य पासवर्ड",
|
||||
@@ -626,8 +475,8 @@
|
||||
"knockTitle": "कोई व्यक्ति बैठक में शामिल होना चाहता है",
|
||||
"knockingParticipantList": "प्रतिभागी सूची दस्तक",
|
||||
"nameField": "अपना नाम दर्ज करें",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} को {{originParticipantName}} ने मीटिंग में शामिल होने की अनुमति नहीं दी।",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} को {{originParticipantName}} ने मीटिंग में शामिल होने की अनुमति दी।",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} has been rejected to join by {{originParticipantName}}",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} has been allowed to join by {{originParticipantName}}",
|
||||
"notificationLobbyDisabled": "लॉबी को {{originParticipantName}}द्वारा अक्षम कर दिया गया",
|
||||
"notificationLobbyEnabled": "लॉबी को {{originParticipantName}}द्वारा सक्षम किया गया",
|
||||
"notificationTitle": "लॉबी",
|
||||
@@ -660,12 +509,9 @@
|
||||
"no": "नहीं",
|
||||
"participant": "प्रतिभागी",
|
||||
"participantStats": "प्रतिभागी आँकड़े",
|
||||
"selectTabTitle": "🎥 रिकॉर्डिंग के लिए कृपया इस टैब को चुनें",
|
||||
"sessionToken": "सत्र टोकन",
|
||||
"start": "रिकॉर्डिंग प्रारंभ करें",
|
||||
"stop": "रिकॉर्डिंग बंद करें",
|
||||
"stopping": "रिकॉर्डिंग बंद की जा रही है",
|
||||
"wait": "कृपया प्रतीक्षा करें, आपकी रिकॉर्डिंग सेव की जा रही है",
|
||||
"yes": "हाँ"
|
||||
},
|
||||
"lockRoomPassword": "पासवर्ड",
|
||||
@@ -676,30 +522,11 @@
|
||||
},
|
||||
"me": "मैं",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "सुरक्षा में खामी",
|
||||
"allowAll": "सभी की अनुमति दें",
|
||||
"allowAudio": "ऑडियो की अनुमति दें",
|
||||
"allowDesktop": "स्क्रीन शेयरिंग की अनुमति दें",
|
||||
"allowVideo": "वीडियो की अनुमति दें",
|
||||
"allowedUnmute": "आप माइक्रोफोन अनम्यूट कर सकते हैं, कैमरा चालू कर सकते हैं या स्क्रीन साझा कर सकते हैं।",
|
||||
"audioUnmuteBlockedDescription": "सिस्टम सीमाओं के कारण माइक्रोफोन अनम्यूट अस्थायी रूप से ब्लॉक कर दिया गया है।",
|
||||
"audioUnmuteBlockedTitle": "माइक्रोफोन अनम्यूट ब्लॉक!",
|
||||
"chatMessages": "चैट संदेश",
|
||||
"OldElectronAPPTitle": "Security vulnerability!",
|
||||
"connectedOneMember": "{{name}} मीटिंग में शामिल हुए",
|
||||
"connectedThreePlusMembers": "{{name}} और {{count}} अन्य लोग मीटिंग में शामिल हुए",
|
||||
"connectedTwoMembers": "{{first}} और {{second}} मीटिंग में शामिल हुआ",
|
||||
"connectionFailed": "कनेक्शन विफल। कृपया बाद में पुनः प्रयास करें!",
|
||||
"dataChannelClosed": "वीडियो की गुणवत्ता प्रभावित हो सकती है",
|
||||
"dataChannelClosedDescription": "ब्रिज चैनल बंद है, इसलिए वीडियो की गुणवत्ता न्यूनतम स्तर तक सीमित हो सकती है।",
|
||||
"dataChannelClosedDescriptionWithAudio": "ब्रिज चैनल बंद है, इसलिए ऑडियो और वीडियो में व्यवधान हो सकता है।",
|
||||
"dataChannelClosedWithAudio": "ऑडियो और वीडियो की गुणवत्ता प्रभावित हो सकती है",
|
||||
"desktopMutedRemotelyTitle": "आपकी स्क्रीन शेयरिंग {{participantDisplayName}} द्वारा रोकी गई है",
|
||||
"disabledIframe": "एंबेडिंग केवल डेमो उद्देश्यों के लिए है, इसलिए यह कॉल {{timeout}} मिनट में डिस्कनेक्ट हो जाएगी।",
|
||||
"disabledIframeSecondaryNative": "एंबेडिंग {{domain}} केवल डेमो उद्देश्यों के लिए है, इसलिए यह कॉल {{timeout}} मिनट में डिस्कनेक्ट हो जाएगी।",
|
||||
"disabledIframeSecondaryWeb": "एंबेडिंग {{domain}} केवल डेमो उद्देश्यों के लिए है, इसलिए यह कॉल {{timeout}} मिनट में डिस्कनेक्ट हो जाएगी। उत्पादन एंबेडिंग के लिए कृपया <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> का उपयोग करें!",
|
||||
"disconnected": "डिस्कनेक्ट",
|
||||
"displayNotifications": "सूचनाएँ दिखाएँ",
|
||||
"dontRemindMe": "मुझे याद न दिलाएँ",
|
||||
"focus": "Conference focus",
|
||||
"focusFail": "{{component}} उपलब्ध नहीं - {{ms}} सेकंड में पुनः प्रयास करें",
|
||||
"grantedTo": "Moderator rights granted to {{to}}!",
|
||||
@@ -707,29 +534,7 @@
|
||||
"invitedThreePlusMembers": "{{name}} और {{count}} अन्य लोगों को आमंत्रित किया गया",
|
||||
"invitedTwoMembers": "{{first}} और {{second}} को आमंत्रित किया गया",
|
||||
"kickParticipant": "{{kicked}} को {{kicker}} द्वारा किक किया गया",
|
||||
"leftOneMember": "{{name}} ने मीटिंग छोड़ दी",
|
||||
"leftThreePlusMembers": "{{name}} और कई अन्य ने मीटिंग छोड़ दी",
|
||||
"leftTwoMembers": "{{first}} और {{second}} ने मीटिंग छोड़ दी",
|
||||
"linkToSalesforce": "Salesforce से लिंक करें",
|
||||
"linkToSalesforceDescription": "आप मीटिंग सारांश को Salesforce ऑब्जेक्ट से लिंक कर सकते हैं।",
|
||||
"linkToSalesforceError": "मीटिंग को Salesforce से लिंक करने में विफल",
|
||||
"linkToSalesforceKey": "इस मीटिंग को लिंक करें",
|
||||
"linkToSalesforceProgress": "मीटिंग को Salesforce से लिंक किया जा रहा है…",
|
||||
"linkToSalesforceSuccess": "मीटिंग सफलतापूर्वक Salesforce से लिंक हो गई",
|
||||
"localRecordingStarted": "{{name}} ने लोकल रिकॉर्डिंग शुरू की।",
|
||||
"localRecordingStopped": "{{name}} ने लोकल रिकॉर्डिंग बंद की।",
|
||||
"me": "मैं",
|
||||
"moderationInEffectCSDescription": "यदि आप अपनी स्क्रीन साझा करना चाहते हैं तो कृपया हाथ उठाएँ।",
|
||||
"moderationInEffectCSTitle": "स्क्रीन शेयरिंग मॉडरेटर द्वारा ब्लॉक की गई है",
|
||||
"moderationInEffectDescription": "यदि आप बोलना चाहते हैं तो कृपया हाथ उठाएँ।",
|
||||
"moderationInEffectTitle": "आपका माइक्रोफोन मॉडरेटर द्वारा म्यूट किया गया है",
|
||||
"moderationInEffectVideoDescription": "यदि आप अपना कैमरा चालू करना चाहते हैं तो कृपया हाथ उठाएँ।",
|
||||
"moderationInEffectVideoTitle": "आपका कैमरा मॉडरेटर द्वारा ब्लॉक किया गया है",
|
||||
"moderationRequestFromModerator": "होस्ट चाहता है कि आप अनम्यूट करें",
|
||||
"moderationRequestFromParticipant": "बोलना चाहता है",
|
||||
"moderationStartedTitle": "मॉडरेशन शुरू हो गई",
|
||||
"moderationStoppedTitle": "मॉडरेशन बंद हो गई",
|
||||
"moderationToggleDescription": "{{participantDisplayName}} द्वारा",
|
||||
"moderator": "मॉडरेटर के अधिकार दिए गए!",
|
||||
"muted": "आपने वार्तालाप को म्यूट करके शुरू किया है।",
|
||||
"mutedRemotelyDescription": "जब आप बोलने के लिए तैयार हों, तो आप हमेशा अनम्यूट कर सकते हैं। बैठक में शोर कम रखने के लिए बोलने के बाद म्यूट कर दें।",
|
||||
@@ -738,85 +543,24 @@
|
||||
"newDeviceAction": "उपयोग करें",
|
||||
"newDeviceAudioTitle": "नए ऑडियो डिवाइस का पता चला",
|
||||
"newDeviceCameraTitle": "नए कैमरे का पता चला",
|
||||
"nextToSpeak": "आप अगली बारी में बोलने वाले हैं",
|
||||
"noiseSuppressionDesktopAudioDescription": "डेस्कटॉप ऑडियो साझा करते समय अतिरिक्त नॉइज़ सप्रेशन सक्षम नहीं किया जा सकता, कृपया इसे अक्षम करें और पुनः प्रयास करें।",
|
||||
"noiseSuppressionFailedTitle": "अतिरिक्त नॉइज़ सप्रेशन शुरू करने में विफल",
|
||||
"noiseSuppressionStereoDescription": "स्टीरियो ऑडियो के साथ अतिरिक्त नॉइज़ सप्रेशन वर्तमान में समर्थित नहीं है।",
|
||||
"oldElectronClientDescription1": "आप जित्सी मीट क्लाइंट के एक पुराने संस्करण का उपयोग करते हुए दिखाई देते हैं, जिसमे सुरक्षा कमजोरियां ज्ञात हैं।",
|
||||
"oldElectronClientDescription2": "नवीनतम बिल्ड",
|
||||
"oldElectronClientDescription3": " अब!",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) किसी अन्य प्रतिभागी द्वारा हटा दिया गया",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) दूसरे प्रतिभागी द्वारा निर्धारित",
|
||||
"raisedHand": "{{name}} बोलना चाहेंगे।",
|
||||
"raisedHands": "{{participantName}} और {{raisedHands}} अन्य लोग",
|
||||
"reactionSounds": "ध्वनि बंद करें",
|
||||
"reactionSoundsForAll": "सभी के लिए ध्वनि बंद करें",
|
||||
"screenShareNoAudio": "विंडो चयन स्क्रीन में 'ऑडियो साझा करें' बॉक्स चयनित नहीं था।",
|
||||
"screenShareNoAudioTitle": "सिस्टम ऑडियो साझा नहीं किया जा सका!",
|
||||
"screenSharingAudioOnlyDescription": "कृपया ध्यान दें कि अपनी स्क्रीन साझा करने से \"सर्वोत्तम प्रदर्शन\" मोड प्रभावित होगा और आप अधिक बैंडविड्थ का उपयोग करेंगे।",
|
||||
"screenSharingAudioOnlyTitle": "\"सर्वोत्तम प्रदर्शन\" मोड",
|
||||
"selfViewTitle": "आप सेटिंग्स से हमेशा सेल्फ-व्यू को अन-हाइड कर",
|
||||
"somebody": "Somebody",
|
||||
"startSilentDescription": "ऑडियो सक्षम करने के लिए मीटिंग को फिर से करें",
|
||||
"startSilentTitle": "आप बिना ऑडियो आउटपुट के साथ शामिल हुए!",
|
||||
"suboptimalBrowserWarning": "हमें डर है कि आपकी मीटिंग अनुभव यहाँ बहुत अच्छा नहीं होने वाला है। हम इसे सुधारने के तरीके ढूंढ़ रहे हैं, लेकिन उस समय तक कृपया <a href='{{recommendedBrowserPageLink}}' target='_blank'>पूरी तरह से समर्थित ब्राउज़र</a> में से एक का प्रयास करें",
|
||||
"suboptimalExperienceTitle": "ब्राउज़र चेतावनी",
|
||||
"suggestRecordingAction": "शुरू करें",
|
||||
"suggestRecordingDescription": "क्या आप रिकॉर्डिंग शुरू करना चाहेंगे?",
|
||||
"suggestRecordingTitle": "इस मीटिंग को रिकॉर्ड करें",
|
||||
"unmute": "अनम्यूट",
|
||||
"videoMutedRemotelyDescription": "आप इसे हमेशा फिर से चालू कर सकते हैं।",
|
||||
"videoMutedRemotelyTitle": "आपका कैमरा {{participantDisplayName}}द्वारा अक्षम कर दिया गया है!",
|
||||
"videoUnmuteBlockedDescription": "सिस्टम सीमाओं के कारण कैमरा अनम्यूट और डेस्कटॉप शेयरिंग अस्थायी रूप से ब्लॉक कर दी गई है।",
|
||||
"videoUnmuteBlockedTitle": "कैमरा अनम्यूट और डेस्कटॉप शेयरिंग ब्लॉक!",
|
||||
"viewParticipants": "प्रतिभागियों को देखें",
|
||||
"viewVisitors": "दर्शकों को देखें",
|
||||
"waitingParticipants": "{{waitingParticipants}} लोग",
|
||||
"waitingVisitors": "कतार में प्रतीक्षारत दर्शक: {{waitingVisitors}}",
|
||||
"waitingVisitorsTitle": "मीटिंग अभी लाइव नहीं है!",
|
||||
"whiteboardLimitDescription": "कृपया अपनी प्रगति सहेजें, क्योंकि उपयोगकर्ता सीमा जल्द ही पहुँच जाएगी और व्हाइटबोर्ड बंद हो जाएगा।",
|
||||
"whiteboardLimitTitle": "व्हाइटबोर्ड उपयोग"
|
||||
"videoMutedRemotelyTitle": "आपका कैमरा {{participantDisplayName}}द्वारा अक्षम कर दिया गया है!"
|
||||
},
|
||||
"participantsPane": {
|
||||
"actions": {
|
||||
"admit": "स्वीकृत करें",
|
||||
"admitAll": "सभी को स्वीकृत करें",
|
||||
"allow": "नॉन-मॉडरेटर को अनुमति दें:",
|
||||
"allowDesktop": "स्क्रीन शेयरिंग की अनुमति दें",
|
||||
"allowVideo": "वीडियो की अनुमति दें",
|
||||
"askDesktop": "स्क्रीन साझा करने के लिए पूछें",
|
||||
"askUnmute": "अनम्यूट करने के लिए पूछें",
|
||||
"audioModeration": "स्वयं अनम्यूट करें",
|
||||
"blockEveryoneMicCamera": "सभी के माइक्रोफोन और कैमरा ब्लॉक करें",
|
||||
"breakoutRooms": "ब्रेकआउट रूम",
|
||||
"desktopModeration": "स्क्रीन शेयरिंग शुरू करें",
|
||||
"goLive": "लाइव जाएँ",
|
||||
"invite": "किसी को आमंत्रित करें",
|
||||
"lowerAllHands": "सभी हाथ नीचे करें",
|
||||
"lowerHand": "हाथ नीचे करें",
|
||||
"moreModerationActions": "अधिक मॉडरेशन विकल्प",
|
||||
"moreModerationControls": "अधिक मॉडरेशन नियंत्रण",
|
||||
"moreParticipantOptions": "अधिक प्रतिभागी विकल्प",
|
||||
"mute": "म्यूट करें",
|
||||
"muteAll": "सभी को म्यूट करें",
|
||||
"muteEveryoneElse": "बाकी सभी को म्यूट करें",
|
||||
"reject": "अस्वीकार",
|
||||
"stopDesktop": "स्क्रीन शेयरिंग बंद करें",
|
||||
"stopEveryonesDesktop": "सभी की स्क्रीन शेयरिंग बंद करें",
|
||||
"stopEveryonesVideo": "सभी का वीडियो बंद करें",
|
||||
"stopVideo": "वीडियो बंद करें",
|
||||
"unblockEveryoneMicCamera": "सभी के माइक्रोफोन और कैमरा अनब्लॉक करें",
|
||||
"videoModeration": "उनका वीडियो शुरू करें"
|
||||
},
|
||||
"headings": {
|
||||
"lobby": "लॉबी ({{count}})",
|
||||
"participantsList": "मीटिंग प्रतिभागी ({{count}})",
|
||||
"viewerRequests": "दर्शकों के अनुरोध {{count}}",
|
||||
"visitorInQueue": " (प्रतीक्षा में {{count}})",
|
||||
"visitorRequests": " (अनुरोध {{count}})",
|
||||
"visitors": "दर्शक {{count}}",
|
||||
"visitorsList": "दर्शक ({{count}})",
|
||||
"waitingLobby": "लॉबी में प्रतीक्षा कर रहे हैं ({{count}})"
|
||||
"reject": "अस्वीकार"
|
||||
}
|
||||
},
|
||||
"passwordDigitsOnly": "अधिकतम {{number}} अंक",
|
||||
@@ -873,7 +617,6 @@
|
||||
"joinAudioByPhone": "फोन ऑडियो के साथ जुड़ें",
|
||||
"joinMeeting": "मीटिंग में शामिल हों",
|
||||
"joinWithoutAudio": "ऑडियो के बिना जुड़ें",
|
||||
"keyboardShortcuts": "कीबोर्ड शॉर्टकट सक्षम करें",
|
||||
"linkCopied": "लिंक क्लिपबोर्ड पर कॉपी किया गया",
|
||||
"lookGood": "ऐसा लगता है कि आपका माइक्रोफ़ोन ठीक से काम कर रहा है",
|
||||
"or": "या",
|
||||
@@ -899,10 +642,9 @@
|
||||
"ringing": "Ringing…"
|
||||
},
|
||||
"profile": {
|
||||
"avatar": "अवतार",
|
||||
"setDisplayNameLabel": "अपना नाम सेट करें",
|
||||
"setEmailInput": "ई-मेल दर्ज करें",
|
||||
"setEmailLabel": "Gravatar ईमेल",
|
||||
"setEmailLabel": "Set अपना ग्रेवार्ट ईमेल सेट करें",
|
||||
"title": "प्रोफ़ाइल"
|
||||
},
|
||||
"raisedHand": "बोलना चाहेंगे",
|
||||
@@ -918,12 +660,6 @@
|
||||
"expandedPending": "रिकॉर्डिंग शुरू की जा रही है…",
|
||||
"failedToStart": "रिकॉर्डिंग शुरू करने में विफलता हुई।",
|
||||
"fileSharingdescription": "रिकॉर्डिंग को बैठक प्रतिभागियों के साथ साझा करें",
|
||||
"highlight": "हाइलाइट",
|
||||
"highlightMoment": "महत्वपूर्ण क्षण",
|
||||
"highlightMomentDisabled": "रिकॉर्डिंग शुरू होने पर आप क्षणों को हाइलाइट कर सकते हैं",
|
||||
"highlightMomentSuccess": "क्षण हाइलाइट किया गया",
|
||||
"highlightMomentSucessDescription": "आपका हाइलाइट किया गया क्षण मीटिंग सारांश में जोड़ा जाएगा।",
|
||||
"inProgress": "रिकॉर्डिंग या लाइव स्ट्रीमिंग प्रगति में है",
|
||||
"limitNotificationDescriptionNative": "उच्च मांग के कारण आपकी रिकॉर्डिंग {{limit}} मिनट तक सीमित रहेगी। असीमित रिकॉर्डिंग के लिए <3>{{app}}</3> आज़माएँ।",
|
||||
"limitNotificationDescriptionWeb": "उच्च मांग के कारण आपकी रिकॉर्डिंग {{limit}} मिनट तक सीमित रहेगी। असीमित रिकॉर्डिंग के लिए <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a> आज़माएँ।",
|
||||
"live": "लाइव",
|
||||
@@ -941,26 +677,18 @@
|
||||
"signOut": "साइन आउट करें",
|
||||
"title": "रिकॉर्डिंग",
|
||||
"unavailable": "ओह! {{serviceName}} वर्तमान में अनुपलब्ध है। हम इस समस्या को हल करने पर काम कर रहे हैं। कृपया बाद में पुनः प्रयास करें।",
|
||||
"unavailableTitle": "रिकॉर्डिंग उपलब्ध नहीं है",
|
||||
"uploadToCloud": "क्लाउड पर अपलोड करें"
|
||||
"unavailableTitle": "रिकॉर्डिंग उपलब्ध नहीं है"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "रीफ़्रेश करने के लिए नीचे खींचें"
|
||||
},
|
||||
"security": {
|
||||
"about": "आप अपनी मीटिंग में $t(lockRoomPassword) जोड़ सकते हैं। सहभागियों को मीटिंग में शामिल होने से पहले $t(lockRoomPassword) प्रदान करना होगा।",
|
||||
"aboutReadOnly": "मॉडरेटर मीटिंग में $t(lockRoomPassword) जोड़ सकते हैं। प्रतिभागियों को मीटिंग में शामिल होने से पहले यह $t(lockRoomPassword) प्रदान करना होगा।",
|
||||
"aboutReadOnly": "Moderator participants can add a $t(lockRoomPassword) to the meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.",
|
||||
"insecureRoomNameWarning": "कमरे का नाम असुरक्षित है। अनचाहे सहभागियों की कॉन्फ्रेंस में शामिल हो सकते हैं। सुरक्षा बटन का उपयोग करके अपनी मीटिंग को सुरक्षित बनाने का विचार करें। ",
|
||||
"title": "सुरक्षा विकल्प",
|
||||
"unsafeRoomActions": {
|
||||
"meeting": "सुरक्षा बटन का उपयोग करके अपनी मीटिंग को सुरक्षित करने पर विचार करें।",
|
||||
"prejoin": "कृपया अधिक विशिष्ट मीटिंग नाम का उपयोग करने पर विचार करें।",
|
||||
"welcome": "कृपया अधिक विशिष्ट मीटिंग नाम का उपयोग करें या दिए गए सुझावों में से किसी एक को चुनें।"
|
||||
}
|
||||
"securityOptions": "Security options"
|
||||
},
|
||||
"settings": {
|
||||
"audio": "ऑडियो",
|
||||
"buttonLabel": "सेटिंग्स",
|
||||
"calendar": {
|
||||
"about": "{{appName}} कैलेंडर एकीकरण आपके कैलेंडर तक सुरक्षित रूप से पहुंचने के लिए उपयोग किया जाता है ताकि यह आगामी कार्यक्रम पढ़ सके।",
|
||||
"disconnect": "डिस्कनेक्ट करें",
|
||||
@@ -970,35 +698,23 @@
|
||||
},
|
||||
"devices": "डिवाइस",
|
||||
"followMe": "हर कोई मेरा अनुसरण करेगा",
|
||||
"incomingMessage": "आने वाला संदेश",
|
||||
"language": "भाषा",
|
||||
"loggedIn": "{{name}} के रूप में लॉग इन किया",
|
||||
"maxStageParticipants": "मुख्य स्टेज पर पिन किए जा सकने वाले प्रतिभागियों की अधिकतम संख्या",
|
||||
"microphones": "माइक्रोफोन",
|
||||
"moderator": "होस्ट",
|
||||
"more": "अधिक",
|
||||
"moderator": "Moderator",
|
||||
"more": "More",
|
||||
"name": "नाम",
|
||||
"noDevice": "कोई नहीं",
|
||||
"notifications": "सूचना",
|
||||
"participantJoined": "प्रतिभागी जुड़े",
|
||||
"participantKnocking": "प्रतिभागी लॉबी में आए",
|
||||
"participantLeft": "प्रतिभागी बाहर गए",
|
||||
"playSounds": "ध्वनि चलाएँ",
|
||||
"reactions": "मीटिंग प्रतिक्रियाएँ",
|
||||
"sameAsSystem": "सिस्टम के समान ({{label}})",
|
||||
"selectAudioOutput": "ऑडियो आउटपुट",
|
||||
"selectCamera": "कैमरा",
|
||||
"selectMic": "माइक्रोफोन",
|
||||
"shortcuts": "शॉर्टकट्स",
|
||||
"speakers": "वक्ता",
|
||||
"speakers": "Speakers",
|
||||
"startAudioMuted": "सभी लोग म्यूट से शुरू करेंगे",
|
||||
"startVideoMuted": "सभी लोग छिपे हुए शुरू करेंगे",
|
||||
"talkWhileMuted": "म्यूट रहते हुए बोलें",
|
||||
"title": "सेटिंग",
|
||||
"video": "वीडियो"
|
||||
"title": "सेटिंग"
|
||||
},
|
||||
"settingsView": {
|
||||
"advanced": "उन्नत",
|
||||
"advanced": "Advanced",
|
||||
"alertCancel": "रद्द करें",
|
||||
"alertOk": "ओके",
|
||||
"alertTitle": "चेतावनी",
|
||||
@@ -1114,14 +830,14 @@
|
||||
"exitTileView": "टाइल दृश्य से बाहर निकलें",
|
||||
"feedback": "प्रतिक्रिया छोड़ें",
|
||||
"hangup": "छोड़ें",
|
||||
"help": "सहायता",
|
||||
"help": "Help",
|
||||
"invite": "लोगों को आमंत्रित करें",
|
||||
"lobbyButtonDisable": "लॉबी मोड को अक्षम करें",
|
||||
"lobbyButtonEnable": "लॉबी मोड सक्षम करें",
|
||||
"login": "लॉग इन",
|
||||
"logout": "लॉगआउट",
|
||||
"lowerYourHand": "अपना हाथ नीचे करें",
|
||||
"moreActions": "अधिक कार्रवाइयाँ",
|
||||
"moreActions": "More actions",
|
||||
"moreOptions": "अधिक विकल्प",
|
||||
"mute": "म्यूट / अनम्यूट",
|
||||
"muteEveryone": "सभी को म्यूट करें",
|
||||
@@ -1142,21 +858,19 @@
|
||||
"security": "सुरक्षा विकल्प",
|
||||
"selectBackground": "पृष्ठभूमि का चयन करें",
|
||||
"shareRoom": "किसी को आमंत्रित करें",
|
||||
"shareaudio": "ऑडियो साझा करें",
|
||||
"sharedvideo": "एक वीडियो साझा करें",
|
||||
"sharedvideo": "एक YouTube वीडियो साझा करें",
|
||||
"shortcuts": "शॉर्टकट देखें",
|
||||
"speakerStats": "स्पीकर आँकड़े",
|
||||
"startScreenSharing": "स्क्रीन साझाकरण प्रारंभ करें",
|
||||
"startSubtitles": "सबटाइटल शुरू करें",
|
||||
"startSubtitles": "Start subtitles",
|
||||
"stopScreenSharing": "स्क्रीन शेयरिंग बंद करो",
|
||||
"stopSharedVideo": "वीडियो बंद करें",
|
||||
"stopSharedVideo": "YouTube वीडियो बंद करें",
|
||||
"stopSubtitles": "उपशीर्षक बंद करें",
|
||||
"talkWhileMutedPopup": "बोलने की कोशिश कर रहा है? आप मौन हैं",
|
||||
"tileViewToggle": "टॉगल टाइल दृश्य",
|
||||
"toggleCamera": "कैमरा टॉगल करें",
|
||||
"videoSettings": "वीडियो सेटिंग्स",
|
||||
"videomute": "स्टॉप कैमरा",
|
||||
"videounmute": "स्टार्ट कैमरा"
|
||||
"videomute": "स्टार्ट / स्टॉप कैमरा"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "सबटाइटल शुरू / बंद करें",
|
||||
@@ -1205,11 +919,10 @@
|
||||
"domuteOthers": "सभी को म्यूट करें",
|
||||
"domuteVideo": "कैमरा अक्षम करें",
|
||||
"domuteVideoOfOthers": "अन्य सभी के लिए कैमरा बंद करें",
|
||||
"flip": "उलटना",
|
||||
"grantModerator": "संचालक बनाएं",
|
||||
"hideSelfView": "स्वयं का दृश्य छिपाएँ",
|
||||
"flip": "Flip",
|
||||
"grantModerator": "Grant Moderator",
|
||||
"kick": "निकालें",
|
||||
"moderator": "संचालक",
|
||||
"moderator": "Moderator",
|
||||
"mute": "प्रतिभागी मौन है",
|
||||
"muted": "म्यूटेड",
|
||||
"remoteControl": "स्टार्ट / स्टॉप रिमोट कंट्रोल",
|
||||
@@ -1245,20 +958,8 @@
|
||||
"headerSubtitle": "सुरक्षित और उच्च गुणवत्ता बैठकें",
|
||||
"headerTitle": "जित्सी मीट",
|
||||
"info": "डायल-इन जानकारी",
|
||||
"jitsiOnMobile": "मोबाइल पर जित्सी – हमारे एप्लिकेशन डाउनलोड करें और कहीं से भी एक बैठक शुरू करें",
|
||||
"jitsiOnMobile": "मोबाइल पर Jitsi – हमारे एप्लिकेशन डाउनलोड करें और कहीं से भी एक बैठक शुरू करें",
|
||||
"join": "बनाये / जुड़े ",
|
||||
"logo": {
|
||||
"calendar": "कैलेंडर लोगो",
|
||||
"desktopPreviewThumbnail": "डेस्कटॉप प्रीव्यू थंबनेल",
|
||||
"googleLogo": "गूगल लोगो",
|
||||
"logoDeepLinking": "जित्सी मीट लोगो",
|
||||
"microsoftLogo": "माइक्रोसॉफ्ट लोगो",
|
||||
"policyLogo": "नीति लोगो"
|
||||
},
|
||||
"meetingsAccessibilityLabel": "मीटिंग्स",
|
||||
"mobileDownLoadLinkAndroid": "एंड्रॉइड के लिए मोबाइल ऐप डाउनलोड करें",
|
||||
"mobileDownLoadLinkFDroid": "F-Droid के लिए मोबाइल ऐप डाउनलोड करें",
|
||||
"mobileDownLoadLinkIos": "iOS के लिए मोबाइल ऐप डाउनलोड करें",
|
||||
"moderatedMessage": "Or <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">book a meeting URL</a> in advance where you are the only moderator.",
|
||||
"privacy": "गोपनीयता",
|
||||
"recentList": "हाल का",
|
||||
@@ -1271,13 +972,6 @@
|
||||
"sendFeedback": "फ़ीडबैक भेजें",
|
||||
"startMeeting": "मीटिंग प्रारंभ करें",
|
||||
"terms": "शर्तें",
|
||||
"title": "सुरक्षित, पूरी तरह से चित्रित, और पूरी तरह से मुक्त वीडियो कॉन्फ्रेंसिंग",
|
||||
"upcomingMeetings": "आपकी आगामी मीटिंग्स"
|
||||
},
|
||||
"whiteboard": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "व्हाइटबोर्ड"
|
||||
},
|
||||
"screenTitle": "व्हाइटबोर्ड"
|
||||
"title": "सुरक्षित, पूरी तरह से चित्रित, और पूरी तरह से मुक्त वीडियो कॉन्फ्रेंसिंग"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"addPeople": {
|
||||
"accessibilityLabel": {
|
||||
"meetingLink": "Meeting-link: {{url}}"
|
||||
"meetingLink": "Meeting link: {{url}}"
|
||||
},
|
||||
"add": "Uitnodigen",
|
||||
"addContacts": "Nodig uw contacten uit",
|
||||
@@ -126,7 +126,7 @@
|
||||
"messagebox": "Typ een bericht",
|
||||
"newMessages": "Nieuwe berichten",
|
||||
"nickname": {
|
||||
"featureChat": "gesprek",
|
||||
"featureChat": "chat",
|
||||
"featureClosedCaptions": "ondertiteling",
|
||||
"featureFileSharing": "bestandsdeling",
|
||||
"featurePolls": "peilingen",
|
||||
@@ -375,7 +375,7 @@
|
||||
"kickTitle": "Oei! {{participantDisplayName}} heeft u uit de vergadering verwijderd",
|
||||
"learnMore": "meer informatie",
|
||||
"linkMeeting": "Vergadering koppelen",
|
||||
"linkMeetingTitle": "Vergadering koppelen aan Salesforce",
|
||||
"linkMeetingTitle": "Vergadering koppelen aan Verkoop",
|
||||
"liveStreaming": "Livestreamen",
|
||||
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Niet mogelijk tijdens opnemen",
|
||||
"localUserControls": "Lokale gebruikerscontrole",
|
||||
@@ -383,8 +383,6 @@
|
||||
"lockRoom": "$t(lockRoomPassword) voor vergadering toevoegen",
|
||||
"lockTitle": "Vergrendelen is mislukt",
|
||||
"login": "Inloggen",
|
||||
"loginFailed": "Inloggen is mislukt.",
|
||||
"loginOnResume": "Uw authenticatiesessie is verlopen. U moet opnieuw inloggen om de vergadering voort te zetten.",
|
||||
"loginQuestion": "Weet u zeker dat u wilt inloggen en de conferentie verlaten?",
|
||||
"logoutQuestion": "Weet u zeker dat u zich wilt afmelden en de conferentie wilt stoppen?",
|
||||
"logoutTitle": "Afmelden",
|
||||
@@ -470,7 +468,7 @@
|
||||
"screenSharingFailed": "Oeps! Er is iets misgegaan, de schermdeling kon niet worden gestart!",
|
||||
"screenSharingFailedTitle": "Schermdeling mislukt!",
|
||||
"screenSharingPermissionDeniedError": "Oeps! Er is iets misgegaan met uw toegangsrechten voor schermdeling. Herlaad en probeer opnieuw.",
|
||||
"searchInSalesforce": "Zoeken in Salesforce",
|
||||
"searchInSalesforce": "Zoeken bij Verkoop",
|
||||
"searchResults": "Zoekresultaten({{count}})",
|
||||
"searchResultsDetailsError": "Er ging iets mis bij het ophalen van eigenaargegevens.",
|
||||
"searchResultsError": "Er ging iets mis bij het ophalen van gegevens",
|
||||
@@ -506,7 +504,7 @@
|
||||
"sharedVideoDialogError": "Fout: Ongeldige of verboden URL",
|
||||
"sharedVideoLinkPlaceholder": "YouTube-link of directe video-link",
|
||||
"show": "Weergeven",
|
||||
"start": "Starten ",
|
||||
"start": "Start ",
|
||||
"startLiveStreaming": "Livestream starten",
|
||||
"startRecording": "Opname starten",
|
||||
"startRemoteControlErrorMessage": "Er is een fout opgetreden tijdens het starten van de sessie van extern beheer.",
|
||||
@@ -594,7 +592,6 @@
|
||||
"newFileNotification": "{{ participantName }} deelde '{{ fileName }}'",
|
||||
"removeFile": "Verwijderen",
|
||||
"removeFileSuccess": "Bestand met succes verwijderd",
|
||||
"uploadDisabled": "Bestanden uploaden is niet toegestaan. Vraag een moderator om toestemming voor die handeling.",
|
||||
"uploadFailedDescription": "Probeer het nog eens.",
|
||||
"uploadFailedTitle": "Upload is mislukt",
|
||||
"uploadFile": "Bestand delen"
|
||||
@@ -734,7 +731,7 @@
|
||||
},
|
||||
"lobby": {
|
||||
"backToKnockModeButton": "Vraag om deel te mogen nemen",
|
||||
"chat": "Gesprek",
|
||||
"chat": "Chat",
|
||||
"dialogTitle": "Wachtruimte-modus",
|
||||
"disableDialogContent": "Wachtruimte-modus is momenteel ingeschakeld. Deze functie zorgt ervoor dat ongewenste deelnemers niet aan uw vergadering kunnen deelnemen. Wilt u het uitschakelen?",
|
||||
"disableDialogSubmit": "Uitschakelen",
|
||||
@@ -850,12 +847,12 @@
|
||||
"leftOneMember": "{{name}} heeft de vergadering verlaten",
|
||||
"leftThreePlusMembers": "{{name}} en vele anderen hebben de vergadering verlaten",
|
||||
"leftTwoMembers": "{{first}} en {{second}} hebben de vergadering verlaten",
|
||||
"linkToSalesforce": "Koppelen aan Salesforce",
|
||||
"linkToSalesforceDescription": "U kunt de samenvatting van de vergadering koppelen aan een Salesforce-object.",
|
||||
"linkToSalesforceError": "Koppelen aan Salesforce is mislukt",
|
||||
"linkToSalesforce": "Koppelen aan Verkoop",
|
||||
"linkToSalesforceDescription": "U kunt de samenvatting van de vergadering koppelen aan een Verkoop-object.",
|
||||
"linkToSalesforceError": "Koppelen aan Verkoop is mislukt",
|
||||
"linkToSalesforceKey": "Deze vergadering koppelen",
|
||||
"linkToSalesforceProgress": "Vergadering koppelen aan Salesforce…",
|
||||
"linkToSalesforceSuccess": "De vergadering is gekoppeld aan Salesforce",
|
||||
"linkToSalesforceProgress": "Vergadering koppelen aan Verkoop…",
|
||||
"linkToSalesforceSuccess": "De vergadering is gekoppeld aan Verkoop",
|
||||
"localRecordingStarted": "{{name}} heeft een lokale opname gestart.",
|
||||
"localRecordingStopped": "{{name}} heeft een lokale opname gestopt.",
|
||||
"me": "Ik",
|
||||
@@ -891,7 +888,7 @@
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) verwijderd door een andere deelnemer",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) ingesteld door een ander deelnemer",
|
||||
"raiseHandAction": "Hand opsteken",
|
||||
"raisedHand": "Wil graag deelnemen.",
|
||||
"raisedHand": "Wilgraag deelnemen.",
|
||||
"raisedHands": "{{participantName}} en {{raisedHands}} meer mensen",
|
||||
"reactionSounds": "Geluiden uitschakelen",
|
||||
"reactionSoundsForAll": "Geluiden uitschakelen voor iedereen",
|
||||
@@ -1099,15 +1096,15 @@
|
||||
"setEmailLabel": "Gravatar e-mailadres",
|
||||
"title": "Profiel"
|
||||
},
|
||||
"raisedHand": "Wil graag spreken",
|
||||
"raisedHand": "Zou graag willen spreken",
|
||||
"raisedHandsLabel": "Aantal handen omhoog",
|
||||
"record": {
|
||||
"already": {
|
||||
"linked": "De vergadering is al gekoppeld aan dit Salesforce-object."
|
||||
"linked": "De vergadering is al gekoppeld aan dit Verkoop-object."
|
||||
},
|
||||
"type": {
|
||||
"account": "Account",
|
||||
"contact": "Contactpersoon",
|
||||
"contact": "Contact",
|
||||
"lead": "Potentiële klant",
|
||||
"opportunity": "Kans",
|
||||
"owner": "Eigenaar"
|
||||
@@ -1279,7 +1276,7 @@
|
||||
"displayEmotions": "Emoties weergeven",
|
||||
"fearful": "Angstig",
|
||||
"happy": "Blij",
|
||||
"hours": "{{count}}u",
|
||||
"hours": "{{count}}h",
|
||||
"labelTooltip": "Aantal deelnemers: {{count}}",
|
||||
"minutes": "{{count}}m",
|
||||
"name": "Naam",
|
||||
@@ -1350,7 +1347,7 @@
|
||||
"laugh": "Lachen",
|
||||
"leaveConference": "Vergadering verlaten",
|
||||
"like": "Duim omhoog",
|
||||
"linkToSalesforce": "Koppelen aan Salesforce",
|
||||
"linkToSalesforce": "Koppelen met Verkoop",
|
||||
"lobbyButton": "Wachtruimte in- of uitschakelen",
|
||||
"localRecording": "Besturingselementen voor lokale opname in- of uitschakelen",
|
||||
"lockRoom": "Wachtwoord voor vergadering in- of uitschakelen",
|
||||
@@ -1457,7 +1454,7 @@
|
||||
"leaveBreakoutRoom": "Aparte vergaderruimte verlaten",
|
||||
"leaveConference": "Vergadering verlaten",
|
||||
"like": "Duim omhoog",
|
||||
"linkToSalesforce": "Koppelen aan Salesforce",
|
||||
"linkToSalesforce": "Koppelen aan Verkoop",
|
||||
"lobbyButtonDisable": "Wachtruimte uitschakelen",
|
||||
"lobbyButtonEnable": "Wachtruimte inschakelen",
|
||||
"login": "Aanmelden",
|
||||
@@ -1633,30 +1630,30 @@
|
||||
"chatIndicator": "(toeschouwer)",
|
||||
"joinMeeting": {
|
||||
"description": "U bent momenteel een toeschouwer bij deze conferentie.",
|
||||
"raiseHand": "Hand opsteken",
|
||||
"title": "Deelnemen aan vergadering",
|
||||
"wishToSpeak": "Als u wilt spreken, steek dan hieronder uw hand op en wacht op goedkeuring van de moderator."
|
||||
"raiseHand": "Raise your hand",
|
||||
"title": "Joining meeting",
|
||||
"wishToSpeak": "If you wish to speak, please raise your hand below and wait for the moderator's approval."
|
||||
},
|
||||
"labelTooltip": "Aantal toeschouwers: {{count}}",
|
||||
"labelTooltip": "Number of viewers: {{count}}",
|
||||
"notification": {
|
||||
"demoteDescription": "Hierheen gestuurd door {{actor}}, steek uw hand op om deel te nemen",
|
||||
"noMainParticipantsDescription": "Een deelnemer moet de vergadering starten. Probeer het over een tijdje opnieuw.",
|
||||
"noMainParticipantsTitle": "Deze vergadering is nog niet begonnen.",
|
||||
"noVisitorLobby": "U kunt niet deelnemen terwijl er een wachtruimte is ingeschakeld voor de vergadering.",
|
||||
"notAllowedPromotion": "Een deelnemer moet eerst uw verzoek toestaan.",
|
||||
"requestToJoin": "Hand opgestoken",
|
||||
"requestToJoinDescription": "Uw verzoek is naar de moderators gestuurd. Even geduld!",
|
||||
"title": "U bent een toeschouwer in de vergadering"
|
||||
"demoteDescription": "Sent here by {{actor}}, raise your hand to participate",
|
||||
"noMainParticipantsDescription": "A participant needs to start the meeting. Please try again in a bit.",
|
||||
"noMainParticipantsTitle": "This meeting hasn't started yet.",
|
||||
"noVisitorLobby": "You cannot join while there is a lobby enabled for the meeting.",
|
||||
"notAllowedPromotion": "A participant needs to allow your request first.",
|
||||
"requestToJoin": "Hand Raised",
|
||||
"requestToJoinDescription": "Your request was sent to the moderators. Hang tight!",
|
||||
"title": "You are a viewer in the meeting"
|
||||
},
|
||||
"waitingMessage": "U zult deelnemen aan de vergadering zodra deze live is!"
|
||||
"waitingMessage": "You'll join the meeting as soon as it is live!"
|
||||
},
|
||||
"volumeSlider": "Volume-schuifknop",
|
||||
"volumeSlider": "Volume slider",
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
"join": "Tik om deel te nemen",
|
||||
"roomname": "Voer naam van ruimte in"
|
||||
},
|
||||
"addMeetingName": "Geef de vergadering een naam",
|
||||
"addMeetingName": "Add Meeting name",
|
||||
"appDescription": "U kunt nu videochatten met het gehele team. Nodig uit wie u maar wilt. {{app}} is een volledig versleutelde, 100% open-source oplossing voor videovergaderingen, die u wanneer u maar wilt gratis kunt gebruiken — zonder dat u een account nodig hebt.",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "Spraak",
|
||||
@@ -1676,16 +1673,16 @@
|
||||
"join": "AANMAKEN / DEELNEMEN",
|
||||
"logo": {
|
||||
"calendar": "Agenda logo",
|
||||
"desktopPreviewThumbnail": "Bureaubladvoorbeeld miniatuur",
|
||||
"desktopPreviewThumbnail": "Desktop preview thumbnail",
|
||||
"googleLogo": "Google logo",
|
||||
"logoDeepLinking": "Jitsi meet logo",
|
||||
"microsoftLogo": "Microsoft logo",
|
||||
"policyLogo": "Policy logo"
|
||||
},
|
||||
"meetingsAccessibilityLabel": "Vergaderingen",
|
||||
"mobileDownLoadLinkAndroid": "Download mobiele app voor Android",
|
||||
"mobileDownLoadLinkFDroid": "Download mobiele app voor F-Droid",
|
||||
"mobileDownLoadLinkIos": "Download mobiele app voor iOS",
|
||||
"meetingsAccessibilityLabel": "Meetings",
|
||||
"mobileDownLoadLinkAndroid": "Download mobile app for Android",
|
||||
"mobileDownLoadLinkFDroid": "Download mobile app for F-Droid",
|
||||
"mobileDownLoadLinkIos": "Download mobile app for iOS",
|
||||
"moderatedMessage": "Of <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">boek een vergadering URL</a> van tevoren waar u de enige moderator bent.",
|
||||
"privacy": "Privacy",
|
||||
"recentList": "Recent",
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"hideParticipantList": "Fshihe listën e pjesëmarrësve",
|
||||
"mainRoom": "Dhoma kryesore",
|
||||
"notifications": {
|
||||
"joined": "Po hyhet te dhomë aneks konsultimesh “{{name}}”",
|
||||
"joined": "Po hyhet te dhomë aneks konsultimesh \"{{name}}\"",
|
||||
"joinedMainRoom": "Po hyhet te dhoma kryesore",
|
||||
"joinedTitle": "Dhoma Aneks Konsultimesh"
|
||||
},
|
||||
@@ -109,15 +109,9 @@
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"disabled": "Dërgimi i mesazheve të fjalosjes është çaktivizuar.",
|
||||
"enter": "Hyni në dhomë",
|
||||
"enter": "Hyni në fjalosje",
|
||||
"error": "Gabim: mesazhi juaj s’u dërgua. Arsye: {{error}}",
|
||||
"everyone": "Gjithkush",
|
||||
"fieldPlaceHolder": "Aa",
|
||||
"fileAccessibleTitle": "{{user}} ngarkoi një kartelë",
|
||||
"fileAccessibleTitleMe": "me ngarkoi një kartelë",
|
||||
"fileDeleted": "U fshi një kartelë",
|
||||
"guestsChatIndicator": "(guest)",
|
||||
"fieldPlaceHolder": "Shtypni këtu mesazhin tuaj",
|
||||
"lobbyChatMessageTo": "Mesazh fjalosjeje në holl për {{recipient}}",
|
||||
"message": "Mesazh",
|
||||
"messageAccessibleTitle": "{{user}} thotë:",
|
||||
@@ -126,20 +120,9 @@
|
||||
"messagebox": "Shtypni një mesazh",
|
||||
"newMessages": "Mesazhe të rinj",
|
||||
"nickname": {
|
||||
"featureChat": "fjalosje",
|
||||
"featureClosedCaptions": "titra të mbyllur",
|
||||
"featureFileSharing": "dhënie kartelash",
|
||||
"featurePolls": "pyetësorë",
|
||||
"popover": "Zgjidhni një nofkë",
|
||||
"title": "Që të përdorni fjalosjen, jepni një nofkë",
|
||||
"titleWith1Features": "Që të përdorni {{feature1}}, jepni një nofkë",
|
||||
"titleWith2Features": "Që të përdorni {{feature1}} dhe {{feature2}}, jepni një nofkë",
|
||||
"titleWith3Features": "Që të përdorni {{feature1}}, {{feature2}} dhe {{feature3}}, jepni një nofkë",
|
||||
"titleWith4Features": "Që të përdorni {{feature1}}, {{feature2}}, {{feature3}} dhe {{feature4}}, jepni një nofkë",
|
||||
"titleWithCC": "Që të përdorni fjalosje dhe titra të mbyllur, jepni një nofkë",
|
||||
"titleWithPolls": "Që të përdorni fjalosjen dhe pyetësorë, jepni një nofkë",
|
||||
"titleWithPollsAndCC": "Që të përdorni fjalosje, pyetësorë dhe titra të mbyllur, jepni një nofkë",
|
||||
"titleWithPollsAndCCAndFileSharing": "Që të përdorni fjalosje, pyetësorë, titra të mbyllur dhe kartela, jepni një nofkë"
|
||||
"titleWithPolls": "Që të përdorni fjalosjen dhe pyetësorë, jepni një nofkë"
|
||||
},
|
||||
"noMessagesMessage": "Te takimi s’ka ende mesazhe. Nisni një bisedë këtu!",
|
||||
"privateNotice": "Mesazh privat për {{recipient}}",
|
||||
@@ -148,27 +131,18 @@
|
||||
"systemDisplayName": "Sistem",
|
||||
"tabs": {
|
||||
"chat": "Fjalosje",
|
||||
"closedCaptions": "CC",
|
||||
"fileSharing": "Kartela",
|
||||
"polls": "Pyetësorë"
|
||||
},
|
||||
"title": "Fjalosje",
|
||||
"titleWithCC": "CC",
|
||||
"titleWithFeatures": "Bisedoni dhe",
|
||||
"titleWithFileSharing": "Kartela",
|
||||
"titleWithPolls": "Pyetësorë",
|
||||
"titleWithPolls": "Fjalosje dhe Pyetësorë",
|
||||
"you": "ju"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"buttonText": "Instaloni Zgjerimin për Chrome",
|
||||
"buttonTextEdge": "Instaloni Zgjerimin për Edge",
|
||||
"buttonTextEdge": "Instaloni Zgjerimin Edge",
|
||||
"close": "Mbylle",
|
||||
"dontShowAgain": "Mos ma shfaq sërish këtë",
|
||||
"installExtensionText": "Instaloni zgjerimin për integrim me Google Calendar dhe Office 365"
|
||||
},
|
||||
"closedCaptionsTab": {
|
||||
"emptyState": "Lënda e titrave të mbyllur do të jetë e përdorshme sapo një moderator t’i fillojë ato",
|
||||
"startClosedCaptionsButton": "Nis titra të mbyllur"
|
||||
"installExtensionText": "Instaloni zgjerimin për integrim të Google Calendar-it dhe Office 365-s"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "Po ju lidhim me takimin tuaj…"
|
||||
@@ -227,9 +201,6 @@
|
||||
"video_ssrc": "Video SSRC:",
|
||||
"yes": "po"
|
||||
},
|
||||
"customPanel": {
|
||||
"close": "Mbylle"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Më herët",
|
||||
"today": "Sot",
|
||||
@@ -252,7 +223,7 @@
|
||||
"noDesktopApp": "S’e keni aplikacionin?",
|
||||
"noMobileApp": "S’e keni aplikacionin?",
|
||||
"or": "OR",
|
||||
"termsAndConditions": "Duke vazhduar, pajtoheni me <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>terma & kushte.</a>",
|
||||
"termsAndConditions": "Duke vazhduar, pajtoheni me <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>terms & conditions.</a> tona",
|
||||
"title": "Po niset takimi juaj në {{app}}…",
|
||||
"titleNew": "Po niset takimi juaj…",
|
||||
"tryAgainButton": "Riprovoni në desktop",
|
||||
@@ -292,8 +263,7 @@
|
||||
"Remove": "Hiqe",
|
||||
"Share": "Ndaje",
|
||||
"Submit": "Parashtroje",
|
||||
"Understand": "E kuptoj, mbamëni të heshtuar për tani",
|
||||
"UnderstandAndUnmute": "E kuptoj, ju lutem, hiqmani heshtimin",
|
||||
"WaitForHostMsg": "Konferenca s’ka nisur, ngaqë s’ka mbërritur ende ndonjë moderator. Nëse dëshironi të bëheni një moderator, ju lutemi, bëni hyrjen. Përndryshe, ju lutemi, pritni.",
|
||||
"WaitForHostNoAuthMsg": "Konferenca s’ka nisur, ngaqë s’ka mbërritur ende ndonjë moderator. Përndryshe, ju lutemi, pritni.",
|
||||
"WaitingForHostButton": "Prit për moderator",
|
||||
"WaitingForHostTitle": "Po pritet për një moderator…",
|
||||
@@ -315,12 +285,6 @@
|
||||
"alreadySharedVideoTitle": "Lejohet vetëm një ndarje videoje me të tjerët në herë",
|
||||
"applicationWindow": "Dritare aplikacioni",
|
||||
"authenticationRequired": "Lypset mirëfilltësim",
|
||||
"cameraCaptureDialog": {
|
||||
"description": "Bëni dhe dërgoni një foto duke përdorur kamerën e celularit tuaj",
|
||||
"ok": "Hape kamerën",
|
||||
"reject": "Jo tani",
|
||||
"title": "Bëni një foto"
|
||||
},
|
||||
"cameraConstraintFailedError": "Kamera juaj s’plotëson disa nga kufizimet e domosdoshme.",
|
||||
"cameraNotFoundError": "S’u gjet kamera.",
|
||||
"cameraNotSendingData": "S’qemë në gjendje të përdornim kamerën tuaj. Ju lutemi, kontrolloni se mos pajisjen po e përdor një tjetër aplikacion, përzgjidhni pajisje tjetër që nga menuja e rregullimeve, ose provoni të ringarkoni aplikacionin.",
|
||||
@@ -335,7 +299,6 @@
|
||||
"conferenceReloadMsg": "Po provojmë ta ndreqim këtë gjë. Rilidhje pas {{seconds}} sekondash…",
|
||||
"conferenceReloadTitle": "Mjerisht, diç shkoi ters.",
|
||||
"confirm": "Ripohojeni",
|
||||
"confirmBack": "Mbrapsht",
|
||||
"confirmNo": "Jo",
|
||||
"confirmYes": "Po",
|
||||
"connectError": "Hëm! Diç shkoi ters dhe s’mundëm të lidhemi dot me konferencën.",
|
||||
@@ -345,7 +308,7 @@
|
||||
"copied": "U kopjua",
|
||||
"copy": "Kopjoje",
|
||||
"demoteParticipantDialog": "Jeni i sigurt se doni ta kaloni këtë pjesëmarrës si vizitor?",
|
||||
"demoteParticipantTitle": "Kaloje si parës",
|
||||
"demoteParticipantTitle": "Kaloje si vizitor",
|
||||
"dismiss": "Hidhe tej",
|
||||
"displayNameRequired": "Njatjeta! Cili është emri juaj?",
|
||||
"done": "U bë",
|
||||
@@ -371,20 +334,16 @@
|
||||
"kickParticipantButton": "Përzëre",
|
||||
"kickParticipantDialog": "Jeni i sigurt se doni të përzihet ky pjesëmarrës?",
|
||||
"kickParticipantTitle": "Të përzihet ky pjesëmarrës?",
|
||||
"kickSystemTitle": "Ooh! U përzutë nga takimi",
|
||||
"kickTitle": "Ooh! {{participantDisplayName}} ju përzuri nga takimi",
|
||||
"learnMore": "mësoni më tepër",
|
||||
"linkMeeting": "Lidheni takimin",
|
||||
"linkMeetingTitle": "Lidheni takimin me Salesforce",
|
||||
"liveStreaming": "Transmetim i Drejtpërdrejtë",
|
||||
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Jo e mundshme, kur është aktiv regjistrimi",
|
||||
"localUserControls": "Kontrolle vendore përdoruesi",
|
||||
"lockMessage": "S’u arrit të kyçej konferenca.",
|
||||
"lockRoom": "Shtoni takim $t(lockRoomPassword)",
|
||||
"lockRoom": "Shtoni takim $t(lockRoomPasswordUppercase)",
|
||||
"lockTitle": "Kyçja dështoi",
|
||||
"login": "Hyrje",
|
||||
"loginFailed": "Hyrja dështoi.",
|
||||
"loginOnResume": "Sesioni juaj me mirëfilltësim ka skaduar. Lypset të bëni sërish hyrjen, që të vazhdoni takimin.",
|
||||
"loginQuestion": "Jeni i sigurt se doni të hyhet dhe të braktiset konferenca?",
|
||||
"logoutQuestion": "Jeni i sigurt se doni të dilet dhe të braktiset konferenca?",
|
||||
"logoutTitle": "Dalje",
|
||||
@@ -397,35 +356,23 @@
|
||||
"micPermissionDeniedError": "S’keni akorduar leje për t’u përdorur mikrofoni juaj. Mundeni prapëseprapë të merrni pjesë te konferenca, por të tjerët s’do t’ju dëgjojnë. Përdorni butonin e kamerës, te shtylla e adresave, për ta ndrequr këtë gjë.",
|
||||
"micTimeoutError": "S’u nis dot burim audio. Ndodhi mbarim kohe!",
|
||||
"micUnknownError": "S’përdoret dot mikrofoni, për një arsye të panjohur.",
|
||||
"moderationAudioLabel": "Lejoji jo-moderatorët të heqin heshtimin e vetes",
|
||||
"moderationDesktopLabel": "Lejoji jo-moderatorët të ndajnë me të tjerët ekranin e tyre",
|
||||
"moderationVideoLabel": "Lejoji jo-moderatorët të nisin videon e tyre",
|
||||
"moderationAudioLabel": "Lejoji pjesëmarrësit të heqin heshtimin e vetes",
|
||||
"moderationVideoLabel": "Lejoji pjesëmarrësit të nisin videon e tyre",
|
||||
"muteEveryoneDialog": "Pjesëmarrësit mund të heqin kurdo heshtimin e veten.",
|
||||
"muteEveryoneDialogModerationOn": "Pjesëmarrësit mund të dërgojnë kurdo kërkesë për të folur.",
|
||||
"muteEveryoneElseDialog": "Po i heshtuat, s’do të jeni në gjendje t’u hiqni heshtimin, por ata munden të heqin kurdo heshtimin për veten.",
|
||||
"muteEveryoneElseTitle": "Të heshtohet gjithkush, hiq {{whom}}?",
|
||||
"muteEveryoneElsesDesktopDialog": "Pasi ndarja me të tjerë të jetë ndalur, s’do të jeni në gjendje ta rinisni, por ata mund ta bëjnë në çfarëdo kohe.",
|
||||
"muteEveryoneElsesDesktopTitle": "Të ndalet për gjithkënd ndarja e ekranit, hiq {{whom}}?",
|
||||
"muteEveryoneElsesVideoDialog": "Pasi të jetë çaktivizuar kamera, s’do të jeni në gjendje ta riaktivizoni, por ata munden ta riaktivizojnë kurdo.",
|
||||
"muteEveryoneElsesVideoTitle": "Të ndalet videoja e gjithkujt, hiq {{whom}}?",
|
||||
"muteEveryoneSelf": "ju",
|
||||
"muteEveryoneStartMuted": "Tani e tutje, gjithkush fillon i heshtuar",
|
||||
"muteEveryoneTitle": "Të heshtohet gjithkush?",
|
||||
"muteEveryonesDesktopDialog": "Pjesëmarrësit mund të ndajnë me të tjerët ekranin e tyre kurdo.",
|
||||
"muteEveryonesDesktopDialogModerationOn": "Pjesëmarrësit mund të dërgojnë kurdo një kërkesë për ndarjen e ekranit të tyre.",
|
||||
"muteEveryonesDesktopTitle": "Të ndalet ndarja e ekranit për gjithkënd?",
|
||||
"muteEveryonesVideoDialog": "Pjesëmarrësit mund të aktivizojnë videon e tyre kurdo.",
|
||||
"muteEveryonesVideoDialogModerationOn": "Pjesëmarrësit mund të dërgojnë kurdo një kërkesë për të aktivizuar videon e tyre.",
|
||||
"muteEveryonesVideoDialogModerationOn": "Pjesëmarrësit mund të dërgojnë kurdo kërkesë për aktivizimin e videos së tyre.",
|
||||
"muteEveryonesVideoDialogOk": "Çaktivizoje",
|
||||
"muteEveryonesVideoTitle": "Të ndalet videoja e gjithkujt?",
|
||||
"muteParticipantBody": "S’do jeni në gjendje të hiqni heshtimin për ta, por ata munden kurdo ta heqin për veten.",
|
||||
"muteParticipantButton": "Heshtoje",
|
||||
"muteParticipantsDesktopBody": "S’do të jeni në gjendje të nisni ndarjen e ekranit të tyre me të tjerë, por ata munden kurdo t ta bëjnë.",
|
||||
"muteParticipantsDesktopBodyModerationOn": "S’do të jeni në gjendje të nisni tregimin e ekrani t të tyre dhe as ata s’do të jenë në gjendje.",
|
||||
"muteParticipantsDesktopButton": "Ndale tregimin e ekranit",
|
||||
"muteParticipantsDesktopDialog": "Jeni i sigurt se doni të çaktivizoni tregimin e ekranit të këtij pjesëmarrësi? S’do të jeni në gjendje ta rinisni, por ai mund të bëjë kurdo.",
|
||||
"muteParticipantsDesktopDialogModerationOn": "Jeni i sigurt se doni të çaktivizoni tregimin e ekranit të këtij pjesëmarrësi? S’do të jeni në gjendje ta rinisni dhe as ata s’do të jenë në gjendje.",
|
||||
"muteParticipantsDesktopTitle": "Të çaktivizohet tregim ekrani i këtij pjesëmarrësit?",
|
||||
"muteParticipantsVideoBody": "S’do të jeni në gjendje të riaktivizoni kamerën e tyre, por ata munden kurdo ta riaktivizojnë për veten.",
|
||||
"muteParticipantsVideoBodyModerationOn": "S’do të jeni në gjendje të riaktivizoni kamerën e tyre dhe as ata s’do të munden.",
|
||||
"muteParticipantsVideoButton": "Ndale videon",
|
||||
@@ -444,11 +391,7 @@
|
||||
"readMore": "më tepër",
|
||||
"recentlyUsedObjects": "Së fundi përdorët objekte",
|
||||
"recording": "Regjistrim",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Jo i mundshëm, kur ka aktiv një transmetim të drejtpërdrejtë",
|
||||
"recordingInProgressDescription": "Ky takim po regjistrohet dhe analizohet nga IA{{learnMore}}. Audioja dhe videoja juaj janë heshtuar. Nëse zgjidhni të hiqet heshtimi, pranimi juaj po regjistrohet.",
|
||||
"recordingInProgressDescriptionFirstHalf": "Ky takim po regjistrohet dhe analizohet nga IA",
|
||||
"recordingInProgressDescriptionSecondHalf": ". Audioja dhe videoja juaj janë heshtuar. Nëse zgjidhni të hiqet heshtimi, pranimi juaj po regjistrohet.",
|
||||
"recordingInProgressTitle": "Regjistrim në kryerje e sipër",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Jo i mundshëm kur ka aktiv një transmetim të drejtpërdrejtë",
|
||||
"rejoinNow": "Rihyni tani",
|
||||
"remoteControlAllowedMessage": "{{user}} pranoi kërkesën tuaj për kontroll së largëti!",
|
||||
"remoteControlDeniedMessage": "{{user}} hodhi poshtë kërkesën tuaj për kontroll së largëti!",
|
||||
@@ -485,7 +428,7 @@
|
||||
"sessTerminatedReason": "Takimi u përfundua",
|
||||
"sessionRestarted": "Thirrja rinisi për shkak të një problemi lidhjeje.",
|
||||
"shareAudio": "Vazhdoni",
|
||||
"shareAudioAltText": "që të ndani me të tjerë lëndën e dëshiruar, kaloni te “Skedë Shfletuesi”, përzgjidhni lëndën, aktivizoni shenjën për “ndani audio me të tjerë” dhe mandej klikoni butonin “ndaje me të tjerë”",
|
||||
"shareAudioAltText": "që të ndani me të tjerë lëndën e dëshiruar, kaloni te \"Skedë Shfletuesi\", përzgjidhni lëndën, aktivizoni shenjën për \"ndani audio me të tjerë\" dhe mandej klikoni butonin \"ndaje me të tjerë\"",
|
||||
"shareAudioTitle": "Si të ndahet audio me të tjerë",
|
||||
"shareAudioWarningD1": "lypset të ndalni tregim ekrani, para se të ndani audion tuaj me të tjerë.",
|
||||
"shareAudioWarningD2": "lypset të rinisni tregimin e ekranit tuaj dhe t’i vini shenjë mundësisë “ndani audio me të tjerë”.",
|
||||
@@ -538,7 +481,6 @@
|
||||
"tokenAuthFailedWithReasons": "Na ndjeni, nuk keni leje të merrni pjesë në këtë thirrje. Arsye e mundshme: {{reason}}",
|
||||
"tokenAuthUnsupported": "Nuk mbulohet URL token-i.",
|
||||
"transcribing": "Transkriptim",
|
||||
"unauthenticatedAccessDisabled": "Kjo thirrje lyp mirëfilltësim. Ju lutemi, bëni hyrjen, që të mund të vazhdoni.",
|
||||
"unlockRoom": "Hiq $t(lockRoomPassword) takimi",
|
||||
"user": "Përdorues",
|
||||
"userIdentifier": "Identifikues përdoruesi",
|
||||
@@ -551,10 +493,10 @@
|
||||
"viewUpgradeOptions": "Shihni mundësi përmirësimi",
|
||||
"viewUpgradeOptionsContent": "Që të përfitoni përdorim të pakufizuar veçorish me pagesë, të tilla si regjistrimi, transkriptime, RTMP Streaming & etj, duhet të përmirësoni planin tuaj.",
|
||||
"viewUpgradeOptionsTitle": "Zbuluat një veçori me pagesë!",
|
||||
"whiteboardLimitContent": "Na ndjeni, është mbërritur në kufi përdorues të njëkohshëm tabele.",
|
||||
"whiteboardLimitContent": "Na ndjeni, është mbërritur te kufiri i tabelave të njëkohshme.",
|
||||
"whiteboardLimitReference": "Për më tepër hollësi, ju lutemi, vizitoni",
|
||||
"whiteboardLimitReferenceUrl": "sajtin tonë",
|
||||
"whiteboardLimitTitle": "Përdorim tabele i kufizuar",
|
||||
"whiteboardLimitTitle": "Kufizim përdorimi tabele",
|
||||
"yourEntireScreen": "Krejt ekranin tuaj"
|
||||
},
|
||||
"documentSharing": {
|
||||
@@ -579,26 +521,6 @@
|
||||
"veryBad": "Shumë i Dobët",
|
||||
"veryGood": "Shumë i Mirë"
|
||||
},
|
||||
"fileSharing": {
|
||||
"downloadFailedDescription": "Ju lutemi, riprovoni.",
|
||||
"downloadFailedTitle": "Shkarkimi dështoi",
|
||||
"downloadFile": "Shkarkoje",
|
||||
"downloadStarted": "Filloi shkarkimi i kartelës",
|
||||
"dragAndDrop": "Tërhiqni dhe lini kartela këtu, ose kudo në ekran",
|
||||
"fileAlreadyUploaded": "Kartela është ngarkuar tashmë te ky takim.",
|
||||
"fileRemovedByOther": "Kartela juaj '{{ fileName }}' u hoq",
|
||||
"fileTooLargeDescription": "Ju lutemi, siguroni që kartela të mos tejkalojë {{ maxFileSize }}.",
|
||||
"fileTooLargeTitle": "Kartela e përzgjedhur është shumë e madhe",
|
||||
"fileUploadProgress": "Ecuri ngarkimi kartele",
|
||||
"fileUploadedSuccessfully": "Kartela u ngarkua me sukses",
|
||||
"newFileNotification": "{{ participantName }} ndau '{{ fileName }}' me të tjerë",
|
||||
"removeFile": "Hiqe",
|
||||
"removeFileSuccess": "Kartela u hoq me sukses",
|
||||
"uploadDisabled": "S’lejoheni të ngarkoni kartela. Kërkojini një moderatori të drejta lejimi të këtij veprimi.",
|
||||
"uploadFailedDescription": "Ju lutemi, riprovoni.",
|
||||
"uploadFailedTitle": "Ngarkimi dështoi",
|
||||
"uploadFile": "Ndani kartelë"
|
||||
},
|
||||
"filmstrip": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Miniatura videosh"
|
||||
@@ -705,7 +627,7 @@
|
||||
"errorAPI": "Ndodhi një gabim teksa hyhej te transmetimet tuaja YouTube. Ju lutemi, provoni të ribëni hyrjen.",
|
||||
"errorLiveStreamNotEnabled": "Transmetimi i Drejtpërdrejtë s’është i aktivizuar për {{email}}. Ju lutemi, aktivizoni transmetim të drejtpërdrejtë, ose hyni në një llogari me transmetim të drejtpërdrejtë të aktivizuar.",
|
||||
"expandedOff": "Transmetimi i drejtpërdrejtë u ndal",
|
||||
"expandedOn": "Takimi aktualisht po transmetohet drejtpërdrejt",
|
||||
"expandedOn": "Takimi po transmetohet aktualisht në YouTube.",
|
||||
"expandedPending": "Po fillohet transmetim i drejtpërdrejtë…",
|
||||
"failedToStart": "S’u arrit të fillohej Transmetim i Drejtpërdrejtë",
|
||||
"getStreamKeyManually": "S’qemë në gjendje të sillnim ndonjë transmetim të drejtpërdrejtë. Provoni të merrni kyçin tuaj për transmetim të drejtpërdrejtë nga YouTube-i.",
|
||||
@@ -767,8 +689,7 @@
|
||||
"notificationTitle": "Holl",
|
||||
"passwordJoinButton": "Hyni",
|
||||
"title": "Holl",
|
||||
"toggleLabel": "Aktivizoni hollin",
|
||||
"waitForModerator": "Konferenca s’ka nisur ende, ngaqë s’ka mbërritur ndonjë moderator. Nëse do të donit të bëheni një moderator, ju lutemi, bëni hyrjen në llogari. Përndryshe, ju lutemi, prisni."
|
||||
"toggleLabel": "Aktivizoni hollin"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -811,26 +732,21 @@
|
||||
"me": "unë",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "Cenueshmëri sigurie!",
|
||||
"allowAll": "Lejo Gjithçka",
|
||||
"allowAudio": "Lejo Audio",
|
||||
"allowDesktop": "Lejo tregim ekrani",
|
||||
"allowVideo": "Lejo Video",
|
||||
"allowAction": "Lejoje",
|
||||
"allowedUnmute": "Mund të hiqni heshtimin e mikrofonit tuaj, të nisni kamerën tuaj ose të tregoni ekranin tuaj.",
|
||||
"audioUnmuteBlockedDescription": "Veprimi i heqjes së heshtimit të mikrofonit është bllokuar përkohësisht për shkak kufizimesh të sistemit.",
|
||||
"audioUnmuteBlockedTitle": "Heqje heshtimi mikrofoni e bllokuar!",
|
||||
"chatMessages": "Mesazhe fjalosjeje",
|
||||
"connectedOneMember": "{{name}} nisi takimin",
|
||||
"connectedThreePlusMembers": "{{name}} dhe mjaft të tjerë hynë në takim",
|
||||
"connectedThreePlusMembers": "{{name}} dhe mjaft të tjerë tjerë hynë në takim",
|
||||
"connectedTwoMembers": "{{first}} dhe {{second}} tjetër hynë në takim",
|
||||
"connectionFailed": "Lidhja dështoi. Ju lutemi, riprovoni më vonë!",
|
||||
"dataChannelClosed": "Cilësia e videos mund të jetë dëmtuar",
|
||||
"dataChannelClosedDescription": "Kanali urë u shkëput, kështu që cilësia e videos është kufizuar te vlera më e ulët.",
|
||||
"dataChannelClosedDescriptionWithAudio": "Kanali urë është jashtë funksionimi, prandaj mund të ndodhin shkëputje te audioja dhe videoja.",
|
||||
"dataChannelClosedWithAudio": "Cilësia e audios dhe videos mund të jetë dëmtuar",
|
||||
"desktopMutedRemotelyTitle": "Tregimi i ekranit tuaj është ndalur nga {{participantDisplayName}}",
|
||||
"disabledIframe": "Trupëzimi është menduar vetëm për qëllime demonstrimi, ndaj kjo thirrje do të ndërpritet pas {{timeout}} minutash.",
|
||||
"disabledIframeSecondaryNative": "Trupëzimi i {{domain}} është menduar vetëm për qëllime demonstrimi, ndaj kjo thirrje do të ndërpritet pas {{timeout}} minutash.",
|
||||
"disabledIframeSecondaryWeb": "Trupëzimi i {{domain}} është menduar vetëm për qëllime demonstrimi, ndaj kjo thirrje do të ndërpritet pas {{timeout}} minutash. Ju lutemi, për trupëzime të njëmendta përdorni <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi si një Shërbim</a>!",
|
||||
"disabledIframeSecondary": "Trupëzimi i {{domain}} është menduar vetëm për qëllime demonstrimi, ndaj kjo thirrje do të ndërpritet pas {{timeout}} minutash. Ju lutemi, për trupëzime të njëmendta përdorni <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a>!",
|
||||
"disconnected": "u shkëput",
|
||||
"displayNotifications": "Shfaq njoftime për",
|
||||
"dontRemindMe": "Mos ma kujto",
|
||||
@@ -838,7 +754,7 @@
|
||||
"focusFail": "{{component}} jo i passhëm - riprovoni pas {{ms}} sekondash",
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "Njoftime",
|
||||
"hostAskedUnmute": "Moderatori do të donte të merrnit pjesë.",
|
||||
"hostAskedUnmute": "Moderatori do të donte të flisnit",
|
||||
"invalidTenant": "Qiramarrës i pavlefshëm",
|
||||
"invalidTenantHyphenDescription": "Qiramarrësi që po përdorni është i pavlefshëm (fillon, ose përfundon me '-').",
|
||||
"invalidTenantLengthDescription": "Qiramarrësi që po përdorni është shumë i gjatë.",
|
||||
@@ -873,31 +789,30 @@
|
||||
"moderator": "Tani jeni moderator",
|
||||
"muted": "E keni filluar bisedën të heshtur.",
|
||||
"mutedRemotelyDescription": "Mundeni përherë të hiqni heshtimin, kur të jeni gati për të folur. Ribëni heshtimin, kur të mbaroni, për të mbajtur zhurmat jashtë takimit.",
|
||||
"mutedRemotelyTitle": "Jeni heshtuar nga {{participantDisplayName}}",
|
||||
"mutedRemotelyTitle": "Jeni heshtuar nga {{moderator}}",
|
||||
"mutedTitle": "U heshtuat!",
|
||||
"newDeviceAction": "Përdore",
|
||||
"newDeviceAudioTitle": "U pikas pajisje audio e re",
|
||||
"newDeviceCameraTitle": "U pikas kamerë e re",
|
||||
"nextToSpeak": "Jeni pasuesi në radhë për folje",
|
||||
"noiseSuppressionDesktopAudioDescription": "Mbytja e zhurmave ekstra s’mund të aktivizohet teksa ndahet me të tjerët audioja e desktopit, ju lutemi, çaktivizojeni dhe riprovoni.",
|
||||
"noiseSuppressionFailedTitle": "S’u arrit të nisej mbytja e zhurmave ekstra",
|
||||
"noiseSuppressionStereoDescription": "Aktualisht nuk mbulohet mbytje zhurmash ekstra me audio stereo.",
|
||||
"noiseSuppressionDesktopAudioDescription": "Mbytja e zhurmave s’mund të aktivizohet teksa ndahet me të tjerët audioja e desktopit, ju lutemi, çaktivizojeni dhe riprovoni.",
|
||||
"noiseSuppressionFailedTitle": "S’u arrit të nisej mbytja e zhurmave",
|
||||
"noiseSuppressionStereoDescription": "Aktualisht nuk mbulohet mbytje zhurmash audioje stereo.",
|
||||
"oldElectronClientDescription1": "Duket se përdorni një version të vjetër të klientit Jitsi Meet, i cili ka cenueshmëri të ditura sigurie. Ju lutemi, siguroni përditësimin me ",
|
||||
"oldElectronClientDescription2": "montimin tonë më të ri",
|
||||
"oldElectronClientDescription3": " që tani!",
|
||||
"openChat": "Hapni fjalosjen",
|
||||
"participantWantsToJoin": "Dëshiron të hyjë në takim",
|
||||
"participantsWantToJoin": "Dëshirojnë të hyjnë në takim",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) u hoq nga një tjetër pjesëmarrës",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) u caktua nga një tjetër pjesëmarrës",
|
||||
"raiseHandAction": "Ngrini dorën",
|
||||
"raisedHand": "Do të donte të merrte pjesë.",
|
||||
"raisedHand": "Do të donte të fliste.",
|
||||
"raisedHands": "{{participantName}} dhe {{raisedHands}} persona të tjerë",
|
||||
"reactionSounds": "Çaktivizo tingujt",
|
||||
"reactionSoundsForAll": "Çaktivizoji tingujt për të tërë",
|
||||
"screenShareNoAudio": "Kutizës për ndarje audioje me të tjerë s’i është vënë shenjë te skena e përzgjedhjes së dritares.",
|
||||
"screenShareNoAudioTitle": "S’u nda dot me të tjerë audioja e sistemit!",
|
||||
"screenSharingAudioOnlyDescription": "Ju lutemi, kini parasysh se duke dhënë ekranin tuaj, ndikoni te mënyra “Punimi më i mirë” dhe do të përdorni më tepër gjerësi bande.",
|
||||
"screenSharingAudioOnlyDescription": "Ju lutemi, kini parasysh se duke dhënë ekranin tuaj, ndikoni te mënyra “Punimi më i mirë” dhe do të përdorni më tepër gjerësi bande",
|
||||
"screenSharingAudioOnlyTitle": "Mënyra “”Punimi më i mirë",
|
||||
"selfViewTitle": "Mundeni përherë të hiqni fshehjen e pamjes së vetes, që nga rregullimet",
|
||||
"somebody": "Dikush",
|
||||
@@ -908,18 +823,16 @@
|
||||
"suggestRecordingAction": "Niseni",
|
||||
"suggestRecordingDescription": "Do të donit të nisej një regjistrim?",
|
||||
"suggestRecordingTitle": "Regjistroje këtë takim",
|
||||
"unmute": "Çheshtoje Audion",
|
||||
"unmuteScreen": "Fillo tregim ekrani",
|
||||
"unmuteVideo": "Hiqe Heshtimin e Videos",
|
||||
"unmute": "Çheshtoje",
|
||||
"videoMutedRemotelyDescription": "Mundeni përherë ta rihapni.",
|
||||
"videoMutedRemotelyTitle": "Videoja juaj u mbyll nga {{participantDisplayName}}",
|
||||
"videoMutedRemotelyTitle": "Videoja juaj u mbyll nga {{moderator}}",
|
||||
"videoUnmuteBlockedDescription": "Heqja e heshtimit të kamerës dhe veprimi i tregimit të desktopit janë bllokuar përkohësisht për shkak kufizimesh të sistemit.",
|
||||
"videoUnmuteBlockedTitle": "Heqja e heshtimit të kamerës dhe tregimi i desktopit janë bllokuar!",
|
||||
"viewLobby": "Shihni hollin",
|
||||
"viewParticipants": "Shihni pjesëmarrësit",
|
||||
"viewVisitors": "Shihni vizitorët",
|
||||
"waitingParticipants": "{{waitingParticipants}} vetë",
|
||||
"waitingVisitors": "Parës që presin në radhë: {{waitingVisitors}}",
|
||||
"waitingVisitors": "Vizitorë që presin në radhë: {{waitingVisitors}}",
|
||||
"waitingVisitorsTitle": "Takimi s’ka filluar endeThe meeting is not live yet!",
|
||||
"whiteboardLimitDescription": "Ju lutemi, ruani ç’keni bërë, ngaqë së shpejti do të mbërrihet në kufi përdoruesi dhe tabela do të mbyllet.",
|
||||
"whiteboardLimitTitle": "Përdorim tabele"
|
||||
@@ -927,47 +840,39 @@
|
||||
"participantsPane": {
|
||||
"actions": {
|
||||
"admit": "Pranoje",
|
||||
"admitAll": "Admit all",
|
||||
"allow": "Lejoju jo-moderatorëve:",
|
||||
"allowDesktop": "Të lejojnë tregim ekranesh",
|
||||
"allowVideo": "Të lejojnë video",
|
||||
"askDesktop": "Të lejojnë tregim ekrani",
|
||||
"askUnmute": "Të kërkojnë çheshtim",
|
||||
"audioModeration": "Të heqin heshtim të vetes",
|
||||
"blockEveryoneMicCamera": "Të bllokojnë mikrofonin dhe kamerën e kujtdo",
|
||||
"admitAll": "Pranoji të tërë",
|
||||
"allow": "Lejoju pjesëmarrësve të:",
|
||||
"allowVideo": "Çaktivizoni videon",
|
||||
"askUnmute": "Kërkoni heqje heshtimi",
|
||||
"audioModeration": "Heqin heshtim të vetes",
|
||||
"blockEveryoneMicCamera": "Bllokoni mikrofonin dhe kamerën e gjithkujt",
|
||||
"breakoutRooms": "Dhoma anekse konsultimesh",
|
||||
"desktopModeration": "Të nisin tregim ekrani",
|
||||
"goLive": "Go live",
|
||||
"invite": "Të ftojnë dikë",
|
||||
"lowerAllHands": "Të ulin krejt duart",
|
||||
"lowerHand": "Të ulin dorën",
|
||||
"invite": "Ftoni Dikë",
|
||||
"lowerAllHands": "Uli krejt duart",
|
||||
"lowerHand": "Uleni dorën",
|
||||
"moreModerationActions": "Më tepër mundësi moderimi",
|
||||
"moreModerationControls": "Më tepër kontrolle moderimi",
|
||||
"moreParticipantOptions": "Më tepër mundësi pjesëmarrësi",
|
||||
"mute": "Të heshtojnë",
|
||||
"muteAll": "Të heshtojnë të tërë",
|
||||
"muteEveryoneElse": "Të heshtojnë gjithkënd tjetër",
|
||||
"reject": "Të hedhin poshtë",
|
||||
"stopDesktop": "Të ndalin tregim ekrani",
|
||||
"stopEveryonesDesktop": "Të ndalin tregim ekrani të kujtdo",
|
||||
"stopEveryonesVideo": "Të ndalin videon e gjithkujt",
|
||||
"stopVideo": "Të ndalin video",
|
||||
"unblockEveryoneMicCamera": "Të zhbllokojnë mikrofonin dhe kamerën e gjithkujt",
|
||||
"videoModeration": "Të nisin videon e tyre"
|
||||
"mute": "Heshtoje",
|
||||
"muteAll": "Heshtoji të tërë",
|
||||
"muteEveryoneElse": "Heshto gjithkënd tjetër",
|
||||
"reject": "Hidhe poshtë",
|
||||
"stopEveryonesVideo": "Ndal videon e gjithkujt",
|
||||
"stopVideo": "Ndale videon",
|
||||
"unblockEveryoneMicCamera": "Zhblloko mikrofonin dhe kamerën e gjithkujt",
|
||||
"videoModeration": "Nisin videon e vetes"
|
||||
},
|
||||
"close": "Mbylle",
|
||||
"headings": {
|
||||
"lobby": "Holl ({{count}})",
|
||||
"participantsList": "Pjesëmarrës takimi ({{count}})",
|
||||
"viewerRequests": "Kërkesa parësish {{count}}",
|
||||
"visitorInQueue": " (në radhë {{count}})",
|
||||
"visitorRequests": " (kërkesa {{count}})",
|
||||
"visitors": "Parës {{count}}",
|
||||
"visitorsList": "Parës ({{count}})",
|
||||
"lobby": "Holli ({{count}})",
|
||||
"participantsList": "Pjesëmarrës në takim ({{count}})",
|
||||
"visitorInQueue": " (në pritje {{count}})",
|
||||
"visitorRequests": " (requests {{count}})",
|
||||
"visitors": "Vizitorë {{count}}",
|
||||
"waitingLobby": "Duke pritur në holl ({{count}})"
|
||||
},
|
||||
"search": "Kërkoni te pjesëmarrësit",
|
||||
"searchDescription": "Që të filtrohen pjesëmarrës, filloni të shtypni në tastierë",
|
||||
"title": "Pjesëmarrës"
|
||||
},
|
||||
"passwordDigitsOnly": "Deri në {{number}} shifra",
|
||||
@@ -984,9 +889,6 @@
|
||||
"by": "Nga {{ name }}",
|
||||
"closeButton": "Mbylle pyetësorin",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "Dërgo pyetësor"
|
||||
},
|
||||
"addOption": "Shtoni mundësi",
|
||||
"answerPlaceholder": "Mundësia {{index}}",
|
||||
"cancel": "Anuloje",
|
||||
@@ -995,10 +897,11 @@
|
||||
"pollQuestion": "Pyetje Pyetësori",
|
||||
"questionPlaceholder": "Bëni një pyetje",
|
||||
"removeOption": "Hiqe mundësinë",
|
||||
"save": "Ruaje"
|
||||
"save": "Ruaje",
|
||||
"send": "Dërgoje"
|
||||
},
|
||||
"errors": {
|
||||
"notUniqueOption": "Mundësitë duhet të jenë unike"
|
||||
"notUniqueOption": "Opsionet duhet të jenë unike"
|
||||
},
|
||||
"notification": {
|
||||
"description": "Që të votoni, hapni skedën e pyetësorëve",
|
||||
@@ -1006,7 +909,7 @@
|
||||
},
|
||||
"results": {
|
||||
"changeVote": "Ndryshoni votën",
|
||||
"empty": "Te takimi s’ka ende pyetësorë.",
|
||||
"empty": "Te takimi s’ka ende pyetësorë. Nisni një pyetësor këtu!",
|
||||
"hideDetailedResults": "Fshihi hollësitë",
|
||||
"showDetailedResults": "Shfaqi hollësitë",
|
||||
"vote": "Votoni"
|
||||
@@ -1035,11 +938,11 @@
|
||||
"audioHighQuality": "Presim që audioja juaj të jetë në cilësi të shkëlqyer.",
|
||||
"audioLowNoVideo": "Presim që audioja juaj të jetë në cilësi të ulët dhe s’do të ketë video.",
|
||||
"goodQuality": "Shkëlqyeshëm! Cilësia e medias tuaj do të jetë shumë e mirë.",
|
||||
"noMediaConnectivity": "S’gjetëm dot rrugë për të vendosur lidhje media për këtë provë. Zakonisht kjo shkaktohet nga një “firewall”, ose NAT.",
|
||||
"noMediaConnectivity": "S’gjetëm dot rrugë për të vendosur lidhje media për këtë provë. Zakonisht kjo shkaktohet nga një firewall ose NAT.",
|
||||
"noVideo": "Presim që cilësia e videos për ju të jetë për të vënë kujën.",
|
||||
"testFailed": "Prova e lidhjes hasi në probleme të papritura, por kjo mund të mos prekë funksionimin për ju.",
|
||||
"undetectable": "Nëse s’bëni dot ende thirrje në shfletues, rekomandojmë të siguroheni se altoparlantët, mikrofoni dhe kamera juaj janë ujdisur si duhet, se i keni akorduar shfletuesit tuaja të drejta të përdorë mikrofonin dhe kamerën tuaj, dhe se versioni i shfletuesit tuaj është i përditësuar. Nëse keni ende probleme me thirrje, duhet të lidheni me zhvilluesin e aplikacionit web.",
|
||||
"veryPoorConnection": "Presim që cilësia e thirrjes te ju të jetë për të vënë kujën.",
|
||||
"veryPoorConnection": "Presim që cilësia e thirrjes te ju të jetë për të vënë kujën",
|
||||
"videoFreezing": "Presim që videoja te ju të ngrijë, të bëhet e zezë dhe të shfaqet tërë piksela.",
|
||||
"videoHighQuality": "Presim që videoja te ju të ketë cilësi të mirë.",
|
||||
"videoLowQuality": "Presim që videoja te ju të ketë cilësi të ulët, për sa i takon shpejtësisë së kuadrove dhe qartësisë.",
|
||||
@@ -1066,7 +969,7 @@
|
||||
"joinWithoutAudio": "Merrni pjesë pa audio",
|
||||
"keyboardShortcuts": "Aktivizo shkurtore tastiere",
|
||||
"linkCopied": "Lidhja u kopjua në të papastër",
|
||||
"lookGood": "Pajisjet tuaja po punojnë si duhet",
|
||||
"lookGood": "Gjithçka po punon si duhet",
|
||||
"or": "ose",
|
||||
"premeeting": "Para takimit",
|
||||
"proceedAnyway": "Vazhdo, sido qoftë",
|
||||
@@ -1108,7 +1011,7 @@
|
||||
"type": {
|
||||
"account": "Llogari",
|
||||
"contact": "Kontakt",
|
||||
"lead": "",
|
||||
"lead": "Lead",
|
||||
"opportunity": "Mundësi",
|
||||
"owner": "Zotërues"
|
||||
}
|
||||
@@ -1123,7 +1026,7 @@
|
||||
"error": "Regjistrimi dështoi. Ju lutemi, riprovoni.",
|
||||
"errorFetchingLink": "Gabim në sjellje lidhje regjistrimi.",
|
||||
"expandedOff": "Regjistrimi u ndal",
|
||||
"expandedOn": "Takimi është aktualisht duke u regjistruar",
|
||||
"expandedOn": "Takimi është aktualisht duke u regjistruar.",
|
||||
"expandedPending": "Po niset regjistrimi…",
|
||||
"failedToStart": "S’u arrit të niset regjistrimi",
|
||||
"fileSharingdescription": "Ndajeni regjistrimin me pjesëmarrësit në takim",
|
||||
@@ -1196,7 +1099,6 @@
|
||||
"signedIn": "Aktualisht po shihen veprimtari kalendari për {{email}}. Klikoni mbi butonin Shkëputu më poshtë që të ndalni parjen e veprimtarive të kalendarit.",
|
||||
"title": "Kalendar"
|
||||
},
|
||||
"chatWithPermissions": "Çaktivizo fjalosje për jo-moderatorë",
|
||||
"desktopShareFramerate": "Shpejtësi kuadrosh për tregim desktopi",
|
||||
"desktopShareHighFpsWarning": "Një shpejtësi më e madhe për tregimin e ekranit mund të ketë ndikim në konsumin e gjerësisë së bandës. Që të hyjnë në fuqi rregullimet e reja, lypset të rinisni tregimin e ekranit.",
|
||||
"desktopShareWarning": "Që të hyjnë në fuqi rregullimet e reja, duhet të rinisni tregimin e ekranit.",
|
||||
@@ -1211,7 +1113,7 @@
|
||||
"microphones": "Mikrofona",
|
||||
"moderator": "Moderator",
|
||||
"moderatorOptions": "Mundësi moderatori",
|
||||
"more": "Të përgjithshme",
|
||||
"more": "Të përgjitshme",
|
||||
"name": "Emër",
|
||||
"noDevice": "Asnjë",
|
||||
"notifications": "Njoftime",
|
||||
@@ -1226,7 +1128,6 @@
|
||||
"selectMic": "Mikrofon",
|
||||
"selfView": "Parje e vetes",
|
||||
"shortcuts": "Shkurtore",
|
||||
"showSubtitlesOnStage": "Shfaq titra në skenë",
|
||||
"speakers": "Altoparlantë",
|
||||
"startAudioMuted": "Gjithkush fillon i heshtuar",
|
||||
"startReactionsMuted": "Heshto tinguj reagimesh për këdo",
|
||||
@@ -1280,13 +1181,11 @@
|
||||
"fearful": "I frikësuar",
|
||||
"happy": "I gëzuar",
|
||||
"hours": "{{count}}h",
|
||||
"labelTooltip": "Numër pjesëmarrësish: {{count}}",
|
||||
"minutes": "{{count}}m",
|
||||
"name": "Emër",
|
||||
"neutral": "Asnjanës",
|
||||
"sad": "I trishtuar",
|
||||
"search": "Kërko",
|
||||
"searchDescription": "Që të filtrohen pjesëmarrësh, filloni të shtypni",
|
||||
"searchHint": "Kërkoni për pjesëmarrës",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "Statistika Folësi",
|
||||
@@ -1321,25 +1220,23 @@
|
||||
"chat": "Hapni / Mbyllni fjalosje",
|
||||
"clap": "Duartrokitje",
|
||||
"closeChat": "Mbylle fjalosjen",
|
||||
"closeCustomPanel": "Mbylle",
|
||||
"closeMoreActions": "Mbyll menunë Më Tepër Veprime",
|
||||
"closeParticipantsPane": "Mbyll kuadratin pjesëmarrës",
|
||||
"closedCaptions": "Titra të mbyllur",
|
||||
"collapse": "Tkurre",
|
||||
"document": "Shfaq/Fshih dokument të ndarë",
|
||||
"documentClose": "Mbylle dokumentin e ndarë",
|
||||
"documentClose": "Mbyll dokument të ndarë",
|
||||
"documentOpen": "Hap dokument të ndarë",
|
||||
"download": "Shkarkoni aplikacionet tona",
|
||||
"embedMeeting": "Trupëzoni takimin",
|
||||
"endConference": "Përfundoje takimin për gjithkënd",
|
||||
"endConference": "Përfundoje takimin për të tërë",
|
||||
"enterFullScreen": "Shiheni sa krejt ekrani",
|
||||
"enterTileView": "Kaloni në pamje me kuadrate",
|
||||
"exitFullScreen": "Dilni nga mënyra “Sa krejt ekrani”",
|
||||
"exitTileView": "Dilni nga pamje me kuadrate",
|
||||
"enterTileView": "Kaloni nën mënyrën me kuadrate",
|
||||
"exitFullScreen": "Dil nga mënyra “Sa krejt ekrani”",
|
||||
"exitTileView": "Dil nga mënyra me kuadrate",
|
||||
"expand": "Zgjeroje",
|
||||
"feedback": "Lini përshtypje",
|
||||
"fullScreen": "Kaloni në/Dilni nga mënyra “Sa krejt ekrani”",
|
||||
"giphy": "Shfaqni/Fshihni menunë GIPHY",
|
||||
"fullScreen": "Kalo nën/Dil nga mënyra “Sa krejt ekrani”",
|
||||
"giphy": "Shfaq/fshih menunë GIPHY",
|
||||
"grantModerator": "Akordoji të Drejta Moderatori",
|
||||
"hangup": "Braktiseni takimin",
|
||||
"heading": "Panel",
|
||||
@@ -1349,7 +1246,7 @@
|
||||
"kick": "Përzëre pjesëmarrësin",
|
||||
"laugh": "E qeshur",
|
||||
"leaveConference": "Dilni nga takimi",
|
||||
"like": "",
|
||||
"like": "Thumbs Up",
|
||||
"linkToSalesforce": "Lidhje për te Salesforce",
|
||||
"lobbyButton": "Aktivizo/Çaktivizoni mënyrën holl",
|
||||
"localRecording": "Shfaq/Fshih kontrolle regjistrimi vendor",
|
||||
@@ -1365,9 +1262,9 @@
|
||||
"muteEveryoneElsesVideoStream": "Ndal videon e gjithkujt tjetër",
|
||||
"muteEveryonesVideoStream": "Ndal videon e gjithkujt",
|
||||
"muteGUMPending": "Po lidhet mikrofoni juaj",
|
||||
"noiseSuppression": "Mbytje zhurmash ekstra",
|
||||
"noiseSuppression": "Mbytje zhurmash",
|
||||
"openChat": "Hapni fjalosje",
|
||||
"participants": "Hapni kuadrat pjesëmarrësish. {{participantsCount}} pjesëmarrës",
|
||||
"participants": "Hapni kuadrat pjesëmarrësish",
|
||||
"pip": "Aktivizo/Çaktivizo mënyrën “Picture-in-Picture”",
|
||||
"privateMessage": "Dërgoni mesazh privat",
|
||||
"profile": "Përpunoni profilin tuaj",
|
||||
@@ -1403,87 +1300,68 @@
|
||||
"videounmute": "Nis kamerën"
|
||||
},
|
||||
"addPeople": "Shtoni persona te thirrja juaj",
|
||||
"advancedAudioSettings": {
|
||||
"aec": {
|
||||
"label": "Asgjësim jehone akustike"
|
||||
},
|
||||
"agc": {
|
||||
"label": "Kontroll i automatizuar gain-i"
|
||||
},
|
||||
"ns": {
|
||||
"label": "Mbytje zhurmash"
|
||||
},
|
||||
"stereo": {
|
||||
"label": "Stereo"
|
||||
}
|
||||
},
|
||||
"audioOnlyOff": "Aktivizo mënyrën gjerësi e ulët bande",
|
||||
"audioOnlyOn": "Çaktivizo mënyrën gjerësi e ulët bande",
|
||||
"audioOnlyOff": "Çaktivizo mënyrën “Sasi e ulët të dhënash trafiku”",
|
||||
"audioOnlyOn": "Aktivizo mënyrën “Sasi e ulët të dhënash trafiku”",
|
||||
"audioRoute": "Përzgjidhni pajisje zëri",
|
||||
"audioSettings": "Rregullime audio",
|
||||
"authenticate": "Bëni mirëfilltësimiin",
|
||||
"boo": "Buuu",
|
||||
"callQuality": "Administroni cilësi videosh",
|
||||
"chat": "Hapni / Mbyllni fjalosjen",
|
||||
"clap": "Duartrokitni",
|
||||
"closeChat": "Mbylleni fjalosjen",
|
||||
"closeCustomPanel": "Mbylle",
|
||||
"closeParticipantsPane": "Mbyllni kuadrat pjesëmarrësish",
|
||||
"closeReactionsMenu": "Mbyllni menu reagimesh",
|
||||
"closedCaptions": "Titra të mbyllur",
|
||||
"copilot": "Copilot",
|
||||
"disableNoiseSuppression": "Çaktivizoni asgjësim ekstra zhurmash",
|
||||
"audioSettings": "Rregullime për audion",
|
||||
"authenticate": "Bëni mirëfilltësimin",
|
||||
"boo": "Ya",
|
||||
"callQuality": "Administroni cilësi video",
|
||||
"chat": "Hap / Mbyll fjalosje",
|
||||
"clap": "Duartrokitje",
|
||||
"closeChat": "Mbyll fjalosjen",
|
||||
"closeParticipantsPane": "Mbylle kuadratin e pjesëmarrësve",
|
||||
"closeReactionsMenu": "Mbyll menu reagimesh",
|
||||
"disableNoiseSuppression": "Çaktivizo mbytje zhurmash",
|
||||
"disableReactionSounds": "Mund të çaktivizoni tinguj reagimesh për këtë takim",
|
||||
"documentClose": "Mbyll dokument të ndarë",
|
||||
"documentOpen": "Hap dokument të ndarë",
|
||||
"download": "Shkarkoni aplikacionet tona",
|
||||
"e2ee": "Fshehtëzim Skaj-më-Skaj",
|
||||
"documentClose": "Mbylle dokumentin e ndarë",
|
||||
"documentOpen": "Hape dokumentin e ndarë",
|
||||
"download": "Shkarkoni aplikacione tonat",
|
||||
"e2ee": "Fshehtëzim Skaj-Më-Skaj",
|
||||
"embedMeeting": "Trupëzoni takim",
|
||||
"enableNoiseSuppression": "Aktivizoni asgjësim ekstra zhurmash",
|
||||
"enableNoiseSuppression": "Aktivizoni mbytje zhurmash",
|
||||
"endConference": "Përfundoje takimin për të tërë",
|
||||
"enterFullScreen": "Shiheni sa krejt ekrani",
|
||||
"enterTileView": "Kaloni nën mënyrën me kuadrate",
|
||||
"exitFullScreen": "Dil nga mënyra “Sa krejt ekrani”",
|
||||
"exitTileView": "Dil nga mënyra me kuadrate",
|
||||
"enterTileView": "Kalo te pamja me kuadrate",
|
||||
"exitFullScreen": "Dil nga mënyra sa krejt ekrani",
|
||||
"exitTileView": "Dil nga pamja me kuadrate",
|
||||
"feedback": "Lini përshtypje",
|
||||
"fileSharing": "Dhënie kartelash",
|
||||
"giphy": "Shfaq/fshih menunë GIPHY",
|
||||
"hangup": "Braktiseni takimin",
|
||||
"giphy": "Shfaq/Fshih menunë GIPHY",
|
||||
"hangup": "Braktisni takimin",
|
||||
"help": "Ndihmë",
|
||||
"hideWhiteboard": "Fshihe tabelën",
|
||||
"invite": "Ftoni njerëz",
|
||||
"invite": "Ftoni persona",
|
||||
"joinBreakoutRoom": "Hyni në dhomë aneks konsultimesh",
|
||||
"laugh": "E qeshur",
|
||||
"laugh": "Qeshje",
|
||||
"leaveBreakoutRoom": "Dilni nga dhomë aneks konsultimesh",
|
||||
"leaveConference": "Dilni nga takimi",
|
||||
"like": "",
|
||||
"linkToSalesforce": "Lidhje për te",
|
||||
"lobbyButtonDisable": "Çaktivizoni mënyrën holl",
|
||||
"lobbyButtonEnable": "Aktivizoni mënyrën holl",
|
||||
"login": "Hyni",
|
||||
"logout": "Dilni",
|
||||
"like": "Thumbs Up",
|
||||
"linkToSalesforce": "Lidhje për te Salesforce",
|
||||
"lobbyButtonDisable": "Çaktivizo mënyrën holl",
|
||||
"lobbyButtonEnable": "Aktivizo mënyrën holl",
|
||||
"login": "Hyrje",
|
||||
"logout": "Dalje",
|
||||
"love": "Zemër",
|
||||
"lowerYourHand": "Ulni dorën",
|
||||
"moreActions": "Më tepër veprime",
|
||||
"moreOptions": "Më tepër veprime",
|
||||
"mute": "Heshto mikrofonin",
|
||||
"muteEveryone": "Heshto gjithkënd",
|
||||
"muteEveryonesVideo": "Çaktivizo kamerën e gjithkujt",
|
||||
"muteEveryonesVideo": "Çaktivizo videon e gjithkujt",
|
||||
"muteGUMPending": "Po lidhet mikrofoni juaj",
|
||||
"noAudioSignalDesc": "Nëse s’e keni heshtuar qëllimisht që nga rregullimet e sistemit, ose nga hardware-i, shihni mundësinë e ndërrimit të pajisjes.",
|
||||
"noAudioSignalDescSuggestion": "Nëse s’e keni heshtuar qëllimisht që nga rregullimet e sistemit, ose nga hardware-i, shihni mundësinë e kalimit te pajisja e sugjeruar.",
|
||||
"noAudioSignalDesc": "Nëse s’e keni heshtuar që nga rregullimet e sistemit, ose nga hardware-i, shihni mundësinë e ndërrimit të pajisjes.",
|
||||
"noAudioSignalDescSuggestion": "Nëse s’e keni heshtuar që nga rregullimet e sistemit, ose nga hardware-i, shihni mundësinë e kalimit te pajisja e sugjeruar.",
|
||||
"noAudioSignalDialInDesc": "Mund t’i bini numrit edhe duke përdorur:",
|
||||
"noAudioSignalDialInLinkDesc": "Numra thirrjeje",
|
||||
"noAudioSignalTitle": "S’ka sinjal që vjen nga mikrofoni juaj!",
|
||||
"noiseSuppression": "Mbytje zhurmash ekstra",
|
||||
"noiseSuppression": "Mbytje zhurmash",
|
||||
"noisyAudioInputDesc": "Dëgjohet sikur mikrofoni juaj prodhon zhurmë, ju lutemi, shihni mundësinë e heshtimit të tij, ose të ndërrimit të pajisjes.",
|
||||
"noisyAudioInputTitle": "Mikrofoni juaj duket të jetë i zhurmshëm!",
|
||||
"openChat": "Hapni fjalosje",
|
||||
"openReactionsMenu": "Hap menu reagimesh",
|
||||
"participants": "Pjesëmarrës",
|
||||
"pip": "Kalo nën mënyrën “Picture-in-Picture”",
|
||||
"polls": "Pyetësorë",
|
||||
"privateMessage": "Dërgoni mesazh privat",
|
||||
"privateMessage": "Dërgo mesazh privat",
|
||||
"profile": "Përpunoni profilin tuaj",
|
||||
"raiseHand": "Ngrini dorën",
|
||||
"raiseYourHand": "Ngrini dorën",
|
||||
@@ -1492,7 +1370,6 @@
|
||||
"reactionHeart": "Dërgoni reagim me zemër",
|
||||
"reactionLaugh": "Dërgoni reagim me qeshje",
|
||||
"reactionLike": "Dërgoni reagim me “thumbs up”",
|
||||
"reactionLove": "Dërgoni reagim me dashuri",
|
||||
"reactionSilence": "Dërgoni reagim me heshtje",
|
||||
"reactionSurprised": "Dërgoni reagim të befasuari",
|
||||
"reactions": "Reagime",
|
||||
@@ -1504,9 +1381,9 @@
|
||||
"shortcuts": "Shihni shkurtore",
|
||||
"showWhiteboard": "Shfaq tabelë",
|
||||
"silence": "Heshtje",
|
||||
"speakerStats": "Statistika pjesëmarrësish",
|
||||
"speakerStats": "Statistika folësi",
|
||||
"startScreenSharing": "Nise tregimin e ekranit",
|
||||
"startSubtitles": "Titra • {{language}}",
|
||||
"startSubtitles": "Nis titra",
|
||||
"stopAudioSharing": "Ndal ndarje audioje me të tjerë",
|
||||
"stopScreenSharing": "Ndale tregimin e ekranit",
|
||||
"stopSharedVideo": "Ndale videon",
|
||||
@@ -1524,19 +1401,15 @@
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Ndali / Nisi titrat",
|
||||
"expandedLabel": "Transkriptimi aktualisht është aktiv",
|
||||
"failed": "Transkriptimi dështoi",
|
||||
"labelTooltip": "Ky takim po transkriptohet.",
|
||||
"labelTooltipExtra": "Përveç kësaj, më vonë do të ketë një transkriptim të gatshëm.",
|
||||
"openClosedCaptions": "Hap titra të mbyllur",
|
||||
"original": "Origjinali",
|
||||
"sourceLanguageDesc": "Aktualisht si gjuhë e mbledhjes është vënë <b>{{sourceLanguage}}</b>. <br/> Mundeni ta ndryshoni që nga ",
|
||||
"failedToStart": "S’u arrit të nisej transkriptim",
|
||||
"labelToolTip": "Takimit po i bëhet transkriptim",
|
||||
"sourceLanguageDesc": "Aktualisht si gjuhë takimi është caktuar <b>{{sourceLanguage}}</b>. <br/> Mund ta ndryshoni që nga ",
|
||||
"sourceLanguageHere": "këtu",
|
||||
"start": "Fillo shfaqje titrash",
|
||||
"stop": "Ndal shfaqje titrash",
|
||||
"subtitles": "Titra",
|
||||
"subtitlesOff": "Off",
|
||||
"tr": "TR",
|
||||
"translateTo": "Përktheje në"
|
||||
"tr": "TR"
|
||||
},
|
||||
"unpinParticipant": "{{participantName}} - Hiqja fiksimin",
|
||||
"userMedia": {
|
||||
@@ -1568,7 +1441,7 @@
|
||||
"ldTooltip": "Po shihet video në cilësi të ulët",
|
||||
"lowDefinition": "Cilësi e ulët",
|
||||
"performanceSettings": "Rregullime funksionimi",
|
||||
"recording": "Ky takim po regjistrohet.",
|
||||
"recording": "Regjistrim në kryerje e sipër",
|
||||
"sd": "CS",
|
||||
"sdTooltip": "Po shihet video në cilësi standarde",
|
||||
"standardDefinition": "Cilësi standarde",
|
||||
@@ -1578,8 +1451,6 @@
|
||||
"connectionInfo": "Hollësi Lidhjeje",
|
||||
"demote": "Kaloje te vizitorët",
|
||||
"domute": "Heshtoje",
|
||||
"domuteDesktop": "Ndaleni tregimin e ekranit",
|
||||
"domuteDesktopOfOthers": "Ndaleni tregimin e ekranit për këdo tjetër",
|
||||
"domuteOthers": "Heshto gjithkënd tjetër",
|
||||
"domuteVideo": "Çaktivizoje kamerën",
|
||||
"domuteVideoOfOthers": "Çaktivizo kamerën e gjithkujt tjetër",
|
||||
@@ -1630,23 +1501,21 @@
|
||||
"webAssemblyWarningDescription": "WebAssembly e çaktivizuar ose e pambuluar nga ky shfletues"
|
||||
},
|
||||
"visitors": {
|
||||
"chatIndicator": "(parës)",
|
||||
"chatIndicator": "(vizitor)",
|
||||
"joinMeeting": {
|
||||
"description": "Aktualisht jeni vëzhgues në këtë konferencë.",
|
||||
"raiseHand": "Ngrini dorën",
|
||||
"title": "Hyrje në takim",
|
||||
"wishToSpeak": "Nëse dëshironi të flisni, ju lutemi, ngrini dorën më poshtë dhe prisni miratimin e moderatorit."
|
||||
},
|
||||
"labelTooltip": "Numër parësish: {{count}}",
|
||||
"labelTooltip": "Numër vizitorësh: {{count}}",
|
||||
"notification": {
|
||||
"demoteDescription": "Dërguar këtu nga {{actor}}, që të merrni pjesë, ngrini dorën",
|
||||
"noMainParticipantsDescription": "Duhet që një pjesëmarrës të nisë takimin. Ju lutemi, riprovoni pas pak.",
|
||||
"noMainParticipantsDescription": "Duhet që një pjesëmarrëstë nisë takimin. Ju lutemi, riprovoni pas pak.",
|
||||
"noMainParticipantsTitle": "Ky takim s’ka filluar ende.",
|
||||
"noVisitorLobby": "S’mund të hyni, teksa ka një holl të hapur për takimin.",
|
||||
"noVisitorLobby": "S’mund të hyni, teksa ka një holll të hapur për takimin.",
|
||||
"notAllowedPromotion": "Lypset që së pari një pjesëmarrës të lejojë kërkesën tuaj.",
|
||||
"requestToJoin": "U ngrit Dorë",
|
||||
"requestToJoinDescription": "Kërkesa juaj iu dërgua moderatorëve. Mos u largoni!",
|
||||
"title": "Jeni një parës te takimi"
|
||||
"title": "Jeni vizitor në takim"
|
||||
},
|
||||
"waitingMessage": "Do të merrni pjesë në këta takim sapo të fillojë!"
|
||||
},
|
||||
|
||||
@@ -383,8 +383,6 @@
|
||||
"lockRoom": "Add meeting $t(lockRoomPassword)",
|
||||
"lockTitle": "Lock failed",
|
||||
"login": "Login",
|
||||
"loginFailed": "Login failed.",
|
||||
"loginOnResume": "Your authentication session has expired. You need to login again to continue the meeting.",
|
||||
"loginQuestion": "Are you sure you want to login and leave the conference?",
|
||||
"logoutQuestion": "Are you sure you want to logout and leave the conference?",
|
||||
"logoutTitle": "Logout",
|
||||
@@ -594,7 +592,6 @@
|
||||
"newFileNotification": "{{ participantName }} shared '{{ fileName }}'",
|
||||
"removeFile": "Remove",
|
||||
"removeFileSuccess": "File removed successfully",
|
||||
"uploadDisabled": "Not allowed to upload files. Ask a moderator for permission rights for that operation.",
|
||||
"uploadFailedDescription": "Please try again.",
|
||||
"uploadFailedTitle": "Upload failed",
|
||||
"uploadFile": "Share file"
|
||||
@@ -1145,6 +1142,7 @@
|
||||
"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.",
|
||||
"off": "Recording stopped",
|
||||
"offBy": "{{name}} stopped the recording",
|
||||
"on": "Recording started",
|
||||
|
||||
@@ -2253,32 +2253,6 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the external application that a file has been uploaded.
|
||||
*
|
||||
* @param {Object} fileMetadata - The file metadata.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyFileUploaded(fileMetadata) {
|
||||
this._sendEvent({
|
||||
name: 'file-uploaded',
|
||||
file: fileMetadata
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the external application that a file has been deleted.
|
||||
*
|
||||
* @param {string} fileId - The ID of the deleted file.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyFileDeleted(fileId) {
|
||||
this._sendEvent({
|
||||
name: 'file-deleted',
|
||||
fileId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the external application that the audio or video is being shared by a participant.
|
||||
*
|
||||
|
||||
10
modules/API/external/external_api.js
vendored
10
modules/API/external/external_api.js
vendored
@@ -133,8 +133,6 @@ const events = {
|
||||
'face-landmark-detected': 'faceLandmarkDetected',
|
||||
'feedback-submitted': 'feedbackSubmitted',
|
||||
'feedback-prompt-displayed': 'feedbackPromptDisplayed',
|
||||
'file-deleted': 'fileDeleted',
|
||||
'file-uploaded': 'fileUploaded',
|
||||
'filmstrip-display-changed': 'filmstripDisplayChanged',
|
||||
'incoming-message': 'incomingMessage',
|
||||
'knocking-participant': 'knockingParticipant',
|
||||
@@ -775,7 +773,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* @returns {void}
|
||||
*
|
||||
* @deprecated
|
||||
* NOTE: This method is not removed for backward compatibility purposes.
|
||||
* NOTE: This method is not removed for backward comatability purposes.
|
||||
*/
|
||||
addEventListener(event, listener) {
|
||||
this.on(event, listener);
|
||||
@@ -862,7 +860,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* @returns {void}
|
||||
*
|
||||
* @deprecated
|
||||
* NOTE: This method is not removed for backward compatibility purposes.
|
||||
* NOTE: This method is not removed for backward comatability purposes.
|
||||
*/
|
||||
addEventListeners(listeners) {
|
||||
for (const event in listeners) { // eslint-disable-line guard-for-in
|
||||
@@ -1413,7 +1411,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* @returns {void}
|
||||
*
|
||||
* @deprecated
|
||||
* NOTE: This method is not removed for backward compatibility purposes.
|
||||
* NOTE: This method is not removed for backward comatability purposes.
|
||||
*/
|
||||
removeEventListener(event) {
|
||||
this.removeAllListeners(event);
|
||||
@@ -1426,7 +1424,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* @returns {void}
|
||||
*
|
||||
* @deprecated
|
||||
* NOTE: This method is not removed for backward compatibility purposes.
|
||||
* NOTE: This method is not removed for backward comatability purposes.
|
||||
*/
|
||||
removeEventListeners(eventList) {
|
||||
eventList.forEach(event => this.removeEventListener(event));
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import Logger from '@jitsi/logger';
|
||||
import $ from 'jquery';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
@@ -166,7 +166,8 @@ export default class LargeVideoManager {
|
||||
|
||||
this.removePresenceLabel();
|
||||
|
||||
ReactDOM.unmountComponentAtNode(this._dominantSpeakerAvatarContainer);
|
||||
this._avatarRoot?.unmount();
|
||||
this._avatarRoot = null;
|
||||
|
||||
this.container.style.display = 'none';
|
||||
}
|
||||
@@ -518,14 +519,16 @@ export default class LargeVideoManager {
|
||||
* Updates the src of the dominant speaker avatar
|
||||
*/
|
||||
updateAvatar() {
|
||||
ReactDOM.render(
|
||||
if (!this._avatarRoot) {
|
||||
this._avatarRoot = createRoot(this._dominantSpeakerAvatarContainer);
|
||||
}
|
||||
this._avatarRoot.render(
|
||||
<Provider store = { APP.store }>
|
||||
<Avatar
|
||||
id = "dominantSpeakerAvatar"
|
||||
participantId = { this.id }
|
||||
size = { 200 } />
|
||||
</Provider>,
|
||||
this._dominantSpeakerAvatarContainer
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -559,15 +562,18 @@ export default class LargeVideoManager {
|
||||
const presenceLabelContainer = document.getElementById('remotePresenceMessage');
|
||||
|
||||
if (presenceLabelContainer) {
|
||||
ReactDOM.render(
|
||||
if (!this._presenceLabelRoot) {
|
||||
this._presenceLabelRoot = createRoot(presenceLabelContainer);
|
||||
}
|
||||
this._presenceLabelRoot.render(
|
||||
<Provider store = { APP.store }>
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
<PresenceLabel
|
||||
participantID = { id }
|
||||
className = 'presence-label' />
|
||||
</I18nextProvider>
|
||||
</Provider>,
|
||||
presenceLabelContainer);
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,11 +583,8 @@ export default class LargeVideoManager {
|
||||
* @returns {void}
|
||||
*/
|
||||
removePresenceLabel() {
|
||||
const presenceLabelContainer = document.getElementById('remotePresenceMessage');
|
||||
|
||||
if (presenceLabelContainer) {
|
||||
ReactDOM.unmountComponentAtNode(presenceLabelContainer);
|
||||
}
|
||||
this._presenceLabelRoot?.unmount();
|
||||
this._presenceLabelRoot = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import Logger from '@jitsi/logger';
|
||||
import $ from 'jquery';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { browser } from '../../../react/features/base/lib-jitsi-meet';
|
||||
import { FILMSTRIP_BREAKPOINT } from '../../../react/features/filmstrip/constants';
|
||||
@@ -659,7 +659,12 @@ export class VideoContainer extends LargeContainer {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
const container = document.getElementById('largeVideoBackgroundContainer');
|
||||
|
||||
if (!this._backgroundRoot) {
|
||||
this._backgroundRoot = createRoot(container);
|
||||
}
|
||||
this._backgroundRoot.render(
|
||||
<LargeVideoBackground
|
||||
hidden = { this._hideBackground || this._isHidden }
|
||||
mirror = {
|
||||
@@ -669,8 +674,7 @@ export class VideoContainer extends LargeContainer {
|
||||
}
|
||||
orientationFit = { this._backgroundOrientation }
|
||||
videoElement = { this.video }
|
||||
videoTrack = { this.stream } />,
|
||||
document.getElementById('largeVideoBackgroundContainer')
|
||||
videoTrack = { this.stream } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
26
package-lock.json
generated
26
package-lock.json
generated
@@ -66,7 +66,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/v2131.0.0+6912eed8/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2124.0.0+80df84a1/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"null-loader": "4.0.1",
|
||||
"optional-require": "1.0.3",
|
||||
@@ -19049,8 +19049,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2131.0.0+6912eed8/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-oTQHdvxPkevpn0CNRiiKaojb6PQxRJMdrmV5N7yrWsiqUA5qw+eMQ7A251oahaSfpW5yLz2CM4l+OHGADWrBUQ==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2124.0.0+80df84a1/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-wNfil8xxSjcrT3oNA5Lil0qETqR6W2XxpXNewYYjFiM2kSuWgH8fcVxgcQYzvGH+sOqEjdUEk1V81oNo/rB6tQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "^2.6.7",
|
||||
@@ -19062,7 +19062,6 @@
|
||||
"base64-js": "1.5.1",
|
||||
"current-executing-script": "0.1.3",
|
||||
"emoji-regex": "10.4.0",
|
||||
"jwt-decode": "4.0.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"sdp-transform": "2.3.0",
|
||||
"strophe.js": "https://github.com/jitsi/strophejs/releases/download/v1.5-jitsi-3/strophe.js-1.5.0.tgz",
|
||||
@@ -19092,15 +19091,6 @@
|
||||
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lib-jitsi-meet/node_modules/jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
@@ -40858,8 +40848,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2131.0.0+6912eed8/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-oTQHdvxPkevpn0CNRiiKaojb6PQxRJMdrmV5N7yrWsiqUA5qw+eMQ7A251oahaSfpW5yLz2CM4l+OHGADWrBUQ==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2124.0.0+80df84a1/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-wNfil8xxSjcrT3oNA5Lil0qETqR6W2XxpXNewYYjFiM2kSuWgH8fcVxgcQYzvGH+sOqEjdUEk1V81oNo/rB6tQ==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "^2.6.7",
|
||||
"@jitsi/logger": "2.1.1",
|
||||
@@ -40870,7 +40860,6 @@
|
||||
"base64-js": "1.5.1",
|
||||
"current-executing-script": "0.1.3",
|
||||
"emoji-regex": "10.4.0",
|
||||
"jwt-decode": "4.0.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"sdp-transform": "2.3.0",
|
||||
"strophe.js": "https://github.com/jitsi/strophejs/releases/download/v1.5-jitsi-3/strophe.js-1.5.0.tgz",
|
||||
@@ -40898,11 +40887,6 @@
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
|
||||
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="
|
||||
},
|
||||
"jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -72,7 +72,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/v2131.0.0+6912eed8/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2124.0.0+80df84a1/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"null-loader": "4.0.1",
|
||||
"optional-require": "1.0.3",
|
||||
|
||||
@@ -113,13 +113,12 @@ export function maybeRedirectToTokenAuthUrl(
|
||||
const audioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
|
||||
const videoMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO);
|
||||
|
||||
if (!isTokenAuthEnabled(state)) {
|
||||
if (!isTokenAuthEnabled(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if tokenAuthUrl check jwt if is about to expire go through the url to get new token
|
||||
const jwt = state['features/base/jwt'].jwt;
|
||||
const refreshToken = state['features/base/jwt'].refreshToken;
|
||||
const expirationDate = getJwtExpirationDate(jwt);
|
||||
|
||||
// if there is jwt and its expiration time is less than 3 minutes away
|
||||
@@ -138,8 +137,7 @@ export function maybeRedirectToTokenAuthUrl(
|
||||
videoMuted
|
||||
},
|
||||
room,
|
||||
tenant,
|
||||
refreshToken
|
||||
tenant
|
||||
)
|
||||
.then((tokenAuthServiceUrl: string | undefined) => {
|
||||
if (!tokenAuthServiceUrl) {
|
||||
|
||||
@@ -11,9 +11,8 @@ const route = {
|
||||
* store.
|
||||
*
|
||||
* @param {any} _stateful - Used on web.
|
||||
* @param {any} _dispatch - Used on web.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export function _getRouteToRender(_stateful?: any): Promise<object> {
|
||||
export function _getRouteToRender(_stateful?: any) {
|
||||
return Promise.resolve(route);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// @ts-expect-error
|
||||
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
|
||||
|
||||
import { getTokenAuthUrl } from '../authentication/functions.web';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { isRoomValid } from '../base/conference/functions';
|
||||
import { isSupportedBrowser } from '../base/environment/environment';
|
||||
import { browser } from '../base/lib-jitsi-meet';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { parseURIString } from '../base/util/uri';
|
||||
import Conference from '../conference/components/web/Conference';
|
||||
import { getDeepLinkingPage } from '../deep-linking/functions';
|
||||
import UnsupportedDesktopBrowser from '../unsupported-browser/components/UnsupportedDesktopBrowser';
|
||||
@@ -20,10 +23,9 @@ import { IReduxState } from './types';
|
||||
*
|
||||
* @param {(Function|Object)} stateful - THe redux store, state, or
|
||||
* {@code getState} function.
|
||||
* @param {Dispatch} dispatch - The Redux dispatch function.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export function _getRouteToRender(stateful: IStateful): Promise<object> {
|
||||
export function _getRouteToRender(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
|
||||
return _getWebConferenceRoute(state) || _getWebWelcomePageRoute(state);
|
||||
@@ -34,10 +36,9 @@ export function _getRouteToRender(stateful: IStateful): Promise<object> {
|
||||
* a valid conference is being joined.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {Dispatch} dispatch - The Redux dispatch function.
|
||||
* @returns {Promise|undefined}
|
||||
*/
|
||||
function _getWebConferenceRoute(state: IReduxState): Promise<any> | undefined {
|
||||
function _getWebConferenceRoute(state: IReduxState) {
|
||||
const room = state['features/base/conference'].room;
|
||||
|
||||
if (!isRoomValid(room)) {
|
||||
@@ -45,6 +46,36 @@ function _getWebConferenceRoute(state: IReduxState): Promise<any> | undefined {
|
||||
}
|
||||
|
||||
const route = _getEmptyRoute();
|
||||
const config = state['features/base/config'];
|
||||
|
||||
// if we have auto redirect enabled, and we have previously logged in successfully
|
||||
// let's redirect to the auth url to get the token and login again
|
||||
if (!browser.isElectron() && config.tokenAuthUrl && config.tokenAuthUrlAutoRedirect
|
||||
&& state['features/authentication'].tokenAuthUrlSuccessful
|
||||
&& !state['features/base/jwt'].jwt && room) {
|
||||
const { locationURL = { href: '' } as URL } = state['features/base/connection'];
|
||||
const { tenant } = parseURIString(locationURL.href) || {};
|
||||
const { startAudioOnly } = config;
|
||||
|
||||
return getTokenAuthUrl(
|
||||
config,
|
||||
locationURL,
|
||||
{
|
||||
audioMuted: false,
|
||||
audioOnlyEnabled: startAudioOnly,
|
||||
skipPrejoin: false,
|
||||
videoMuted: false
|
||||
},
|
||||
room,
|
||||
tenant
|
||||
)
|
||||
.then((url: string | undefined) => {
|
||||
route.href = url;
|
||||
|
||||
return route;
|
||||
})
|
||||
.catch(() => Promise.resolve(route));
|
||||
}
|
||||
|
||||
// Update the location if it doesn't match. This happens when a room is
|
||||
// joined from the welcome page. The reason for doing this instead of using
|
||||
|
||||
@@ -3,7 +3,7 @@ import '../authentication/middleware';
|
||||
import '../av-moderation/middleware';
|
||||
import '../base/conference/middleware';
|
||||
import '../base/i18n/middleware';
|
||||
import '../base/jwt/middleware.any';
|
||||
import '../base/jwt/middleware';
|
||||
import '../base/known-domains/middleware';
|
||||
import '../base/lastn/middleware';
|
||||
import '../base/lib-jitsi-meet/middleware';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import '../base/app/middleware';
|
||||
import '../base/jwt/middleware.web';
|
||||
import '../base/config/middleware';
|
||||
import '../base/connection/middleware';
|
||||
import '../base/devices/middleware';
|
||||
|
||||
@@ -26,6 +26,16 @@ export const LOGIN = 'LOGIN';
|
||||
*/
|
||||
export const LOGOUT = 'LOGOUT';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that we have authenticated successful when
|
||||
* tokenAuthUrl is set.
|
||||
*
|
||||
* {
|
||||
* type: SET_TOKEN_AUTH_URL_SUCCESS
|
||||
* }
|
||||
*/
|
||||
export const SET_TOKEN_AUTH_URL_SUCCESS = 'SET_TOKEN_AUTH_URL_SUCCESS';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that the cyclic operation of waiting
|
||||
* for conference owner has been aborted.
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
ENABLE_MODERATOR_LOGIN,
|
||||
LOGIN,
|
||||
LOGOUT,
|
||||
SET_TOKEN_AUTH_URL_SUCCESS,
|
||||
STOP_WAIT_FOR_OWNER,
|
||||
UPGRADE_ROLE_FINISHED,
|
||||
UPGRADE_ROLE_STARTED,
|
||||
@@ -241,3 +242,16 @@ export function waitForOwner() {
|
||||
export function openLoginDialog() {
|
||||
return openDialog('LoginDialog', LoginDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the config with new options.
|
||||
*
|
||||
* @param {boolean} value - The new value.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setTokenAuthUrlSuccess(value: boolean) {
|
||||
return {
|
||||
type: SET_TOKEN_AUTH_URL_SUCCESS,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,13 +88,3 @@ export function openTokenAuthUrl(tokenAuthServiceUrl: string) {
|
||||
Linking.openURL(tokenAuthServiceUrl);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used.
|
||||
*
|
||||
* @param {string} tokenAuthServiceUrl - Authentication service URL.
|
||||
* @returns {Promise<any>} Resolves.
|
||||
*/
|
||||
export function loginWithPopup(tokenAuthServiceUrl: string): Promise<any> {
|
||||
return Promise.resolve(tokenAuthServiceUrl);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { maybeRedirectToWelcomePage } from '../app/actions.web';
|
||||
import { IStore } from '../app/types';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import { setJWT } from '../base/jwt/actions';
|
||||
import { browser } from '../base/lib-jitsi-meet';
|
||||
import { showErrorNotification } from '../notifications/actions';
|
||||
|
||||
import { CANCEL_LOGIN } from './actionTypes';
|
||||
import LoginQuestionDialog from './components/web/LoginQuestionDialog';
|
||||
import { isTokenAuthInline } from './functions.any';
|
||||
import logger from './logger';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
@@ -50,147 +46,6 @@ export function redirectToDefaultLocation() {
|
||||
return (dispatch: IStore['dispatch']) => dispatch(maybeRedirectToWelcomePage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a cryptographic nonce.
|
||||
*
|
||||
* @returns {string} The generated nonce.
|
||||
*/
|
||||
function generateNonce(): string {
|
||||
const array = new Uint8Array(32);
|
||||
|
||||
crypto.getRandomValues(array);
|
||||
|
||||
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs login with a popup window.
|
||||
*
|
||||
* @param {string} tokenAuthServiceUrl - Authentication service URL.
|
||||
* @returns {Promise<any>} A promise that resolves with the authentication
|
||||
* result or rejects with an error.
|
||||
*/
|
||||
export function loginWithPopup(tokenAuthServiceUrl: string): Promise<any> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
// Open popup
|
||||
const width = 500;
|
||||
const height = 600;
|
||||
const left = window.screen.width / 2 - width / 2;
|
||||
const top = window.screen.height / 2 - height / 2;
|
||||
|
||||
let nonceParam = '';
|
||||
|
||||
try {
|
||||
const nonce = generateNonce();
|
||||
|
||||
sessionStorage.setItem('oauth_nonce', nonce);
|
||||
|
||||
nonceParam = `&nonce=${nonce}`;
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException && e.name === 'SecurityError') {
|
||||
logger.warn(
|
||||
'sessionStorage access denied (cross-origin or restricted context) enable it to improve security',
|
||||
e);
|
||||
} else {
|
||||
logger.error('Unable to save nonce in session storage', e);
|
||||
}
|
||||
}
|
||||
|
||||
const popup = window.open(
|
||||
`${tokenAuthServiceUrl}${nonceParam}`,
|
||||
`Auth-${Date.now()}`,
|
||||
`width=${width},height=${height},left=${left},top=${top}`
|
||||
);
|
||||
|
||||
if (!popup) {
|
||||
reject(new Error('Popup blocked'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let closedPollInterval: ReturnType<typeof setInterval> | undefined = undefined;
|
||||
const cleanup = (handler: (event: MessageEvent) => void) => {
|
||||
window.removeEventListener('message', handler);
|
||||
clearInterval(closedPollInterval);
|
||||
popup.close();
|
||||
|
||||
try {
|
||||
sessionStorage.removeItem('oauth_nonce');
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
sessionStorage.removeItem('code_verifier');
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
const handler = (event: MessageEvent) => {
|
||||
// Verify origin
|
||||
if (event.origin !== window.location.origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.type === 'oauth-success') {
|
||||
cleanup(handler);
|
||||
|
||||
resolve({
|
||||
accessToken: event.data.accessToken,
|
||||
idToken: event.data.idToken,
|
||||
refreshToken: event.data.refreshToken
|
||||
});
|
||||
} else if (event.data.type === 'oauth-error') {
|
||||
cleanup(handler);
|
||||
|
||||
reject(new Error(event.data.error));
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for messages from the popup
|
||||
window.addEventListener('message', handler);
|
||||
|
||||
// Detect manual popup close before authentication completes
|
||||
closedPollInterval = setInterval(() => {
|
||||
if (popup.closed) {
|
||||
cleanup(handler);
|
||||
reject(new Error('Login cancelled'));
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs silent logout by loading the token authentication logout service URL in an
|
||||
* invisible iframe.
|
||||
*
|
||||
* @param {string} tokenAuthLogoutServiceUrl - Logout service URL.
|
||||
* @returns {Promise<any>} A promise that resolves when logout is complete.
|
||||
*/
|
||||
export function silentLogout(tokenAuthLogoutServiceUrl: string): any {
|
||||
return new Promise<void>(resolve => {
|
||||
const iframe = document.createElement('iframe');
|
||||
|
||||
iframe.style.display = 'none';
|
||||
iframe.src = tokenAuthLogoutServiceUrl;
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
// Listen for logout completion
|
||||
const handler = (event: any) => {
|
||||
if (event.origin !== window.location.origin) return;
|
||||
|
||||
if (event.data.type === 'logout-success') {
|
||||
window.removeEventListener('message', handler);
|
||||
document.body.removeChild(iframe);
|
||||
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', handler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens token auth URL page.
|
||||
*
|
||||
@@ -208,42 +63,6 @@ export function openTokenAuthUrl(tokenAuthServiceUrl: string): any {
|
||||
}
|
||||
};
|
||||
|
||||
if (!browser.isElectron() && isTokenAuthInline(getState()['features/base/config'])) {
|
||||
loginWithPopup(tokenAuthServiceUrl)
|
||||
.then((result: { accessToken: string; idToken: string; refreshToken?: string; }) => {
|
||||
// @ts-ignore
|
||||
const token: string = result.accessToken;
|
||||
const idToken: string = result.idToken;
|
||||
const refreshToken: string | undefined = result.refreshToken;
|
||||
|
||||
// @ts-ignore
|
||||
dispatch(setJWT(token, idToken, refreshToken));
|
||||
|
||||
logger.info('Reconnecting to conference with new token.');
|
||||
|
||||
const { connection } = getState()['features/base/connection'];
|
||||
|
||||
connection?.refreshToken(token).then(
|
||||
() => {
|
||||
const { membersOnly } = getState()['features/base/conference'];
|
||||
|
||||
membersOnly?.join();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
dispatch(setJWT());
|
||||
logger.error(err);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'dialog.loginFailed'
|
||||
}));
|
||||
logger.error(err);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Show warning for leaving conference only when in a conference.
|
||||
if (!browser.isElectron() && getState()['features/base/conference'].conference) {
|
||||
dispatch(openDialog('LoginQuestionDialog', LoginQuestionDialog, {
|
||||
|
||||
@@ -1,30 +1,15 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { IConfig } from '../base/config/configType';
|
||||
import { parseURLParams } from '../base/util/parseURLParams';
|
||||
import { getBackendSafeRoomName } from '../base/util/uri';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
|
||||
/**
|
||||
* Checks if the token for authentication URL is available and the meeting is not jaas.
|
||||
*
|
||||
* @param {IReduxState} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isTokenAuthEnabled = (state: IReduxState): boolean => {
|
||||
const config = state['features/base/config'];
|
||||
|
||||
return typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length > 0
|
||||
&& !isVpaasMeeting(state);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the token authentication should be done inline.
|
||||
* Checks if the token for authentication is available.
|
||||
*
|
||||
* @param {Object} config - Configuration state object from store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isTokenAuthInline = (config: IConfig): boolean =>
|
||||
config.tokenAuthInline === true;
|
||||
export const isTokenAuthEnabled = (config: IConfig): boolean =>
|
||||
typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length > 0;
|
||||
|
||||
/**
|
||||
* Returns the state that we can add as a parameter to the tokenAuthUrl.
|
||||
@@ -38,7 +23,6 @@ export const isTokenAuthInline = (config: IConfig): boolean =>
|
||||
* }.
|
||||
* @param {string?} roomName - The room name.
|
||||
* @param {string?} tenant - The tenant name if any.
|
||||
* @param {string?} refreshToken - The refresh token if available.
|
||||
*
|
||||
* @returns {Object} The state object.
|
||||
*/
|
||||
@@ -51,10 +35,8 @@ export const _getTokenAuthState = (
|
||||
videoMuted: boolean | undefined;
|
||||
},
|
||||
roomName: string | undefined,
|
||||
tenant: string | undefined,
|
||||
refreshToken?: string): object => {
|
||||
tenant: string | undefined): object => {
|
||||
const state = {
|
||||
refreshToken,
|
||||
room: roomName,
|
||||
roomSafe: getBackendSafeRoomName(roomName),
|
||||
tenant
|
||||
|
||||
@@ -23,7 +23,6 @@ export * from './functions.any';
|
||||
* }.
|
||||
* @param {string?} roomName - The room name.
|
||||
* @param {string?} tenant - The tenant name if any.
|
||||
* @param {string?} refreshToken - The refreshToken if any.
|
||||
*
|
||||
* @returns {Promise<string|undefined>} - The URL pointing to JWT login service or
|
||||
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
|
||||
@@ -40,9 +39,7 @@ export const getTokenAuthUrl = (
|
||||
},
|
||||
roomName: string | undefined,
|
||||
// eslint-disable-next-line max-params
|
||||
tenant: string | undefined,
|
||||
// eslint-disable-next-line max-params
|
||||
refreshToken?: string | undefined): Promise<string | undefined> => {
|
||||
tenant: string | undefined): Promise<string | undefined> => {
|
||||
|
||||
const {
|
||||
audioMuted = false,
|
||||
@@ -67,8 +64,7 @@ export const getTokenAuthUrl = (
|
||||
videoMuted
|
||||
},
|
||||
roomName,
|
||||
tenant,
|
||||
refreshToken
|
||||
tenant
|
||||
);
|
||||
|
||||
// Append ios=true or android=true to the token URL.
|
||||
|
||||
@@ -4,7 +4,6 @@ import { IConfig } from '../base/config/configType';
|
||||
import { browser } from '../base/lib-jitsi-meet';
|
||||
|
||||
import { _getTokenAuthState } from './functions.any';
|
||||
import logger from './logger';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
@@ -42,7 +41,6 @@ function _cryptoRandom() {
|
||||
* }.
|
||||
* @param {string?} roomName - The room name.
|
||||
* @param {string?} tenant - The tenant name if any.
|
||||
* @param {string?} refreshToken - The refresh token if available.
|
||||
*
|
||||
* @returns {Promise<string|undefined>} - The URL pointing to JWT login service or
|
||||
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
|
||||
@@ -58,10 +56,9 @@ export const getTokenAuthUrl = (
|
||||
videoMuted: boolean | undefined;
|
||||
},
|
||||
roomName: string | undefined,
|
||||
// eslint-disable max-params
|
||||
tenant: string | undefined,
|
||||
refreshToken?: string): Promise<string | undefined> => {
|
||||
// eslint-enable max-params
|
||||
// eslint-disable-next-line max-params
|
||||
tenant: string | undefined): Promise<string | undefined> => {
|
||||
|
||||
const {
|
||||
audioMuted = false,
|
||||
audioOnlyEnabled = false,
|
||||
@@ -85,8 +82,7 @@ export const getTokenAuthUrl = (
|
||||
videoMuted
|
||||
},
|
||||
roomName,
|
||||
tenant,
|
||||
refreshToken
|
||||
tenant
|
||||
);
|
||||
|
||||
if (browser.isElectron()) {
|
||||
@@ -107,17 +103,7 @@ export const getTokenAuthUrl = (
|
||||
codeVerifier += POSSIBLE_CHARS.charAt(Math.floor(_cryptoRandom() * POSSIBLE_CHARS.length));
|
||||
}
|
||||
|
||||
try {
|
||||
window.sessionStorage.setItem('code_verifier', codeVerifier);
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException && e.name === 'SecurityError') {
|
||||
logger.warn(
|
||||
'sessionStorage access denied (cross-origin or restricted context) enable it to improve security',
|
||||
e);
|
||||
} else {
|
||||
logger.error('Unable to save code verifier in session storage', e);
|
||||
}
|
||||
}
|
||||
window.sessionStorage.setItem('code_verifier', codeVerifier);
|
||||
|
||||
return window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier))
|
||||
.then(digest => {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { APP_WILL_NAVIGATE } from '../base/app/actionTypes';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT
|
||||
} from '../base/conference/actionTypes';
|
||||
import { isRoomValid } from '../base/conference/functions';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes';
|
||||
import { hideDialog } from '../base/dialog/actions';
|
||||
import { isDialogOpen } from '../base/dialog/functions';
|
||||
@@ -33,6 +35,7 @@ import {
|
||||
openTokenAuthUrl,
|
||||
openWaitForOwnerDialog,
|
||||
redirectToDefaultLocation,
|
||||
setTokenAuthUrlSuccess,
|
||||
stopWaitForOwner,
|
||||
waitForOwner
|
||||
} from './actions';
|
||||
@@ -123,15 +126,25 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
|
||||
case CONFERENCE_JOINED: {
|
||||
const { dispatch } = store;
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const config = state['features/base/config'];
|
||||
|
||||
if (isTokenAuthEnabled(config)
|
||||
&& config.tokenAuthUrlAutoRedirect
|
||||
&& state['features/base/jwt'].jwt) {
|
||||
// auto redirect is turned on and we have successfully logged in
|
||||
// let's mark that
|
||||
dispatch(setTokenAuthUrlSuccess(true));
|
||||
}
|
||||
|
||||
if (_isWaitingForModerator(store)) {
|
||||
dispatch(disableModeratorLogin());
|
||||
store.dispatch(disableModeratorLogin());
|
||||
}
|
||||
if (_isWaitingForOwner(store)) {
|
||||
dispatch(stopWaitForOwner());
|
||||
store.dispatch(stopWaitForOwner());
|
||||
}
|
||||
dispatch(hideLoginDialog());
|
||||
store.dispatch(hideLoginDialog());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -174,6 +187,26 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case APP_WILL_NAVIGATE: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const config = state['features/base/config'];
|
||||
const room = state['features/base/conference'].room;
|
||||
|
||||
if (isRoomValid(room)
|
||||
&& config.tokenAuthUrl && config.tokenAuthUrlAutoRedirect
|
||||
&& state['features/authentication'].tokenAuthUrlSuccessful
|
||||
&& !state['features/base/jwt'].jwt) {
|
||||
// if we have auto redirect enabled, and we have previously logged in successfully
|
||||
// we will redirect to the auth url to get the token and login again
|
||||
// we want to mark token auth success to false as if login is unsuccessful
|
||||
// the participant can join anonymously and not go in login loop
|
||||
dispatch(setTokenAuthUrlSuccess(false));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case STOP_WAIT_FOR_OWNER:
|
||||
_clearExistingWaitForOwnerTimeout(store);
|
||||
store.dispatch(hideDialog('WaitForOwnerDialog', WaitForOwnerDialog));
|
||||
@@ -255,7 +288,6 @@ function _handleLogin({ dispatch, getState }: IStore) {
|
||||
const { enabled: audioOnlyEnabled } = state['features/base/audio-only'];
|
||||
const audioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
|
||||
const videoMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO);
|
||||
const refreshToken = state['features/base/jwt'].refreshToken;
|
||||
|
||||
if (!room) {
|
||||
logger.warn('Cannot handle login, room is undefined!');
|
||||
@@ -263,7 +295,7 @@ function _handleLogin({ dispatch, getState }: IStore) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isTokenAuthEnabled(state)) {
|
||||
if (!isTokenAuthEnabled(config)) {
|
||||
dispatch(openLoginDialog());
|
||||
|
||||
return;
|
||||
@@ -279,8 +311,7 @@ function _handleLogin({ dispatch, getState }: IStore) {
|
||||
videoMuted
|
||||
},
|
||||
room,
|
||||
tenant,
|
||||
refreshToken
|
||||
tenant
|
||||
)
|
||||
.then((tokenAuthServiceUrl: string | undefined) => {
|
||||
if (!tokenAuthServiceUrl) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import PersistenceRegistry from '../base/redux/PersistenceRegistry';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
import { assign } from '../base/redux/functions';
|
||||
|
||||
@@ -5,6 +6,7 @@ import {
|
||||
CANCEL_LOGIN,
|
||||
DISABLE_MODERATOR_LOGIN,
|
||||
ENABLE_MODERATOR_LOGIN,
|
||||
SET_TOKEN_AUTH_URL_SUCCESS,
|
||||
STOP_WAIT_FOR_OWNER,
|
||||
UPGRADE_ROLE_FINISHED,
|
||||
UPGRADE_ROLE_STARTED,
|
||||
@@ -18,9 +20,17 @@ export interface IAuthenticationState {
|
||||
thenableWithCancel?: {
|
||||
cancel: Function;
|
||||
};
|
||||
tokenAuthUrlSuccessful?: boolean;
|
||||
waitForOwnerTimeoutID?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the persistence of the feature {@code authentication}.
|
||||
*/
|
||||
PersistenceRegistry.register('features/authentication', {
|
||||
tokenAuthUrlSuccessful: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Listens for actions which change the state of the authentication feature.
|
||||
*
|
||||
@@ -43,6 +53,11 @@ ReducerRegistry.register<IAuthenticationState>('features/authentication',
|
||||
showModeratorLogin: true
|
||||
});
|
||||
|
||||
case SET_TOKEN_AUTH_URL_SUCCESS:
|
||||
return assign(state, {
|
||||
tokenAuthUrlSuccessful: action.value
|
||||
});
|
||||
|
||||
case STOP_WAIT_FOR_OWNER:
|
||||
return assign(state, {
|
||||
error: undefined,
|
||||
|
||||
@@ -354,7 +354,7 @@ export function e2eRttChanged(participant: Object, rtt: number) {
|
||||
* authLogin: string
|
||||
* }}
|
||||
*/
|
||||
export function authStatusChanged(authEnabled: boolean, authLogin?: string) {
|
||||
export function authStatusChanged(authEnabled: boolean, authLogin: string) {
|
||||
return {
|
||||
type: AUTH_STATUS_CHANGED,
|
||||
authEnabled,
|
||||
|
||||
@@ -14,7 +14,6 @@ import { sendAnalytics } from '../../analytics/functions';
|
||||
import { reloadNow } from '../../app/actions';
|
||||
import { IStore } from '../../app/types';
|
||||
import { login } from '../../authentication/actions.any';
|
||||
import { isTokenAuthEnabled } from '../../authentication/functions.any';
|
||||
import { removeLobbyChatParticipant } from '../../chat/actions.any';
|
||||
import { openDisplayNamePrompt } from '../../display-name/actions';
|
||||
import { isVpaasMeeting } from '../../jaas/functions';
|
||||
@@ -267,11 +266,8 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
descriptionKey = 'dialog.errorRoomCreationRestriction';
|
||||
} else if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.ROOM_UNAUTHENTICATED_ACCESS_DISABLED) {
|
||||
titleKey = 'dialog.unauthenticatedAccessDisabled';
|
||||
|
||||
if (isTokenAuthEnabled(getState())) {
|
||||
customActionNameKey = [ 'toolbar.login' ];
|
||||
customActionHandler = [ () => dispatch(login()) ]; // show login button if not jaas
|
||||
}
|
||||
customActionNameKey = [ 'toolbar.login' ];
|
||||
customActionHandler = [ () => dispatch(login()) ];
|
||||
}
|
||||
|
||||
dispatch(showErrorNotification({
|
||||
@@ -406,8 +402,7 @@ async function _connectionEstablished({ dispatch, getState }: IStore, next: Func
|
||||
email = getLocalParticipant(getState())?.email;
|
||||
}
|
||||
|
||||
// it may happen to be already set
|
||||
dispatch(authStatusChanged(true, email || getState()['features/base/conference'].authLogin || ''));
|
||||
dispatch(authStatusChanged(true, email || ''));
|
||||
}
|
||||
|
||||
// FIXME: Workaround for the web version. Currently, the creation of the
|
||||
|
||||
@@ -616,8 +616,8 @@ export interface IConfig {
|
||||
disabled?: boolean;
|
||||
numberOfVisibleTiles?: number;
|
||||
};
|
||||
tokenAuthInline?: boolean;
|
||||
tokenAuthUrl?: string;
|
||||
tokenAuthUrlAutoRedirect?: string;
|
||||
tokenGetUserInfoOutOfContext?: boolean;
|
||||
tokenLogoutUrl?: string;
|
||||
tokenRespectTenant?: boolean;
|
||||
@@ -637,7 +637,6 @@ export interface IConfig {
|
||||
transcription?: {
|
||||
autoCaptionOnTranscribe?: boolean;
|
||||
autoTranscribeOnRecord?: boolean;
|
||||
customLanguages?: object;
|
||||
disableClosedCaptions?: boolean;
|
||||
enabled?: boolean;
|
||||
preferredLanguage?: string;
|
||||
|
||||
@@ -51,16 +51,6 @@ export const CONNECTION_PROPERTIES_UPDATED = 'CONNECTION_PROPERTIES_UPDATED';
|
||||
*/
|
||||
export const CONNECTION_WILL_CONNECT = 'CONNECTION_WILL_CONNECT';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that the token for a connection is expired.
|
||||
*
|
||||
* {
|
||||
* type: CONNECTION_TOKEN_EXPIRED,
|
||||
* connection: JitsiConnection
|
||||
* }
|
||||
*/
|
||||
export const CONNECTION_TOKEN_EXPIRED = 'CONNECTION_TOKEN_EXPIRED';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the location URL of the application,
|
||||
* connection, conference, etc.
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
CONNECTION_ESTABLISHED,
|
||||
CONNECTION_FAILED,
|
||||
CONNECTION_PROPERTIES_UPDATED,
|
||||
CONNECTION_TOKEN_EXPIRED,
|
||||
CONNECTION_WILL_CONNECT,
|
||||
SET_LOCATION_URL,
|
||||
SET_PREFER_VISITOR
|
||||
@@ -240,9 +239,6 @@ export function _connectInternal(id?: string, password?: string) {
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.PROPERTIES_UPDATED,
|
||||
_onPropertiesUpdate);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_TOKEN_EXPIRED,
|
||||
_onTokenExpired);
|
||||
|
||||
/**
|
||||
* Unsubscribe the connection instance from
|
||||
@@ -327,16 +323,6 @@ export function _connectInternal(id?: string, password?: string) {
|
||||
dispatch(redirect(vnode, focusJid, username));
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection will resume.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onTokenExpired(): void {
|
||||
dispatch(_connectionTokenExpired(connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection properties were updated.
|
||||
*
|
||||
@@ -378,23 +364,6 @@ function _connectionWillConnect(connection: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when a connection token is expired.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The {@code JitsiConnection} token is expired.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: CONNECTION_TOKEN_EXPIRED,
|
||||
* connection: JitsiConnection
|
||||
* }}
|
||||
*/
|
||||
function _connectionTokenExpired(connection: Object) {
|
||||
return {
|
||||
type: CONNECTION_TOKEN_EXPIRED,
|
||||
connection
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when connection properties are updated.
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getCustomerDetails } from '../../jaas/actions.any';
|
||||
import { getJaasJWT, isVpaasMeeting } from '../../jaas/functions';
|
||||
import { navigateRoot } from '../../mobile/navigation/rootNavigationContainerRef';
|
||||
import { screen } from '../../mobile/navigation/routes';
|
||||
import { conferenceLeft } from '../conference/actions.native';
|
||||
import { conferenceWillLeave } from '../conference/actions.native';
|
||||
import { setJWT } from '../jwt/actions';
|
||||
import { JitsiConnectionErrors } from '../lib-jitsi-meet';
|
||||
|
||||
@@ -60,7 +60,7 @@ export function connect(id?: string, password?: string) {
|
||||
*/
|
||||
export function hangup(_requestFeedback = false) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
dispatch(conferenceWillLeave());
|
||||
dispatch(appNavigate(undefined));
|
||||
dispatch(conferenceLeft());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ export interface IConnectionState {
|
||||
getJid: () => string;
|
||||
getLogs: () => Object;
|
||||
initJitsiConference: Function;
|
||||
refreshToken: Function;
|
||||
removeFeature: Function;
|
||||
};
|
||||
error?: ConnectionFailedError;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './utils.any';
|
||||
@@ -29,3 +29,4 @@ export function isIpadMobileBrowser() {
|
||||
// @ts-ignore
|
||||
return isIosMobileBrowser() && Platform.isPad;
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { MIN_FILMSTRIP_RESIZE_WIDTH } from '../../filmstrip/constants';
|
||||
|
||||
/**
|
||||
* Detects if the current device has touch capability.
|
||||
* This includes smartphones, tablets, and laptops with touch screens.
|
||||
*
|
||||
* @returns {boolean} True if the device supports touch events.
|
||||
*/
|
||||
export function isTouchDevice(): boolean {
|
||||
// Check maxTouchPoints (most reliable for modern browsers)
|
||||
if ('maxTouchPoints' in navigator) {
|
||||
return navigator.maxTouchPoints > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if resize functionality should be enabled based on device capabilities
|
||||
* and screen size. On touch devices, resize is only enabled for larger screens.
|
||||
* On non-touch devices (desktop), resize is always enabled.
|
||||
*
|
||||
* @returns {boolean} True if resize functionality should be available to the user.
|
||||
*/
|
||||
export function shouldEnableResize(): boolean {
|
||||
const hasTouch = isTouchDevice();
|
||||
|
||||
// On non-touch devices (desktop), always enable resize
|
||||
if (!hasTouch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// On touch devices, only enable if screen is large enough.
|
||||
return window?.innerWidth >= MIN_FILMSTRIP_RESIZE_WIDTH;
|
||||
}
|
||||
|
||||
export * from './utils.any';
|
||||
@@ -20,21 +20,15 @@ export function setDelayedLoadOfAvatarUrl(avatarUrl?: string) {
|
||||
* Stores a specific JSON Web Token (JWT) into the redux store.
|
||||
*
|
||||
* @param {string} [jwt] - The JSON Web Token (JWT) to store.
|
||||
* @param {string} idToken - The ID Token to store.
|
||||
* @param {string} refreshToken - The Refresh Token to store.
|
||||
* @returns {{
|
||||
* type: SET_JWT,
|
||||
* jwt: (string|undefined),
|
||||
* idToken: (string|undefined),
|
||||
* refreshToken: (string|undefined)
|
||||
* jwt: (string|undefined)
|
||||
* }}
|
||||
*/
|
||||
export function setJWT(jwt?: string, idToken?: string, refreshToken?: string) {
|
||||
export function setJWT(jwt?: string) {
|
||||
return {
|
||||
type: SET_JWT,
|
||||
jwt,
|
||||
idToken,
|
||||
refreshToken
|
||||
jwt
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { AnyAction } from 'redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { isVpaasMeeting } from '../../jaas/functions';
|
||||
import { authStatusChanged } from '../conference/actions.any';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { SET_CONFIG } from '../config/actionTypes';
|
||||
import { CONNECTION_ESTABLISHED, SET_LOCATION_URL } from '../connection/actionTypes';
|
||||
@@ -40,8 +39,6 @@ StateListenerRegistry.register(
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const state = store.getState();
|
||||
|
||||
switch (action.type) {
|
||||
case SET_CONFIG:
|
||||
case SET_LOCATION_URL:
|
||||
@@ -49,6 +46,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
// have decided to store in the feature jwt
|
||||
return _setConfigOrLocationURL(store, next, action);
|
||||
case CONNECTION_ESTABLISHED: {
|
||||
const state = store.getState();
|
||||
const delayedLoadOfAvatarUrl = state['features/base/jwt'].delayedLoadOfAvatarUrl;
|
||||
|
||||
if (delayedLoadOfAvatarUrl) {
|
||||
@@ -58,7 +56,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
store.dispatch(setDelayedLoadOfAvatarUrl());
|
||||
store.dispatch(setKnownAvatarUrl(delayedLoadOfAvatarUrl));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SET_JWT:
|
||||
return _setJWT(store, next, action);
|
||||
@@ -152,7 +149,7 @@ function _setConfigOrLocationURL({ dispatch, getState }: IStore, next: Function,
|
||||
*/
|
||||
function _setJWT(store: IStore, next: Function, action: AnyAction) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { idToken, jwt, refreshToken, type, ...actionPayload } = action;
|
||||
const { jwt, type, ...actionPayload } = action;
|
||||
|
||||
if (!Object.keys(actionPayload).length) {
|
||||
const state = store.getState();
|
||||
@@ -213,32 +210,24 @@ function _setJWT(store: IStore, next: Function, action: AnyAction) {
|
||||
if (context.user && context.user.role === 'visitor') {
|
||||
action.preferVisitor = true;
|
||||
}
|
||||
} else if (jwtPayload.name || jwtPayload.picture || jwtPayload.email) {
|
||||
if (tokenGetUserInfoOutOfContext) {
|
||||
// there are some tokens (firebase) having picture and name on the main level.
|
||||
_overwriteLocalParticipant(store, {
|
||||
avatarURL: jwtPayload.picture,
|
||||
name: jwtPayload.name,
|
||||
email: jwtPayload.email
|
||||
});
|
||||
}
|
||||
|
||||
store.dispatch(authStatusChanged(true, jwtPayload.email));
|
||||
} else if (tokenGetUserInfoOutOfContext
|
||||
&& (jwtPayload.name || jwtPayload.picture || jwtPayload.email)) {
|
||||
// there are some tokens (firebase) having picture and name on the main level.
|
||||
_overwriteLocalParticipant(store, {
|
||||
avatarURL: jwtPayload.picture,
|
||||
name: jwtPayload.name,
|
||||
email: jwtPayload.email
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (typeof APP === 'undefined') {
|
||||
// The logic of restoring JWT overrides make sense only on mobile.
|
||||
// On Web it should eventually be restored from storage, but there's
|
||||
// no such use case yet.
|
||||
} else if (typeof APP === 'undefined') {
|
||||
// The logic of restoring JWT overrides make sense only on mobile.
|
||||
// On Web it should eventually be restored from storage, but there's
|
||||
// no such use case yet.
|
||||
|
||||
const { user } = state['features/base/jwt'];
|
||||
const { user } = state['features/base/jwt'];
|
||||
|
||||
user && _undoOverwriteLocalParticipant(store, user);
|
||||
}
|
||||
|
||||
// clears authLogin
|
||||
store.dispatch(authStatusChanged(true));
|
||||
user && _undoOverwriteLocalParticipant(store, user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { loginWithPopup } from '../../authentication/actions';
|
||||
import LoginQuestionDialog from '../../authentication/components/web/LoginQuestionDialog';
|
||||
import { getTokenAuthUrl, isTokenAuthEnabled, isTokenAuthInline } from '../../authentication/functions';
|
||||
import { hideNotification, showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../../notifications/constants';
|
||||
import { CONNECTION_TOKEN_EXPIRED } from '../connection/actionTypes';
|
||||
import { openDialog } from '../dialog/actions';
|
||||
import { browser } from '../lib-jitsi-meet';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { parseURIString } from '../util/uri';
|
||||
|
||||
import { setJWT } from './actions';
|
||||
import logger from './logger';
|
||||
|
||||
const PROMPT_LOGIN_NOTIFICATION_ID = 'PROMPT_LOGIN_NOTIFICATION_ID';
|
||||
|
||||
/**
|
||||
* Middleware to handle token expiration on web - prompts the user to re-authenticate.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register((store: IStore) => next => action => {
|
||||
if (action.type === CONNECTION_TOKEN_EXPIRED) {
|
||||
const state = store.getState();
|
||||
const jwt = state['features/base/jwt'].jwt;
|
||||
const refreshToken = state['features/base/jwt'].refreshToken;
|
||||
|
||||
if (typeof APP !== 'undefined' && jwt && isTokenAuthEnabled(state)) {
|
||||
const { connection, locationURL = { href: '' } as URL } = state['features/base/connection'];
|
||||
const { tenant } = parseURIString(locationURL.href) || {};
|
||||
const room = state['features/base/conference'].room;
|
||||
const dispatch = store.dispatch;
|
||||
|
||||
getTokenAuthUrl(
|
||||
state['features/base/config'],
|
||||
locationURL,
|
||||
{
|
||||
audioMuted: false,
|
||||
audioOnlyEnabled: false,
|
||||
skipPrejoin: true,
|
||||
videoMuted: false
|
||||
},
|
||||
room,
|
||||
tenant,
|
||||
refreshToken
|
||||
)
|
||||
.then((url: string | undefined) => {
|
||||
if (url) {
|
||||
dispatch(showNotification({
|
||||
descriptionKey: 'dialog.loginOnResume',
|
||||
titleKey: 'dialog.login',
|
||||
uid: PROMPT_LOGIN_NOTIFICATION_ID,
|
||||
customActionNameKey: [ 'dialog.login' ],
|
||||
customActionHandler: [ () => {
|
||||
store.dispatch(hideNotification(PROMPT_LOGIN_NOTIFICATION_ID));
|
||||
|
||||
if (isTokenAuthInline(state['features/base/config'])) {
|
||||
loginWithPopup(url)
|
||||
.then((result: { accessToken: string; idToken: string; refreshToken?: string; }) => {
|
||||
const token: string = result.accessToken;
|
||||
const idToken: string = result.idToken;
|
||||
const newRefreshToken: string | undefined = result.refreshToken;
|
||||
|
||||
dispatch(setJWT(token, idToken, newRefreshToken || refreshToken));
|
||||
|
||||
connection?.refreshToken(token)
|
||||
.catch((err: any) => {
|
||||
dispatch(setJWT());
|
||||
logger.error(err);
|
||||
});
|
||||
}).catch(logger.error);
|
||||
} else {
|
||||
dispatch(openDialog('LoginQuestionDialog', LoginQuestionDialog, {
|
||||
handler: () => {
|
||||
// Give time for the dialog to close.
|
||||
setTimeout(() => {
|
||||
if (browser.isElectron()) {
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
} ],
|
||||
appearance: NOTIFICATION_TYPE.ERROR
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
}
|
||||
})
|
||||
.catch(logger.error);
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
@@ -11,10 +11,8 @@ export interface IJwtState {
|
||||
};
|
||||
delayedLoadOfAvatarUrl?: string;
|
||||
group?: string;
|
||||
idToken?: string;
|
||||
jwt?: string;
|
||||
knownAvatarUrl?: string;
|
||||
refreshToken?: string;
|
||||
server?: string;
|
||||
tenant?: string;
|
||||
user?: {
|
||||
|
||||
@@ -60,6 +60,66 @@ const AVATAR_CHECKER_FUNCTIONS = [
|
||||
return null;
|
||||
}
|
||||
];
|
||||
/* eslint-enable arrow-body-style */
|
||||
|
||||
/**
|
||||
* Returns the list of active speakers that should be moved to the top of the sorted list of participants so that the
|
||||
* dominant speaker is visible always on the vertical filmstrip in stage layout.
|
||||
*
|
||||
* @param {Function | Object} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
|
||||
* retrieve the state.
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
export function getActiveSpeakersToBeDisplayed(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const {
|
||||
dominantSpeaker,
|
||||
fakeParticipants,
|
||||
sortedRemoteVirtualScreenshareParticipants,
|
||||
speakersList
|
||||
} = state['features/base/participants'];
|
||||
const { visibleRemoteParticipants } = state['features/filmstrip'];
|
||||
let activeSpeakers = new Map(speakersList);
|
||||
|
||||
// Do not re-sort the active speakers if dominant speaker is currently visible.
|
||||
if (dominantSpeaker && visibleRemoteParticipants.has(dominantSpeaker)) {
|
||||
return activeSpeakers;
|
||||
}
|
||||
let availableSlotsForActiveSpeakers = visibleRemoteParticipants.size;
|
||||
|
||||
if (activeSpeakers.has(dominantSpeaker ?? '')) {
|
||||
activeSpeakers.delete(dominantSpeaker ?? '');
|
||||
}
|
||||
|
||||
// Add dominant speaker to the beginning of the list (not including self) since the active speaker list is always
|
||||
// alphabetically sorted.
|
||||
if (dominantSpeaker && dominantSpeaker !== getLocalParticipant(state)?.id) {
|
||||
const updatedSpeakers = Array.from(activeSpeakers);
|
||||
|
||||
updatedSpeakers.splice(0, 0, [ dominantSpeaker, getParticipantById(state, dominantSpeaker)?.name ?? '' ]);
|
||||
activeSpeakers = new Map(updatedSpeakers);
|
||||
}
|
||||
|
||||
// Remove screenshares from the count.
|
||||
if (sortedRemoteVirtualScreenshareParticipants) {
|
||||
availableSlotsForActiveSpeakers -= sortedRemoteVirtualScreenshareParticipants.size * 2;
|
||||
for (const screenshare of Array.from(sortedRemoteVirtualScreenshareParticipants.keys())) {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare as string);
|
||||
|
||||
activeSpeakers.delete(ownerId);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove fake participants from the count.
|
||||
if (fakeParticipants) {
|
||||
availableSlotsForActiveSpeakers -= fakeParticipants.size;
|
||||
}
|
||||
const truncatedSpeakersList = Array.from(activeSpeakers).slice(0, availableSlotsForActiveSpeakers);
|
||||
|
||||
truncatedSpeakersList.sort((a: any, b: any) => a[1].localeCompare(b[1]));
|
||||
|
||||
return new Map(truncatedSpeakersList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the first loadable avatar URL for a participant.
|
||||
|
||||
@@ -68,7 +68,6 @@ const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [
|
||||
];
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
activeSpeakers: new Set<string>(),
|
||||
dominantSpeaker: undefined,
|
||||
fakeParticipants: new Map(),
|
||||
local: undefined,
|
||||
@@ -83,10 +82,10 @@ const DEFAULT_STATE = {
|
||||
remoteVideoSources: new Set<string>(),
|
||||
sortedRemoteVirtualScreenshareParticipants: new Map(),
|
||||
sortedRemoteParticipants: new Map(),
|
||||
speakersList: new Map()
|
||||
};
|
||||
|
||||
export interface IParticipantsState {
|
||||
activeSpeakers: Set<string>;
|
||||
dominantSpeaker?: string;
|
||||
fakeParticipants: Map<string, IParticipant>;
|
||||
local?: ILocalParticipant;
|
||||
@@ -101,6 +100,7 @@ export interface IParticipantsState {
|
||||
remoteVideoSources: Set<string>;
|
||||
sortedRemoteParticipants: Map<string, string>;
|
||||
sortedRemoteVirtualScreenshareParticipants: Map<string, string>;
|
||||
speakersList: Map<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,8 +157,22 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
const { participant } = action;
|
||||
const { id, previousSpeakers = [] } = participant;
|
||||
const { dominantSpeaker, local } = state;
|
||||
const activeSpeakers = new Set(previousSpeakers
|
||||
.filter((speakerId: string) => state.remote.has(speakerId) && (speakerId !== local?.id)));
|
||||
const newSpeakers = [ id, ...previousSpeakers ];
|
||||
const sortedSpeakersList: Array<Array<string>> = [];
|
||||
|
||||
for (const speaker of newSpeakers) {
|
||||
if (speaker !== local?.id) {
|
||||
const remoteParticipant = state.remote.get(speaker);
|
||||
|
||||
remoteParticipant
|
||||
&& sortedSpeakersList.push(
|
||||
[ speaker, _getDisplayName(state, remoteParticipant?.name) ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the remote speaker list sorted alphabetically.
|
||||
sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
|
||||
// Only one dominant speaker is allowed.
|
||||
if (dominantSpeaker) {
|
||||
@@ -169,7 +183,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
return {
|
||||
...state,
|
||||
dominantSpeaker: id, // @ts-ignore
|
||||
activeSpeakers
|
||||
speakersList: new Map(sortedSpeakersList)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -424,7 +438,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
}
|
||||
|
||||
// Remove the participant from the list of speakers.
|
||||
state.activeSpeakers.delete(id);
|
||||
state.speakersList.has(id) && state.speakersList.delete(id);
|
||||
|
||||
if (pinnedParticipant === id) {
|
||||
state.pinnedParticipant = undefined;
|
||||
|
||||
@@ -155,7 +155,6 @@ export const colorMap = {
|
||||
preMeetingBackground: 'surface02', // Pre-meeting screen container background
|
||||
preMeetingPreview: 'ui01', // Video preview in pre-meeting
|
||||
prejoinDialogBackground: 'uiBackground', // Prejoin dialog background
|
||||
prejoinPreviewBackground: 'uiBackground', // Prejoin video preview background (#040404)
|
||||
prejoinDialogDelimiter: 'ui03', // Prejoin dialog delimiter line
|
||||
prejoinDialogDelimiterText: 'text01', // Prejoin dialog delimiter text
|
||||
prejoinTitleText: 'text01', // Prejoin title text color
|
||||
@@ -221,9 +220,9 @@ export const colorMap = {
|
||||
|
||||
// Welcome Page
|
||||
welcomeBackground: 'surface01', // Welcome page background (same as uiBackground)
|
||||
welcomeCard: 'surface02', // Welcome page tab bar background
|
||||
welcomeCard: 'ui01', // Welcome page tab bar background
|
||||
welcomeTabActive: 'icon01', // Welcome page active tab icon
|
||||
welcomeTabInactive: 'ui03', // Welcome page inactive tab icon
|
||||
welcomeTabInactive: 'icon03', // Welcome page inactive tab icon
|
||||
|
||||
// ----- Form Components -----
|
||||
|
||||
@@ -357,9 +356,6 @@ export const colorMap = {
|
||||
// Reactions
|
||||
reactionsMenuBackground: 'ui01', // Reactions menu background
|
||||
reactionsMenuBorder: 'ui02', // Reactions menu border
|
||||
reactionsMenuButtonToggled: 'surface01', // Reactions menu button toggled state background
|
||||
reactionsMenuBoxShadow1: 'ui09', // Reactions menu box shadow primary
|
||||
reactionsMenuBoxShadow2: 'ui08', // Reactions menu box shadow secondary
|
||||
|
||||
// Recording / Live Stream
|
||||
recordingBackground: 'ui01', // Recording panel background
|
||||
@@ -432,7 +428,7 @@ export const colorMap = {
|
||||
securityDialogBorder: 'ui07', // Security dialog border color
|
||||
|
||||
// Deep Linking
|
||||
deepLinkingBackground: 'uiBackground', // Deep linking page content pane background (#1e1e1e)
|
||||
deepLinkingBackground: 'ui01', // Deep linking page content pane background
|
||||
deepLinkingBorder: 'ui03', // Deep linking page content pane border
|
||||
deepLinkingText: 'text01', // Deep linking page text
|
||||
deepLinkingSeparator: 'ui03', // Deep linking separator line
|
||||
@@ -509,9 +505,6 @@ export const colorMap = {
|
||||
// High-contrast
|
||||
icon04: 'surface01',
|
||||
|
||||
// SVG fill color
|
||||
iconSvgFill: 'icon01',
|
||||
|
||||
// Error
|
||||
iconError: 'action03',
|
||||
|
||||
|
||||
@@ -81,11 +81,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
'&:focus': {
|
||||
outline: 0,
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.inputFieldFocus}`,
|
||||
|
||||
'&::placeholder': {
|
||||
opacity: 0
|
||||
}
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.inputFieldFocus}`
|
||||
},
|
||||
|
||||
'&:disabled': {
|
||||
@@ -111,12 +107,12 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
'input::-webkit-outer-spin-button, input::-webkit-inner-spin-button': {
|
||||
'-webkit-appearance': 'none',
|
||||
WebkitAppearance: 'none',
|
||||
margin: 0
|
||||
},
|
||||
|
||||
'input[type=number]': {
|
||||
'-moz-appearance': 'textfield'
|
||||
MozAppearance: 'textfield'
|
||||
},
|
||||
|
||||
icon: {
|
||||
|
||||
@@ -13,25 +13,19 @@ export const commonStyles = (theme: Theme) => {
|
||||
return {
|
||||
':root': {
|
||||
// Inject semantic tokens as CSS custom properties for use in SCSS
|
||||
'--toolbox-background-color': theme.palette.toolboxBackground,
|
||||
'--drawer-background-color': theme.palette.drawerBackground,
|
||||
'--icon-svg-fill': theme.palette.iconSvgFill,
|
||||
'--overflow-menu-background-color': theme.palette.overflowMenuBackground,
|
||||
'--overflow-menu-item-disabled-color': theme.palette.overflowMenuItemDisabled,
|
||||
'--overflow-menu-item-hover-color': theme.palette.overflowMenuItemHover,
|
||||
'--overflow-menu-item-icon-color': theme.palette.overflowMenuItemIcon,
|
||||
'--overflow-menu-item-text-color': theme.palette.overflowMenuItemText,
|
||||
'--prejoin-preview-background': theme.palette.prejoinPreviewBackground,
|
||||
'--reactions-menu-background': theme.palette.reactionsMenuBackground,
|
||||
'--reactions-menu-box-shadow-1': theme.palette.reactionsMenuBoxShadow1,
|
||||
'--reactions-menu-box-shadow-2': theme.palette.reactionsMenuBoxShadow2,
|
||||
'--reactions-menu-button-toggled': theme.palette.reactionsMenuButtonToggled,
|
||||
'--toolbar-button-active-color': theme.palette.toolbarButtonActive,
|
||||
'--toolbar-button-color': theme.palette.toolbarButton,
|
||||
'--toolbar-button-hover-color': theme.palette.toolbarButtonHover,
|
||||
'--toolbar-icon-active-color': theme.palette.toolbarIconActive,
|
||||
'--toolbar-button-active-color': theme.palette.toolbarButtonActive,
|
||||
'--toolbar-icon-color': theme.palette.toolbarIcon,
|
||||
'--toolbar-icon-hover-color': theme.palette.toolbarIconHover,
|
||||
'--toolbox-background-color': theme.palette.toolboxBackground
|
||||
'--toolbar-icon-active-color': theme.palette.toolbarIconActive,
|
||||
'--overflow-menu-background-color': theme.palette.overflowMenuBackground,
|
||||
'--overflow-menu-item-text-color': theme.palette.overflowMenuItemText,
|
||||
'--overflow-menu-item-icon-color': theme.palette.overflowMenuItemIcon,
|
||||
'--overflow-menu-item-hover-color': theme.palette.overflowMenuItemHover,
|
||||
'--overflow-menu-item-disabled-color': theme.palette.overflowMenuItemDisabled
|
||||
},
|
||||
|
||||
'.empty-list': {
|
||||
|
||||
@@ -26,7 +26,6 @@ export interface IPalette {
|
||||
icon02: string;
|
||||
icon03: string;
|
||||
icon04: string;
|
||||
iconSvgFill: string;
|
||||
iconError: string;
|
||||
link01: string;
|
||||
link01Active: string;
|
||||
@@ -154,7 +153,6 @@ export interface IPalette {
|
||||
prejoinDialogBackground: string;
|
||||
prejoinDialogDelimiter: string;
|
||||
prejoinDialogDelimiterText: string;
|
||||
prejoinPreviewBackground: string;
|
||||
prejoinRecordingWarningText: string;
|
||||
prejoinRoomNameText: string;
|
||||
prejoinTitleText: string;
|
||||
@@ -315,9 +313,6 @@ export interface IPalette {
|
||||
pollsVotersText: string;
|
||||
reactionsMenuBackground: string;
|
||||
reactionsMenuBorder: string;
|
||||
reactionsMenuButtonToggled: string;
|
||||
reactionsMenuBoxShadow1: string;
|
||||
reactionsMenuBoxShadow2: string;
|
||||
recordingBackground: string;
|
||||
recordingHighlightButton: string;
|
||||
recordingHighlightButtonDisabled: string;
|
||||
|
||||
@@ -24,13 +24,3 @@ const JITSI_MEET_APPS = [
|
||||
export function isEmbedded(): boolean {
|
||||
return !JITSI_MEET_APPS.includes(getBundleId());
|
||||
}
|
||||
|
||||
/**
|
||||
* React Native has no concept of same-domain embedding. SDK consumers are
|
||||
* always treated as cross-domain embeddings.
|
||||
*
|
||||
* @returns {boolean} Always false in React Native.
|
||||
*/
|
||||
export function isEmbeddedFromSameDomain(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -10,17 +10,3 @@ export function isEmbedded(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether we are loaded in iframe with same parent domain.
|
||||
*
|
||||
* @returns {boolean} Whether the current page is loaded in an iframe with same parent domain.
|
||||
*/
|
||||
export function isEmbeddedFromSameDomain(): boolean {
|
||||
try {
|
||||
return window.self.location.host === window.parent.location.host;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ export const ADD_MESSAGE = 'ADD_MESSAGE';
|
||||
export const ADD_MESSAGE_REACTION = 'ADD_MESSAGE_REACTION';
|
||||
|
||||
/**
|
||||
* The type of the action which signals to clear Redux.
|
||||
* The type of the action which signals to clear messages in Redux.
|
||||
*
|
||||
* {
|
||||
* type: CLEAR_CHAT_STATE
|
||||
* type: CLEAR_MESSAGES
|
||||
* }
|
||||
*/
|
||||
export const CLEAR_CHAT_STATE = 'CLEAR_CHAT_STATE';
|
||||
export const CLEAR_MESSAGES = 'CLEAR_MESSAGES';
|
||||
|
||||
/**
|
||||
* The type of the action which signals the cancellation the chat panel.
|
||||
|
||||
@@ -7,7 +7,7 @@ import { LOBBY_CHAT_INITIALIZED } from '../lobby/constants';
|
||||
import {
|
||||
ADD_MESSAGE,
|
||||
ADD_MESSAGE_REACTION,
|
||||
CLEAR_CHAT_STATE,
|
||||
CLEAR_MESSAGES,
|
||||
CLOSE_CHAT,
|
||||
EDIT_MESSAGE,
|
||||
NOTIFY_PRIVATE_RECIPIENTS_CHANGED,
|
||||
@@ -93,15 +93,15 @@ export function editMessage(message: Object) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the chat features state from Redux.
|
||||
* Clears the chat messages in Redux.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CLEAR_CHAT_STATE
|
||||
* type: CLEAR_MESSAGES
|
||||
* }}
|
||||
*/
|
||||
export function clearChatState() {
|
||||
export function clearMessages() {
|
||||
return {
|
||||
type: CLEAR_CHAT_STATE
|
||||
type: CLEAR_MESSAGES
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { openDialog } from '../../base/dialog/actions';
|
||||
import { MEET_FEATURES } from '../../base/jwt/constants';
|
||||
import { IMessageGroup, groupMessagesBySender } from '../../base/util/messageGrouping';
|
||||
import { maybeShowPremiumFeatureDialog } from '../../jaas/actions';
|
||||
// @ts-ignore
|
||||
import { StartRecordingDialog } from '../../recording/components/Recording';
|
||||
import { setRequestingSubtitles } from '../../subtitles/actions.any';
|
||||
import { canStartSubtitles } from '../../subtitles/functions.any';
|
||||
@@ -65,10 +64,6 @@ const AbstractClosedCaptions = (Component: ComponentType<AbstractProps>) => () =
|
||||
groupMessagesBySender(filteredSubtitles), [ filteredSubtitles ]);
|
||||
|
||||
const startClosedCaptions = useCallback(() => {
|
||||
if (dispatch(maybeShowPremiumFeatureDialog(MEET_FEATURES.RECORDING))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAsyncTranscriptionEnabled) {
|
||||
dispatch(openDialog('StartRecordingDialog', StartRecordingDialog, {
|
||||
recordAudioAndVideo: false
|
||||
|
||||
@@ -4,7 +4,6 @@ import { connect, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { isTouchDevice, shouldEnableResize } from '../../../base/environment/utils';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconInfo, IconMessage, IconShareDoc, IconSubtitles } from '../../../base/icons/svg';
|
||||
import { getLocalParticipant, getRemoteParticipants, isPrivateChatEnabledSelf } from '../../../base/participants/functions';
|
||||
@@ -24,16 +23,7 @@ import {
|
||||
setUserChatWidth,
|
||||
toggleChat
|
||||
} from '../../actions.web';
|
||||
import {
|
||||
CHAT_DRAG_HANDLE_HEIGHT,
|
||||
CHAT_DRAG_HANDLE_OFFSET,
|
||||
CHAT_DRAG_HANDLE_WIDTH,
|
||||
CHAT_SIZE,
|
||||
CHAT_TOUCH_HANDLE_SIZE,
|
||||
ChatTabs,
|
||||
OPTION_GROUPCHAT,
|
||||
SMALL_WIDTH_THRESHOLD
|
||||
} from '../../constants';
|
||||
import { CHAT_SIZE, ChatTabs, OPTION_GROUPCHAT, SMALL_WIDTH_THRESHOLD } from '../../constants';
|
||||
import { getChatMaxSize, getFocusedTab, isChatDisabled } from '../../functions';
|
||||
import { IChatProps as AbstractProps } from '../../types';
|
||||
|
||||
@@ -114,12 +104,7 @@ interface IProps extends AbstractProps {
|
||||
_width: number;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles<{
|
||||
_isResizing: boolean;
|
||||
isTouch: boolean;
|
||||
resizeEnabled: boolean;
|
||||
width: number;
|
||||
}>()((theme, { _isResizing, isTouch, resizeEnabled, width }) => {
|
||||
const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme, { _isResizing, width }) => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.palette.chatBackground,
|
||||
@@ -130,15 +115,11 @@ const useStyles = makeStyles<{
|
||||
width: `${width}px`,
|
||||
zIndex: 300,
|
||||
|
||||
// On non-touch devices (desktop), show handle on hover
|
||||
// On touch devices, handle is always visible if resize is enabled
|
||||
...(!isTouch && {
|
||||
'&:hover, &:focus-within': {
|
||||
'& .dragHandleContainer': {
|
||||
visibility: 'visible'
|
||||
}
|
||||
'&:hover, &:focus-within': {
|
||||
'& .dragHandleContainer': {
|
||||
visibility: 'visible'
|
||||
}
|
||||
}),
|
||||
},
|
||||
|
||||
'@media (max-width: 580px)': {
|
||||
height: '100dvh',
|
||||
@@ -151,7 +132,7 @@ const useStyles = makeStyles<{
|
||||
|
||||
'*': {
|
||||
userSelect: 'text',
|
||||
'-webkit-user-select': 'text'
|
||||
WebkitUserSelect: 'text'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -202,23 +183,16 @@ const useStyles = makeStyles<{
|
||||
|
||||
dragHandleContainer: {
|
||||
height: '100%',
|
||||
// Touch devices need larger hit target but positioned to not take extra space
|
||||
width: isTouch ? `${CHAT_TOUCH_HANDLE_SIZE}px` : `${CHAT_DRAG_HANDLE_WIDTH}px`,
|
||||
width: '9px',
|
||||
backgroundColor: 'transparent',
|
||||
position: 'absolute',
|
||||
cursor: 'col-resize',
|
||||
display: resizeEnabled ? 'flex' : 'none', // Hide if resize not enabled
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
// On touch devices, always visible if resize enabled. On desktop, hidden by default
|
||||
visibility: (isTouch && resizeEnabled) ? 'visible' : 'hidden',
|
||||
// Position touch handle centered on offset from edge, maintaining same gap as non-touch
|
||||
right: isTouch
|
||||
? `${CHAT_DRAG_HANDLE_OFFSET - Math.floor((CHAT_TOUCH_HANDLE_SIZE - CHAT_DRAG_HANDLE_WIDTH) / 2)}px`
|
||||
: `${CHAT_DRAG_HANDLE_OFFSET}px`,
|
||||
visibility: 'hidden',
|
||||
right: '4px',
|
||||
top: 0,
|
||||
// Prevent touch scrolling while dragging
|
||||
touchAction: 'none',
|
||||
|
||||
'&:hover': {
|
||||
'& .dragHandle': {
|
||||
@@ -236,15 +210,10 @@ const useStyles = makeStyles<{
|
||||
},
|
||||
|
||||
dragHandle: {
|
||||
// Keep the same visual appearance on all devices
|
||||
backgroundColor: theme.palette.icon02,
|
||||
height: `${CHAT_DRAG_HANDLE_HEIGHT}px`,
|
||||
width: `${CHAT_DRAG_HANDLE_WIDTH / 3}px`,
|
||||
borderRadius: '1px',
|
||||
// Make more visible when actively shown
|
||||
...(isTouch && resizeEnabled && {
|
||||
backgroundColor: theme.palette.icon01
|
||||
})
|
||||
height: '100px',
|
||||
width: '3px',
|
||||
borderRadius: '1px'
|
||||
},
|
||||
|
||||
privateMessageRecipientsList: {
|
||||
@@ -277,10 +246,7 @@ const Chat = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
// Detect touch capability and screen size for resize functionality
|
||||
const isTouch = isTouchDevice();
|
||||
const resizeEnabled = shouldEnableResize();
|
||||
const { classes, cx } = useStyles({ _isResizing, width: _width, isTouch, resizeEnabled });
|
||||
const { classes, cx } = useStyles({ _isResizing, width: _width });
|
||||
const [ isMouseDown, setIsMouseDown ] = useState(false);
|
||||
const [ mousePosition, setMousePosition ] = useState<number | null>(null);
|
||||
const [ dragChatWidth, setDragChatWidth ] = useState<number | null>(null);
|
||||
@@ -316,21 +282,16 @@ const Chat = ({
|
||||
}, [ participants, defaultRemoteDisplayName, t, notifyTimestamp ]);
|
||||
|
||||
/**
|
||||
* Handles pointer down on the drag handle.
|
||||
* Supports both mouse and touch events via Pointer Events API.
|
||||
* Handles mouse down on the drag handle.
|
||||
*
|
||||
* @param {React.PointerEvent} e - The pointer down event.
|
||||
* @param {MouseEvent} e - The mouse down event.
|
||||
* @returns {void}
|
||||
*/
|
||||
const onDragHandlePointerDown = useCallback((e: React.PointerEvent) => {
|
||||
const onDragHandleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Capture the pointer to ensure we receive all pointer events
|
||||
// even if the pointer moves outside the element
|
||||
(e.target as HTMLElement).setPointerCapture(e.pointerId);
|
||||
|
||||
// Store the initial pointer position and chat width
|
||||
// Store the initial mouse position and chat width
|
||||
setIsMouseDown(true);
|
||||
setMousePosition(e.clientX);
|
||||
setDragChatWidth(_width);
|
||||
@@ -338,7 +299,7 @@ const Chat = ({
|
||||
// Indicate that resizing is in progress
|
||||
dispatch(setChatIsResizing(true));
|
||||
|
||||
// Add visual feedback that we're dragging (cursor for mouse, not visible on touch)
|
||||
// Add visual feedback that we're dragging
|
||||
document.body.style.cursor = 'col-resize';
|
||||
|
||||
// Disable text selection during resize
|
||||
@@ -346,12 +307,11 @@ const Chat = ({
|
||||
}, [ _width, dispatch ]);
|
||||
|
||||
/**
|
||||
* Drag handle pointer up handler.
|
||||
* Supports both mouse and touch events via Pointer Events API.
|
||||
* Drag handle mouse up handler.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
const onDragPointerUp = useCallback(() => {
|
||||
const onDragMouseUp = useCallback(() => {
|
||||
if (isMouseDown) {
|
||||
setIsMouseDown(false);
|
||||
dispatch(setChatIsResizing(false));
|
||||
@@ -363,13 +323,12 @@ const Chat = ({
|
||||
}, [ isMouseDown, dispatch ]);
|
||||
|
||||
/**
|
||||
* Handles drag handle pointer move.
|
||||
* Supports both mouse and touch events via Pointer Events API.
|
||||
* Handles drag handle mouse move.
|
||||
*
|
||||
* @param {PointerEvent} e - The pointermove event.
|
||||
* @param {MouseEvent} e - The mousemove event.
|
||||
* @returns {void}
|
||||
*/
|
||||
const onChatResize = useCallback(throttle((e: PointerEvent) => {
|
||||
const onChatResize = useCallback(throttle((e: MouseEvent) => {
|
||||
if (isMouseDown && mousePosition !== null && dragChatWidth !== null) {
|
||||
// For chat panel resizing on the left edge:
|
||||
// - Dragging left (decreasing X coordinate) should make the panel wider
|
||||
@@ -393,14 +352,14 @@ const Chat = ({
|
||||
|
||||
// Set up event listeners when component mounts
|
||||
useEffect(() => {
|
||||
document.addEventListener('pointerup', onDragPointerUp);
|
||||
document.addEventListener('pointermove', onChatResize);
|
||||
document.addEventListener('mouseup', onDragMouseUp);
|
||||
document.addEventListener('mousemove', onChatResize);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('pointerup', onDragPointerUp);
|
||||
document.removeEventListener('pointermove', onChatResize);
|
||||
document.removeEventListener('mouseup', onDragMouseUp);
|
||||
document.removeEventListener('mousemove', onChatResize);
|
||||
};
|
||||
}, [ onDragPointerUp, onChatResize ]);
|
||||
}, [ onDragMouseUp, onChatResize ]);
|
||||
|
||||
/**
|
||||
* Sends a text message.
|
||||
@@ -641,7 +600,7 @@ const Chat = ({
|
||||
(isMouseDown || _isResizing) && 'visible',
|
||||
'dragHandleContainer'
|
||||
) }
|
||||
onPointerDown = { onDragHandlePointerDown }>
|
||||
onMouseDown = { onDragHandleMouseDown }>
|
||||
<div className = { cx(classes.dragHandle, 'dragHandle') } />
|
||||
</div>
|
||||
</div> : null
|
||||
|
||||
@@ -33,23 +33,6 @@ export const MESSAGE_TYPE_REMOTE = 'remote';
|
||||
|
||||
export const SMALL_WIDTH_THRESHOLD = 580;
|
||||
|
||||
/**
|
||||
* Drag handle dimensions for resizable chat.
|
||||
*/
|
||||
export const CHAT_DRAG_HANDLE_WIDTH = 9;
|
||||
export const CHAT_DRAG_HANDLE_HEIGHT = 100;
|
||||
|
||||
/**
|
||||
* Touch target size for chat drag handle on touch devices.
|
||||
* Provides adequate hit area (44px) for comfortable tapping.
|
||||
*/
|
||||
export const CHAT_TOUCH_HANDLE_SIZE = 44;
|
||||
|
||||
/**
|
||||
* Offset from edge for positioning the chat drag handle.
|
||||
*/
|
||||
export const CHAT_DRAG_HANDLE_OFFSET = 4;
|
||||
|
||||
|
||||
/**
|
||||
* Lobby message type.
|
||||
|
||||
@@ -51,7 +51,7 @@ import {
|
||||
import {
|
||||
addMessage,
|
||||
addMessageReaction,
|
||||
clearChatState,
|
||||
clearMessages,
|
||||
closeChat,
|
||||
notifyPrivateRecipientsChanged,
|
||||
openChat,
|
||||
@@ -352,7 +352,7 @@ StateListenerRegistry.register(
|
||||
}
|
||||
|
||||
// Clear chat messages.
|
||||
dispatch(clearChatState());
|
||||
dispatch(clearMessages());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IVisitorChatParticipant } from '../visitors/types';
|
||||
import {
|
||||
ADD_MESSAGE,
|
||||
ADD_MESSAGE_REACTION,
|
||||
CLEAR_CHAT_STATE,
|
||||
CLEAR_MESSAGES,
|
||||
CLOSE_CHAT,
|
||||
EDIT_MESSAGE,
|
||||
NOTIFY_PRIVATE_RECIPIENTS_CHANGED,
|
||||
@@ -140,10 +140,11 @@ ReducerRegistry.register<IChatState>('features/chat', (state = DEFAULT_STATE, ac
|
||||
};
|
||||
}
|
||||
|
||||
case CLEAR_CHAT_STATE:
|
||||
case CLEAR_MESSAGES:
|
||||
return {
|
||||
...DEFAULT_STATE,
|
||||
width: state.width
|
||||
...state,
|
||||
lastReadMessage: undefined,
|
||||
messages: []
|
||||
};
|
||||
|
||||
case EDIT_MESSAGE: {
|
||||
|
||||
@@ -227,8 +227,7 @@ class Conference extends AbstractConference<IProps, State> {
|
||||
const {
|
||||
_audioOnlyEnabled,
|
||||
_showLobby,
|
||||
_startCarMode,
|
||||
navigation
|
||||
_startCarMode
|
||||
} = this.props;
|
||||
|
||||
if (!prevProps._showLobby && _showLobby) {
|
||||
@@ -237,10 +236,10 @@ class Conference extends AbstractConference<IProps, State> {
|
||||
|
||||
if (prevProps._showLobby && !_showLobby) {
|
||||
if (_audioOnlyEnabled && _startCarMode) {
|
||||
navigation.navigate(screen.conference.carmode);
|
||||
} else {
|
||||
navigate(screen.conference.main);
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(screen.conference.main);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,8 @@ import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { getConferenceNameForTitle } from '../../../base/conference/functions';
|
||||
import { hangup } from '../../../base/connection/actions.web';
|
||||
import { isMobileBrowser } from '../../../base/environment/utils.web';
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import AudioTracksContainer from '../../../base/media/components/web/AudioTracksContainer';
|
||||
import { setColorAlpha } from '../../../base/util/helpers';
|
||||
import { openChat, setFocusedTab } from '../../../chat/actions.web';
|
||||
import Chat from '../../../chat/components/web/Chat';
|
||||
@@ -259,8 +258,10 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
id = 'videospace'
|
||||
onTouchStart = { this._onVideospaceTouchStart }>
|
||||
<LargeVideo />
|
||||
<StageFilmstrip />
|
||||
<ScreenshareFilmstrip />
|
||||
<MainFilmstrip />
|
||||
</div>
|
||||
<AudioTracksContainer />
|
||||
<span
|
||||
aria-level = { 1 }
|
||||
className = 'sr-only'
|
||||
@@ -299,7 +300,7 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
</>)
|
||||
}
|
||||
</div>
|
||||
<AudioTracksContainer />
|
||||
|
||||
{ _showPrejoin || _showLobby || (
|
||||
<>
|
||||
<span
|
||||
@@ -318,7 +319,9 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
</JitsiPortal>
|
||||
: this.renderNotificationsContainer())
|
||||
}
|
||||
|
||||
<CalleeInfoContainer />
|
||||
|
||||
{ shouldShowPrejoin(this.props) && <Prejoin />}
|
||||
{ (_showLobby && !_showVisitorsQueue) && <LobbyScreen />}
|
||||
{ _showVisitorsQueue && <VisitorsQueue />}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconCloudUpload } from '../../../base/icons/svg';
|
||||
import Tooltip from '../../../base/tooltip/components/Tooltip';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.web';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.web';
|
||||
@@ -227,20 +226,16 @@ const FileSharing = () => {
|
||||
)
|
||||
}
|
||||
{
|
||||
<Tooltip
|
||||
containerClassName = { classes.uploadButton }
|
||||
content = { isUploadEnabled ? t('fileSharing.uploadFile') : t('fileSharing.uploadDisabled') }
|
||||
position = 'top'>
|
||||
isUploadEnabled && (
|
||||
<Button
|
||||
accessibilityLabel = { t('fileSharing.uploadFile') }
|
||||
className = { classes.uploadButton }
|
||||
disabled = { !isUploadEnabled }
|
||||
labelKey = { 'fileSharing.uploadFile' }
|
||||
onClick = { handleClick }
|
||||
onKeyPress = { handleKeyPress }
|
||||
ref = { uploadButtonRef }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
} from '../base/icons/svg';
|
||||
import { MEET_FEATURES } from '../base/jwt/constants';
|
||||
import { isJwtFeatureEnabled } from '../base/jwt/functions';
|
||||
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
import { showErrorNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../notifications/constants';
|
||||
import { iAmVisitor } from '../visitors/functions';
|
||||
@@ -25,7 +24,7 @@ import { MAX_FILE_SIZE } from './constants';
|
||||
export function isFileSharingEnabled(state: IReduxState) {
|
||||
const { fileSharing } = state['features/base/config'] ?? {};
|
||||
|
||||
return Boolean(fileSharing?.enabled && fileSharing?.apiUrl && !isInBreakoutRoom(state));
|
||||
return Boolean(fileSharing?.enabled && fileSharing?.apiUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,9 +72,6 @@ StateListenerRegistry.register(
|
||||
type: _FILE_REMOVED,
|
||||
fileId
|
||||
});
|
||||
|
||||
// Notify external API about file deletion (for remote participants)
|
||||
APP.API.notifyFileDeleted(fileId);
|
||||
});
|
||||
|
||||
conference.on(JitsiConferenceEvents.FILE_SHARING_FILES_RECEIVED, (files: object) => {
|
||||
@@ -86,11 +83,6 @@ StateListenerRegistry.register(
|
||||
files,
|
||||
localParticipantId: localParticipant?.id
|
||||
});
|
||||
|
||||
// Notify external API about existing files (for participants joining late)
|
||||
Object.values(files).forEach((file: IFileMetadata) => {
|
||||
APP.API.notifyFileUploaded(file);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -160,8 +152,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
privateMessage: false,
|
||||
timestamp: file.timestamp
|
||||
}));
|
||||
|
||||
APP.API.notifyFileUploaded(file);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -188,9 +178,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
fileId
|
||||
});
|
||||
|
||||
// Notify external API about file deletion (for the local participant who deleted it)
|
||||
APP.API.notifyFileDeleted(fileId);
|
||||
|
||||
const { fileSharing } = state['features/base/config'];
|
||||
const sessionId = conference.getMeetingUniqueId();
|
||||
|
||||
|
||||
@@ -60,19 +60,16 @@ export function setRemoteParticipants(participants: Array<string>) {
|
||||
*
|
||||
* @param {number} startIndex - The start index from the remote participants array.
|
||||
* @param {number} endIndex - The end index from the remote participants array.
|
||||
* @param {number} fullyVisibleCount - The number of fully visible participants (excluding partially visible).
|
||||
* @returns {{
|
||||
* type: SET_VISIBLE_REMOTE_PARTICIPANTS,
|
||||
* startIndex: number,
|
||||
* endIndex: number,
|
||||
* fullyVisibleCount: number
|
||||
* endIndex: number
|
||||
* }}
|
||||
*/
|
||||
export function setVisibleRemoteParticipants(startIndex: number, endIndex: number, fullyVisibleCount?: number) {
|
||||
export function setVisibleRemoteParticipants(startIndex: number, endIndex: number) {
|
||||
return {
|
||||
type: SET_VISIBLE_REMOTE_PARTICIPANTS,
|
||||
startIndex,
|
||||
endIndex,
|
||||
fullyVisibleCount
|
||||
endIndex
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
||||
import { getHideSelfView } from '../../../base/settings/functions.any';
|
||||
import { isToolboxVisible } from '../../../toolbox/functions.native';
|
||||
import { setVisibleRemoteParticipants } from '../../actions.native';
|
||||
import { calculateFullyVisibleParticipantsCount } from '../../functions.any';
|
||||
import {
|
||||
getFilmstripDimensions,
|
||||
isFilmstripVisible,
|
||||
@@ -191,7 +190,7 @@ class Filmstrip extends PureComponent<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onViewableItemsChanged({ viewableItems = [] }: { viewableItems: ViewToken[]; }) {
|
||||
const { _aspectRatio, _clientWidth, _clientHeight, _disableSelfView } = this.props;
|
||||
const { _disableSelfView } = this.props;
|
||||
|
||||
if (!this._separateLocalThumbnail && !_disableSelfView && viewableItems[0]?.index === 0) {
|
||||
// Skip the local thumbnail.
|
||||
@@ -206,31 +205,13 @@ class Filmstrip extends PureComponent<IProps> {
|
||||
let startIndex = Number(viewableItems[0].index);
|
||||
let endIndex = Number(viewableItems[viewableItems.length - 1].index);
|
||||
|
||||
// Calculate fully visible count (excluding partially visible tiles)
|
||||
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
|
||||
const { height: thumbnailHeight, width: thumbnailWidth, margin } = styles.thumbnail;
|
||||
const { height, width } = this._getDimensions();
|
||||
|
||||
// Calculate item size and container size based on layout orientation
|
||||
const itemSize = isNarrowAspectRatio
|
||||
? thumbnailWidth + (2 * margin) // Horizontal layout
|
||||
: thumbnailHeight + (2 * margin); // Vertical layout
|
||||
const containerSize = isNarrowAspectRatio ? width : height;
|
||||
|
||||
const fullyVisibleCount = calculateFullyVisibleParticipantsCount(
|
||||
startIndex,
|
||||
endIndex,
|
||||
containerSize,
|
||||
itemSize
|
||||
);
|
||||
|
||||
if (!this._separateLocalThumbnail && !_disableSelfView) {
|
||||
// We are off by one in the remote participants array.
|
||||
startIndex -= 1;
|
||||
endIndex -= 1;
|
||||
}
|
||||
|
||||
this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex, fullyVisibleCount));
|
||||
this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { ITrack } from '../../../tracks/types';
|
||||
import { MEDIA_TYPE } from '../../constants';
|
||||
|
||||
import AudioTrack from './AudioTrack';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import AudioTrack from '../../../base/media/components/web/AudioTrack';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import { ITrack } from '../../../base/tracks/types';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AudioTracksContainer}.
|
||||
@@ -10,7 +10,7 @@ import { withStyles } from 'tss-react/mui';
|
||||
import { ACTION_SHORTCUT_TRIGGERED, createShortcutEvent, createToolbarEvent } from '../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../analytics/functions';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { isMobileBrowser, isTouchDevice, shouldEnableResize } from '../../../base/environment/utils.web';
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconArrowDown, IconArrowUp } from '../../../base/icons/svg';
|
||||
@@ -32,19 +32,13 @@ import {
|
||||
import {
|
||||
ASPECT_RATIO_BREAKPOINT,
|
||||
DEFAULT_FILMSTRIP_WIDTH,
|
||||
DRAG_HANDLE_HEIGHT,
|
||||
DRAG_HANDLE_TOP_PANEL_HEIGHT,
|
||||
DRAG_HANDLE_TOP_PANEL_WIDTH,
|
||||
DRAG_HANDLE_WIDTH,
|
||||
FILMSTRIP_TYPE,
|
||||
MIN_STAGE_VIEW_HEIGHT,
|
||||
MIN_STAGE_VIEW_WIDTH,
|
||||
TILE_HORIZONTAL_MARGIN,
|
||||
TILE_VERTICAL_MARGIN,
|
||||
TOP_FILMSTRIP_HEIGHT,
|
||||
TOUCH_DRAG_HANDLE_PADDING
|
||||
TOP_FILMSTRIP_HEIGHT
|
||||
} from '../../constants';
|
||||
import { calculateFullyVisibleParticipantsCount } from '../../functions.any';
|
||||
import {
|
||||
getVerticalViewMaxWidth,
|
||||
isFilmstripDisabled,
|
||||
@@ -52,26 +46,12 @@ import {
|
||||
shouldRemoteVideosBeVisible
|
||||
} from '../../functions.web';
|
||||
|
||||
import AudioTracksContainer from './AudioTracksContainer';
|
||||
import Thumbnail from './Thumbnail';
|
||||
import ThumbnailWrapper from './ThumbnailWrapper';
|
||||
|
||||
|
||||
const BACKGROUND_COLOR = 'rgba(51, 51, 51, .5)';
|
||||
const TOUCH_DEVICE_PADDING = {
|
||||
paddingLeft: `${TOUCH_DRAG_HANDLE_PADDING}px`,
|
||||
paddingRight: `${TOUCH_DRAG_HANDLE_PADDING}px`,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0
|
||||
};
|
||||
const TOUCH_DEVICE_TOP_PANEL_PADDING = {
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
paddingTop: `${TOUCH_DRAG_HANDLE_PADDING}px`,
|
||||
paddingBottom: `${TOUCH_DRAG_HANDLE_PADDING}px`
|
||||
};
|
||||
const NON_TOUCH_DEVICE_PANEL = {
|
||||
pading: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the styles for the component.
|
||||
@@ -81,14 +61,6 @@ const NON_TOUCH_DEVICE_PANEL = {
|
||||
* @returns {Object}
|
||||
*/
|
||||
function styles(theme: Theme, props: IProps) {
|
||||
const { _topPanelFilmstrip: isTopPanel } = props;
|
||||
|
||||
const _isTouchDevice = isTouchDevice();
|
||||
const resizeEnabled = shouldEnableResize();
|
||||
const handlePaddding = _isTouchDevice
|
||||
? (isTopPanel ? TOUCH_DEVICE_TOP_PANEL_PADDING : TOUCH_DEVICE_PADDING)
|
||||
: NON_TOUCH_DEVICE_PANEL;
|
||||
|
||||
const result = {
|
||||
toggleFilmstripContainer: {
|
||||
display: 'flex',
|
||||
@@ -122,7 +94,7 @@ function styles(theme: Theme, props: IProps) {
|
||||
margin: 0,
|
||||
border: 'none',
|
||||
|
||||
'-webkit-appearance': 'none',
|
||||
WebkitAppearance: 'none' as const,
|
||||
|
||||
'& svg': {
|
||||
fill: theme.palette.icon01
|
||||
@@ -150,27 +122,23 @@ function styles(theme: Theme, props: IProps) {
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
|
||||
// On touch devices, handle is always visible via base styles, so no hover needed.
|
||||
// On desktop, show handle on hover/focus.
|
||||
...(!_isTouchDevice && {
|
||||
'&:hover, &:focus-within': {
|
||||
'& .resizable-filmstrip': {
|
||||
backgroundColor: BACKGROUND_COLOR
|
||||
},
|
||||
'&:hover, &:focus-within': {
|
||||
'& .resizable-filmstrip': {
|
||||
backgroundColor: BACKGROUND_COLOR
|
||||
},
|
||||
|
||||
'& .filmstrip-hover': {
|
||||
backgroundColor: BACKGROUND_COLOR
|
||||
},
|
||||
'& .filmstrip-hover': {
|
||||
backgroundColor: BACKGROUND_COLOR
|
||||
},
|
||||
|
||||
'& .toggleFilmstripContainer': {
|
||||
opacity: 1
|
||||
},
|
||||
'& .toggleFilmstripContainer': {
|
||||
opacity: 1
|
||||
},
|
||||
|
||||
'& .dragHandleContainer': {
|
||||
visibility: 'visible' as const
|
||||
}
|
||||
'& .dragHandleContainer': {
|
||||
visibility: 'visible' as const
|
||||
}
|
||||
}),
|
||||
},
|
||||
|
||||
'.horizontal-filmstrip &.hidden': {
|
||||
bottom: '-50px',
|
||||
@@ -219,22 +187,14 @@ function styles(theme: Theme, props: IProps) {
|
||||
|
||||
dragHandleContainer: {
|
||||
height: '100%',
|
||||
width: `${DRAG_HANDLE_WIDTH}px`,
|
||||
width: '9px',
|
||||
backgroundColor: 'transparent',
|
||||
position: 'relative' as const,
|
||||
cursor: 'col-resize',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
// On touch devices, always visible if resize enabled. On desktop, hidden by default
|
||||
visibility: (_isTouchDevice && resizeEnabled) ? 'visible' as const : 'hidden' as const,
|
||||
marginLeft: 0,
|
||||
marginTop: 0,
|
||||
// Touch devices get padding for easier tapping
|
||||
// Vertical filmstrip: left/right padding. Top panel: top/bottom padding.
|
||||
...handlePaddding,
|
||||
// Prevent touch scrolling while dragging
|
||||
touchAction: 'none',
|
||||
visibility: 'hidden' as const,
|
||||
|
||||
'&:hover': {
|
||||
'& .dragHandle': {
|
||||
@@ -253,21 +213,20 @@ function styles(theme: Theme, props: IProps) {
|
||||
'&.top-panel': {
|
||||
order: 2,
|
||||
width: '100%',
|
||||
height: `${DRAG_HANDLE_WIDTH}px`,
|
||||
height: '9px',
|
||||
cursor: 'row-resize',
|
||||
|
||||
'& .dragHandle': {
|
||||
height: `${DRAG_HANDLE_TOP_PANEL_HEIGHT}px`,
|
||||
width: `${DRAG_HANDLE_TOP_PANEL_WIDTH}px`
|
||||
height: '3px',
|
||||
width: '100px'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
dragHandle: {
|
||||
// Keep the same visual appearance on all devices
|
||||
backgroundColor: theme.palette.filmstripDragHandle,
|
||||
height: `${DRAG_HANDLE_HEIGHT}px`,
|
||||
width: `${DRAG_HANDLE_WIDTH / 3}px`,
|
||||
height: '100px',
|
||||
width: '3px',
|
||||
borderRadius: '1px'
|
||||
}
|
||||
};
|
||||
@@ -354,11 +313,6 @@ export interface IProps extends WithTranslation {
|
||||
*/
|
||||
_isToolboxVisible: Boolean;
|
||||
|
||||
/**
|
||||
* Whether the device has touch capability.
|
||||
*/
|
||||
_isTouchDevice?: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the current layout is vertical filmstrip.
|
||||
*/
|
||||
@@ -384,6 +338,11 @@ export interface IProps extends WithTranslation {
|
||||
*/
|
||||
_maxTopPanelHeight: number;
|
||||
|
||||
/**
|
||||
* Whethere reduced UI feature is enabled or not.
|
||||
*/
|
||||
_reducedUI: boolean;
|
||||
|
||||
/**
|
||||
* The participants in the call.
|
||||
*/
|
||||
@@ -399,11 +358,6 @@ export interface IProps extends WithTranslation {
|
||||
*/
|
||||
_resizableFilmstrip: boolean;
|
||||
|
||||
/**
|
||||
* Whether resize functionality should be enabled based on device and screen size.
|
||||
*/
|
||||
_resizeEnabled?: boolean;
|
||||
|
||||
/**
|
||||
* The number of rows in tile view.
|
||||
*/
|
||||
@@ -537,10 +491,8 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
this._onGridItemsRendered = this._onGridItemsRendered.bind(this);
|
||||
this._onListItemsRendered = this._onListItemsRendered.bind(this);
|
||||
this._onToggleButtonTouch = this._onToggleButtonTouch.bind(this);
|
||||
this._onDragHandlePointerDown = this._onDragHandlePointerDown.bind(this);
|
||||
this._onDragHandleClick = this._onDragHandleClick.bind(this);
|
||||
this._onDragHandleTouchStart = this._onDragHandleTouchStart.bind(this);
|
||||
this._onDragPointerUp = this._onDragPointerUp.bind(this);
|
||||
this._onDragHandleMouseDown = this._onDragHandleMouseDown.bind(this);
|
||||
this._onDragMouseUp = this._onDragMouseUp.bind(this);
|
||||
this._onFilmstripResize = this._onFilmstripResize.bind(this);
|
||||
|
||||
this._throttledResize = throttle(
|
||||
@@ -564,10 +516,10 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
handler: this._onShortcutToggleFilmstrip
|
||||
}));
|
||||
|
||||
document.addEventListener('pointerup', this._onDragPointerUp);
|
||||
document.addEventListener('mouseup', this._onDragMouseUp);
|
||||
|
||||
// @ts-ignore
|
||||
document.addEventListener('pointermove', this._throttledResize);
|
||||
document.addEventListener('mousemove', this._throttledResize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -578,10 +530,10 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
override componentWillUnmount() {
|
||||
this.props.dispatch(unregisterShortcut('F'));
|
||||
|
||||
document.removeEventListener('pointerup', this._onDragPointerUp);
|
||||
document.removeEventListener('mouseup', this._onDragMouseUp);
|
||||
|
||||
// @ts-ignore
|
||||
document.removeEventListener('pointermove', this._throttledResize);
|
||||
document.removeEventListener('mousemove', this._throttledResize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -599,6 +551,7 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
_filmstripDisabled,
|
||||
_localScreenShareId,
|
||||
_mainFilmstripVisible,
|
||||
_reducedUI,
|
||||
_resizableFilmstrip,
|
||||
_topPanelFilmstrip,
|
||||
_topPanelMaxHeight,
|
||||
@@ -642,6 +595,13 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
// FIX: Until we move AudioTracksContainer to a more global place,
|
||||
// we apply this css hot fix to hide Filmstrip but keep AudioTracksContainer in the DOM,
|
||||
// so we don't have audio problems when reduced UI is enabled.
|
||||
if (_reducedUI) {
|
||||
filmstripStyle.display = 'none';
|
||||
}
|
||||
|
||||
let toolbar: React.ReactNode = null;
|
||||
|
||||
if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled
|
||||
@@ -718,37 +678,27 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
(isMouseDown || _alwaysShowResizeBar) && 'visible',
|
||||
_topPanelFilmstrip && 'top-panel')
|
||||
}
|
||||
onClick = { this._onDragHandleClick }
|
||||
onPointerDown = { this._onDragHandlePointerDown }
|
||||
onTouchStart = { this._onDragHandleTouchStart }>
|
||||
onMouseDown = { this._onDragHandleMouseDown }>
|
||||
<div className = { clsx(classes.dragHandle, 'dragHandle') } />
|
||||
</div>
|
||||
{filmstrip}
|
||||
</div>
|
||||
: filmstrip
|
||||
}
|
||||
<AudioTracksContainer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles pointer down on the drag handle.
|
||||
* Supports both mouse and touch events via Pointer Events API.
|
||||
* Handles mouse down on the drag handle.
|
||||
*
|
||||
* @param {React.PointerEvent} e - The pointer down event.
|
||||
* @param {MouseEvent} e - The mouse down event.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDragHandlePointerDown(e: React.PointerEvent) {
|
||||
_onDragHandleMouseDown(e: React.MouseEvent) {
|
||||
const { _topPanelFilmstrip, _topPanelHeight, _verticalFilmstripWidth } = this.props;
|
||||
|
||||
// Prevent toolbar from appearing and stop event propagation
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Capture the pointer to ensure we receive all pointer events
|
||||
// even if the pointer moves outside the element
|
||||
(e.target as HTMLElement).setPointerCapture(e.pointerId);
|
||||
|
||||
this.setState({
|
||||
isMouseDown: true,
|
||||
mousePosition: _topPanelFilmstrip ? e.clientY : e.clientX,
|
||||
@@ -759,33 +709,11 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents click events on drag handle from triggering toolbar.
|
||||
*
|
||||
* @param {React.MouseEvent} e - The click event.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDragHandleClick(e: React.MouseEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents touch start events on drag handle from triggering toolbar.
|
||||
*
|
||||
* @param {React.TouchEvent} e - The touch start event.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDragHandleTouchStart(e: React.TouchEvent) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drag handle pointer up handler.
|
||||
* Supports both mouse and touch events via Pointer Events API.
|
||||
* Drag handle mouse up handler.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDragPointerUp() {
|
||||
_onDragMouseUp() {
|
||||
if (this.state.isMouseDown) {
|
||||
this.setState({
|
||||
isMouseDown: false
|
||||
@@ -795,13 +723,12 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles drag handle pointer move.
|
||||
* Supports both mouse and touch events via Pointer Events API.
|
||||
* Handles drag handle mouse move.
|
||||
*
|
||||
* @param {PointerEvent} e - The pointermove event.
|
||||
* @param {MouseEvent} e - The mousemove event.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onFilmstripResize(e: PointerEvent) {
|
||||
_onFilmstripResize(e: React.MouseEvent) {
|
||||
if (this.state.isMouseDown) {
|
||||
const {
|
||||
dispatch,
|
||||
@@ -930,32 +857,10 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
*/
|
||||
_onListItemsRendered({ visibleStartIndex, visibleStopIndex }: {
|
||||
visibleStartIndex: number; visibleStopIndex: number; }) {
|
||||
const {
|
||||
dispatch,
|
||||
_currentLayout,
|
||||
_filmstripWidth,
|
||||
_filmstripHeight,
|
||||
_thumbnailWidth,
|
||||
_thumbnailHeight,
|
||||
} = this.props;
|
||||
|
||||
// Calculate fully visible count (excluding partially visible tiles)
|
||||
const isHorizontal = _currentLayout === LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW;
|
||||
const itemSize = isHorizontal
|
||||
? _thumbnailWidth + TILE_HORIZONTAL_MARGIN
|
||||
: _thumbnailHeight + TILE_VERTICAL_MARGIN;
|
||||
const containerSize = isHorizontal ? _filmstripWidth : _filmstripHeight;
|
||||
|
||||
const fullyVisibleCount = calculateFullyVisibleParticipantsCount(
|
||||
visibleStartIndex,
|
||||
visibleStopIndex,
|
||||
containerSize,
|
||||
itemSize
|
||||
);
|
||||
|
||||
const { dispatch } = this.props;
|
||||
const { startIndex, stopIndex } = this._calculateIndices(visibleStartIndex, visibleStopIndex);
|
||||
|
||||
dispatch(setVisibleRemoteParticipants(startIndex, stopIndex, fullyVisibleCount));
|
||||
dispatch(setVisibleRemoteParticipants(startIndex, stopIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1228,6 +1133,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const _currentLayout = getCurrentLayout(state);
|
||||
const _isVerticalFilmstrip = _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW
|
||||
|| (filmstripType === FILMSTRIP_TYPE.MAIN && _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW);
|
||||
const { reducedUI } = state['features/base/responsive-ui'];
|
||||
|
||||
return {
|
||||
_className: className,
|
||||
@@ -1245,6 +1151,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
_mainFilmstripVisible: notDisabled,
|
||||
_maxFilmstripWidth: videoSpaceWidth - MIN_STAGE_VIEW_WIDTH,
|
||||
_maxTopPanelHeight: clientHeight - MIN_STAGE_VIEW_HEIGHT,
|
||||
_reducedUI: reducedUI,
|
||||
_remoteParticipantsLength: _remoteParticipants?.length ?? 0,
|
||||
_topPanelHeight: topPanelHeight.current,
|
||||
_topPanelMaxHeight: topPanelHeight.current || TOP_FILMSTRIP_HEIGHT,
|
||||
@@ -1256,4 +1163,4 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(withStyles(Filmstrip, styles)));
|
||||
export default withStyles(translate(connect(_mapStateToProps)(Filmstrip)), styles);
|
||||
|
||||
@@ -353,7 +353,7 @@ const defaultStyles = (theme: Theme) => {
|
||||
|
||||
tintBackground: {
|
||||
position: 'absolute' as const,
|
||||
zIndex: 0,
|
||||
zIndex: 1,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: theme.palette.thumbnailTintBackground,
|
||||
|
||||
@@ -269,19 +269,6 @@ export const MIN_STAGE_VIEW_HEIGHT = 700;
|
||||
*/
|
||||
export const MIN_STAGE_VIEW_WIDTH = 800;
|
||||
|
||||
/**
|
||||
* Drag handle dimensions for resizable filmstrip.
|
||||
*/
|
||||
export const DRAG_HANDLE_WIDTH = 9;
|
||||
export const DRAG_HANDLE_HEIGHT = 100;
|
||||
export const DRAG_HANDLE_TOP_PANEL_HEIGHT = 3;
|
||||
export const DRAG_HANDLE_TOP_PANEL_WIDTH = 100;
|
||||
|
||||
/**
|
||||
* Touch padding added to each side of drag handle for easier tapping on touch devices.
|
||||
*/
|
||||
export const TOUCH_DRAG_HANDLE_PADDING = 6;
|
||||
|
||||
/**
|
||||
* Horizontal margin used for the vertical filmstrip.
|
||||
*/
|
||||
@@ -311,9 +298,3 @@ export const MAX_ACTIVE_PARTICIPANTS = 6;
|
||||
* Top filmstrip default height.
|
||||
*/
|
||||
export const TOP_FILMSTRIP_HEIGHT = 180;
|
||||
|
||||
/**
|
||||
* Minimum screen width needed for functional filmstrip resizing.
|
||||
* Calculated as stage minimum + filmstrip minimum (800px + 120px = 920px).
|
||||
*/
|
||||
export const MIN_FILMSTRIP_RESIZE_WIDTH = MIN_STAGE_VIEW_WIDTH + DEFAULT_FILMSTRIP_WIDTH;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { IReduxState, IStore } from '../app/types';
|
||||
import { getParticipantById, getVirtualScreenshareParticipantOwnerId } from '../base/participants/functions';
|
||||
import {
|
||||
getActiveSpeakersToBeDisplayed,
|
||||
getVirtualScreenshareParticipantOwnerId
|
||||
} from '../base/participants/functions';
|
||||
|
||||
import { setRemoteParticipants } from './actions';
|
||||
import { isFilmstripScrollVisible } from './functions';
|
||||
@@ -30,70 +33,44 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
|
||||
}
|
||||
|
||||
const {
|
||||
activeSpeakers,
|
||||
dominantSpeaker,
|
||||
fakeParticipants,
|
||||
sortedRemoteParticipants
|
||||
} = state['features/base/participants'];
|
||||
const config = state['features/base/config'];
|
||||
const defaultRemoteDisplayName = config?.defaultRemoteDisplayName ?? 'Fellow Jitster';
|
||||
const dominant = dominantSpeaker ? getParticipantById(state, dominantSpeaker) : undefined;
|
||||
let dominantSpeakerSlot = 0;
|
||||
const previousSpeakers = new Set(activeSpeakers);
|
||||
const remoteParticipants = new Map(sortedRemoteParticipants);
|
||||
const screenShareParticipants = sortedRemoteVirtualScreenshareParticipants
|
||||
? [ ...sortedRemoteVirtualScreenshareParticipants.keys() ] : [];
|
||||
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
|
||||
const speakers = new Array<string>();
|
||||
const { fullyVisibleRemoteParticipantsCount } = state['features/filmstrip'];
|
||||
const speakers = getActiveSpeakersToBeDisplayed(state);
|
||||
|
||||
for (const screenshare of screenShareParticipants) {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
|
||||
|
||||
remoteParticipants.delete(ownerId);
|
||||
remoteParticipants.delete(screenshare);
|
||||
speakers.delete(ownerId);
|
||||
}
|
||||
|
||||
for (const sharedVideo of sharedVideos) {
|
||||
remoteParticipants.delete(sharedVideo);
|
||||
}
|
||||
for (const speaker of speakers.keys()) {
|
||||
remoteParticipants.delete(speaker);
|
||||
}
|
||||
|
||||
// Always update the order of the thubmnails.
|
||||
const participantsWithScreenShare = screenShareParticipants.reduce<string[]>((acc, screenshare) => {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
|
||||
|
||||
acc.push(ownerId);
|
||||
acc.push(screenshare);
|
||||
remoteParticipants.delete(ownerId);
|
||||
remoteParticipants.delete(screenshare);
|
||||
previousSpeakers.delete(ownerId);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
for (const sharedVideo of sharedVideos) {
|
||||
remoteParticipants.delete(sharedVideo);
|
||||
}
|
||||
|
||||
if (dominant && !dominant.local && participantsWithScreenShare.indexOf(dominant.id) === -1) {
|
||||
dominantSpeakerSlot = 1;
|
||||
remoteParticipants.delete(dominant.id);
|
||||
speakers.push(dominant.id);
|
||||
}
|
||||
|
||||
// Find the number of slots available for speakers. Use fullyVisibleRemoteParticipantsCount to exclude partially
|
||||
// visible tiles, ensuring dominant speaker is placed on a fully visible tile.
|
||||
const slotsForSpeakers
|
||||
= fullyVisibleRemoteParticipantsCount
|
||||
- (screenShareParticipants.length * 2)
|
||||
- sharedVideos.length
|
||||
- dominantSpeakerSlot;
|
||||
|
||||
// Construct the list of speakers to be shown.
|
||||
if (slotsForSpeakers > 0) {
|
||||
Array.from(previousSpeakers).slice(0, slotsForSpeakers).forEach((speakerId: string) => {
|
||||
speakers.push(speakerId);
|
||||
remoteParticipants.delete(speakerId);
|
||||
});
|
||||
speakers.sort((a: string, b: string) => {
|
||||
return (getParticipantById(state, a)?.name ?? defaultRemoteDisplayName)
|
||||
.localeCompare(getParticipantById(state, b)?.name ?? defaultRemoteDisplayName);
|
||||
});
|
||||
}
|
||||
|
||||
// Always update the order of the thumbnails.
|
||||
reorderedParticipants = [
|
||||
...participantsWithScreenShare,
|
||||
...sharedVideos,
|
||||
...speakers,
|
||||
...Array.from(speakers.keys()),
|
||||
...Array.from(remoteParticipants.keys())
|
||||
];
|
||||
|
||||
@@ -131,31 +108,3 @@ export function isTileViewModeDisabled(state: IReduxState) {
|
||||
|
||||
return tileView.disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the count of fully visible participants, excluding any partially visible tiles.
|
||||
* This respects the actual rendered items from the list component while accounting for
|
||||
* container padding/gaps.
|
||||
*
|
||||
* @param {number} visibleStartIndex - The start index of visible items.
|
||||
* @param {number} visibleEndIndex - The end index of visible items.
|
||||
* @param {number} containerSize - The width or height of the filmstrip container.
|
||||
* @param {number} itemSize - The width or height of each item including margin.
|
||||
* @returns {number} - The count of fully visible participants (at least 1).
|
||||
*/
|
||||
export function calculateFullyVisibleParticipantsCount(
|
||||
visibleStartIndex: number,
|
||||
visibleEndIndex: number,
|
||||
containerSize: number,
|
||||
itemSize: number
|
||||
): number {
|
||||
// Current visible count from the list component (includes any partially visible tile)
|
||||
const currentVisibleCount = visibleEndIndex - visibleStartIndex + 1;
|
||||
|
||||
// Theoretical max that can fit fully in the container
|
||||
const maxFullyVisible = Math.floor(containerSize / itemSize);
|
||||
|
||||
// Fully visible count is the minimum of actual visible and max that can fit fully
|
||||
// Ensure at least 1 if there are any visible items
|
||||
return Math.max(1, Math.min(currentVisibleCount, maxFullyVisible));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Theme } from '@mui/material/styles';
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { isTouchDevice, shouldEnableResize } from '../base/environment/utils';
|
||||
import { isMobileBrowser } from '../base/environment/utils';
|
||||
import { MEDIA_TYPE } from '../base/media/constants';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
DEFAULT_LOCAL_TILE_ASPECT_RATIO,
|
||||
DISPLAY_AVATAR,
|
||||
DISPLAY_VIDEO,
|
||||
DRAG_HANDLE_WIDTH,
|
||||
FILMSTRIP_GRID_BREAKPOINT,
|
||||
FILMSTRIP_TYPE,
|
||||
INDICATORS_TOOLTIP_POSITION,
|
||||
@@ -46,7 +45,6 @@ import {
|
||||
TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES,
|
||||
TILE_VIEW_GRID_HORIZONTAL_MARGIN,
|
||||
TILE_VIEW_GRID_VERTICAL_MARGIN,
|
||||
TOUCH_DRAG_HANDLE_PADDING,
|
||||
VERTICAL_VIEW_HORIZONTAL_MARGIN
|
||||
} from './constants';
|
||||
|
||||
@@ -623,7 +621,6 @@ export function getIndicatorsTooltipPosition(thumbnailType?: string) {
|
||||
|
||||
/**
|
||||
* Returns whether or not the filmstrip is resizable.
|
||||
* On touch devices, resize is only enabled for larger screens (tablets, not phones).
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {boolean}
|
||||
@@ -632,7 +629,7 @@ export function isFilmstripResizable(state: IReduxState) {
|
||||
const { filmstrip } = state['features/base/config'];
|
||||
const _currentLayout = getCurrentLayout(state);
|
||||
|
||||
return !filmstrip?.disableResizable && shouldEnableResize()
|
||||
return !filmstrip?.disableResizable && !isMobileBrowser()
|
||||
&& (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW || _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW);
|
||||
}
|
||||
|
||||
@@ -665,13 +662,8 @@ export function getVerticalViewMaxWidth(state: IReduxState) {
|
||||
|
||||
// Adding 4px for the border-right and margin-right.
|
||||
// On non-resizable filmstrip add 4px for the left margin and border.
|
||||
// Also adding 7px for the scrollbar.
|
||||
// Drag handle: DRAG_HANDLE_WIDTH + padding (TOUCH_DRAG_HANDLE_PADDING on each side for touch)
|
||||
const dragHandleWidth = isTouchDevice()
|
||||
? DRAG_HANDLE_WIDTH + (TOUCH_DRAG_HANDLE_PADDING * 2)
|
||||
: DRAG_HANDLE_WIDTH;
|
||||
|
||||
maxWidth += (_verticalViewGrid ? 0 : 11) + (_resizableFilmstrip ? dragHandleWidth : 4);
|
||||
// Also adding 7px for the scrollbar. Also adding 9px for the drag handle.
|
||||
maxWidth += (_verticalViewGrid ? 0 : 11) + (_resizableFilmstrip ? 9 : 4);
|
||||
|
||||
return maxWidth;
|
||||
}
|
||||
|
||||
@@ -158,15 +158,6 @@ const DEFAULT_STATE = {
|
||||
*/
|
||||
visibleRemoteParticipants: new Set<string>(),
|
||||
|
||||
/**
|
||||
* The number of fully visible remote participants (excluding partially visible ones).
|
||||
* Used for calculating speaker slots to avoid placing dominant speaker on partially visible tiles.
|
||||
*
|
||||
* @public
|
||||
* @type {number}
|
||||
*/
|
||||
fullyVisibleRemoteParticipantsCount: 0,
|
||||
|
||||
/**
|
||||
* The width of the resizable filmstrip.
|
||||
*
|
||||
@@ -210,7 +201,6 @@ export interface IFilmstripState {
|
||||
pinned?: boolean;
|
||||
}>;
|
||||
enabled: boolean;
|
||||
fullyVisibleRemoteParticipantsCount: number;
|
||||
horizontalViewDimensions: {
|
||||
hasScroll?: boolean;
|
||||
local?: IDimensions;
|
||||
@@ -311,7 +301,7 @@ ReducerRegistry.register<IFilmstripState>(
|
||||
}
|
||||
};
|
||||
case SET_VISIBLE_REMOTE_PARTICIPANTS: {
|
||||
const { endIndex, startIndex, fullyVisibleCount } = action;
|
||||
const { endIndex, startIndex } = action;
|
||||
const { remoteParticipants } = state;
|
||||
const visibleRemoteParticipants = new Set(remoteParticipants.slice(startIndex, endIndex + 1));
|
||||
|
||||
@@ -319,8 +309,7 @@ ReducerRegistry.register<IFilmstripState>(
|
||||
...state,
|
||||
visibleParticipantsStartIndex: startIndex,
|
||||
visibleParticipantsEndIndex: endIndex,
|
||||
visibleRemoteParticipants,
|
||||
fullyVisibleRemoteParticipantsCount: fullyVisibleCount ?? visibleRemoteParticipants.size
|
||||
visibleRemoteParticipants
|
||||
};
|
||||
}
|
||||
case PARTICIPANT_LEFT: {
|
||||
|
||||
@@ -70,15 +70,10 @@ export function getDialInConferenceID(
|
||||
* phone number strings, as the second one should not be used and is deprecated.
|
||||
*/
|
||||
export function getDialInNumbers(
|
||||
url?: string,
|
||||
roomName?: string,
|
||||
mucURL?: string
|
||||
url: string,
|
||||
roomName: string,
|
||||
mucURL: string
|
||||
): Promise<any> {
|
||||
|
||||
if (!url || !roomName || !mucURL) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const separator = url.includes('?') ? '&' : '?';
|
||||
|
||||
// when roomName and mucURL are available
|
||||
|
||||
@@ -16,8 +16,7 @@ import {
|
||||
import { INVITE_TYPES } from './constants';
|
||||
import {
|
||||
invitePeopleAndChatRooms,
|
||||
inviteSipEndpoints,
|
||||
isDialInEnabled
|
||||
inviteSipEndpoints
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { IInvitee } from './types';
|
||||
@@ -211,7 +210,7 @@ export function updateDialInNumbers() {
|
||||
const { numbersFetched } = state['features/invite'];
|
||||
const mucURL = hosts?.muc;
|
||||
|
||||
if (numbersFetched || !isDialInEnabled(state)) {
|
||||
if (numbersFetched || !dialInConfCodeUrl || !dialInNumbersUrl || !mucURL) {
|
||||
// URLs for fetching dial in numbers not defined
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -496,26 +496,6 @@ export function isDialOutEnabled(state: IReduxState): boolean {
|
||||
return isJwtFeatureEnabled(state, MEET_FEATURES.OUTBOUND_CALL, false) && conference?.isSIPCallingSupported();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if dial out is currently enabled or not.
|
||||
*
|
||||
* @param {IReduxState} state - Current state.
|
||||
* @returns {boolean} Indication of whether dial out is currently enabled.
|
||||
*/
|
||||
export function isDialInEnabled(state: IReduxState): boolean {
|
||||
const dialInDisabled = state['features/base/conference']
|
||||
.conference?.getMetadataHandler()?.getMetadata()?.dialinEnabled === false;
|
||||
|
||||
if (dialInDisabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { dialInConfCodeUrl, dialInNumbersUrl, hosts } = state['features/base/config'];
|
||||
const mucURL = hosts?.muc;
|
||||
|
||||
return Boolean(dialInConfCodeUrl && dialInNumbersUrl && mucURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if inviting sip endpoints is enabled or not.
|
||||
*
|
||||
@@ -654,7 +634,7 @@ export function getShareInfoText(
|
||||
const { locationURL = {} } = state['features/base/connection'];
|
||||
const mucURL = hosts?.muc;
|
||||
|
||||
if (skipDialIn || !isDialInEnabled(state)) {
|
||||
if (skipDialIn || !dialInConfCodeUrl || !dialInNumbersUrl || !mucURL) {
|
||||
// URLs for fetching dial in numbers not defined.
|
||||
return Promise.resolve(infoText);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { conferenceWillJoin } from '../../base/conference/actions.any';
|
||||
import { conferenceWillJoin } from '../../base/conference/actions';
|
||||
import { getConferenceName } from '../../base/conference/functions';
|
||||
import { IJitsiConference } from '../../base/conference/reducer';
|
||||
import { getSecurityUiConfig } from '../../base/config/functions.any';
|
||||
@@ -11,8 +11,9 @@ import { getLocalParticipant } from '../../base/participants/functions';
|
||||
import { getFieldValue } from '../../base/react/functions';
|
||||
import { updateSettings } from '../../base/settings/actions';
|
||||
import { IMessage } from '../../chat/types';
|
||||
import { isDeviceStatusVisible } from '../../prejoin/functions.any';
|
||||
import { isDeviceStatusVisible } from '../../prejoin/functions';
|
||||
import { cancelKnocking, joinWithPassword, onSendMessage, setPasswordJoinFailed, startKnocking } from '../actions';
|
||||
import { getLobbyConfig } from '../functions';
|
||||
|
||||
export const SCREEN_STATES = {
|
||||
EDIT: 1,
|
||||
@@ -27,6 +28,11 @@ export interface IProps {
|
||||
*/
|
||||
_deviceStatusVisible: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show the hangup button.
|
||||
*/
|
||||
_hangUp?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the message that display name is required is shown.
|
||||
*/
|
||||
@@ -450,6 +456,7 @@ export function _mapStateToProps(state: IReduxState) {
|
||||
const { disableLobbyPassword } = getSecurityUiConfig(state);
|
||||
const showCopyUrlButton = inviteEnabledFlag || !disableInviteFunctions;
|
||||
const deviceStatusVisible = isDeviceStatusVisible(state);
|
||||
const { showHangUp = true } = getLobbyConfig(state);
|
||||
const { membersOnly, lobbyWaitingForHost } = state['features/base/conference'];
|
||||
const { isLobbyChatActive, lobbyMessageRecipient, messages } = state['features/chat'];
|
||||
const { showModeratorLogin } = state['features/authentication'];
|
||||
@@ -461,6 +468,7 @@ export function _mapStateToProps(state: IReduxState) {
|
||||
_lobbyChatMessages: messages,
|
||||
_lobbyMessageRecipient: lobbyMessageRecipient?.name,
|
||||
_login: showModeratorLogin && !state['features/base/jwt'].jwt,
|
||||
_hangUp: showHangUp,
|
||||
_isLobbyChatActive: isLobbyChatActive,
|
||||
_meetingName: getConferenceName(state),
|
||||
_membersOnlyConference: membersOnly,
|
||||
|
||||
@@ -19,6 +19,7 @@ import { navigate }
|
||||
from '../../../mobile/navigation/components/lobby/LobbyNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { preJoinStyles } from '../../../prejoin/components/native/styles';
|
||||
import HangupButton from '../../../toolbox/components/HangupButton';
|
||||
import AudioMuteButton from '../../../toolbox/components/native/AudioMuteButton';
|
||||
import VideoMuteButton from '../../../toolbox/components/native/VideoMuteButton';
|
||||
import AbstractLobbyScreen, {
|
||||
@@ -220,12 +221,19 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderToolbarButtons() {
|
||||
const { _hangUp } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { preJoinStyles.toolboxContainer as ViewStyle }>
|
||||
<AudioMuteButton
|
||||
styles = { preJoinStyles.buttonStylesBorderless } />
|
||||
<VideoMuteButton
|
||||
styles = { preJoinStyles.buttonStylesBorderless } />
|
||||
{
|
||||
_hangUp
|
||||
&& <HangupButton
|
||||
styles = { preJoinStyles.buttonStylesBorderless } />
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -336,7 +336,6 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
const result = next(action);
|
||||
|
||||
if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
|
||||
dispatch(hideLobbyScreen());
|
||||
dispatch(
|
||||
showNotification({
|
||||
appearance: NOTIFICATION_TYPE.ERROR,
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
SET_PASSWORD
|
||||
} from '../base/conference/actionTypes';
|
||||
import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
|
||||
@@ -69,6 +70,7 @@ ReducerRegistry.register<ILobbyState>('features/lobby', (state = DEFAULT_STATE,
|
||||
}
|
||||
case CONFERENCE_JOINED:
|
||||
case CONFERENCE_LEFT:
|
||||
case CONFERENCE_WILL_LEAVE:
|
||||
return {
|
||||
...state,
|
||||
isDisplayNameRequiredError: false,
|
||||
|
||||
@@ -54,7 +54,7 @@ const useStyles = makeStyles<IStylesProps>()((theme, { isChatOpen }) => {
|
||||
fontWeight: 600,
|
||||
height: '100%',
|
||||
|
||||
[[ '& > *:first-child', '& > *:last-child' ] as any]: {
|
||||
[[ '& > *:first-of-type', '& > *:last-of-type' ] as any]: {
|
||||
flexShrink: 0
|
||||
},
|
||||
|
||||
@@ -116,11 +116,11 @@ const useStyles = makeStyles<IStylesProps>()((theme, { isChatOpen }) => {
|
||||
antiCollapse: {
|
||||
fontSize: 0,
|
||||
|
||||
'&:first-child': {
|
||||
'&:first-of-type': {
|
||||
display: 'none'
|
||||
},
|
||||
|
||||
'&:first-child + *': {
|
||||
'&:first-of-type + *': {
|
||||
marginTop: 0
|
||||
}
|
||||
},
|
||||
|
||||
@@ -33,9 +33,11 @@ import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { openDisplayNamePrompt } from '../../../display-name/actions';
|
||||
import BrandingImageBackground from '../../../dynamic-branding/components/native/BrandingImageBackground';
|
||||
import LargeVideo from '../../../large-video/components/LargeVideo.native';
|
||||
import { getLobbyConfig } from '../../../lobby/functions';
|
||||
import HeaderNavigationButton from '../../../mobile/navigation/components/HeaderNavigationButton';
|
||||
import { navigateRoot } from '../../../mobile/navigation/rootNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import HangupButton from '../../../toolbox/components/HangupButton';
|
||||
import AudioMuteButton from '../../../toolbox/components/native/AudioMuteButton';
|
||||
import VideoMuteButton from '../../../toolbox/components/native/VideoMuteButton';
|
||||
import { isDisplayNameRequired, isRoomNameEnabled } from '../../functions.native';
|
||||
@@ -60,6 +62,9 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
const isDisplayNameReadonly = useSelector(isNameReadOnly);
|
||||
const roomName = useSelector((state: IReduxState) => getConferenceName(state));
|
||||
const roomNameEnabled = useSelector((state: IReduxState) => isRoomNameEnabled(state));
|
||||
const { showHangUp: showHangUpLobby = true } = useSelector((state: IReduxState) => getLobbyConfig(state));
|
||||
const { showHangUp: showHangUpPrejoin = true } = useSelector((state: IReduxState) => state['features/base/config'].prejoinConfig || {});
|
||||
const { knocking } = useSelector((state: IReduxState) => state['features/lobby']);
|
||||
const participantName = localParticipant?.name;
|
||||
const [ displayName, setDisplayName ]
|
||||
= useState(participantName || '');
|
||||
@@ -181,6 +186,11 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
styles = { styles.buttonStylesBorderless } />
|
||||
<VideoMuteButton
|
||||
styles = { styles.buttonStylesBorderless } />
|
||||
{
|
||||
(knocking ? showHangUpLobby : showHangUpPrejoin)
|
||||
&& <HangupButton
|
||||
styles = { styles.buttonStylesBorderless } />
|
||||
}
|
||||
</View>
|
||||
{
|
||||
showDisplayNameInput && <Input
|
||||
|
||||
@@ -502,8 +502,7 @@ function mapStateToProps(state: IReduxState) {
|
||||
const { joiningInProgress } = state['features/prejoin'];
|
||||
const { room } = state['features/base/conference'];
|
||||
const { unsafeRoomConsent } = state['features/base/premeeting'];
|
||||
const config = state['features/base/config'];
|
||||
const { showPrejoinWarning: showRecordingWarning } = config.recordings ?? {};
|
||||
const { showPrejoinWarning: showRecordingWarning } = state['features/base/config'].recordings ?? {};
|
||||
|
||||
return {
|
||||
deviceStatusVisible: isDeviceStatusVisible(state),
|
||||
|
||||
@@ -5,8 +5,6 @@ import { createRecordingDialogEvent } from '../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../analytics/functions';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { IJitsiConference } from '../../../base/conference/reducer';
|
||||
import { MEET_FEATURES } from '../../../base/jwt/constants';
|
||||
import { isJwtFeatureEnabled } from '../../../base/jwt/functions';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { updateDropboxToken } from '../../../dropbox/actions';
|
||||
import { getDropboxData, getNewAccessToken, isEnabled as isDropboxEnabled } from '../../../dropbox/functions.any';
|
||||
@@ -175,8 +173,10 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
|
||||
|
||||
let selectedRecordingService = '';
|
||||
|
||||
// Select the default recording service based on what's actually available.
|
||||
if (this.props._fileRecordingsServiceEnabled) {
|
||||
// TODO: Potentially check if we need to handle changes of
|
||||
// _fileRecordingsServiceEnabled and _areIntegrationsEnabled()
|
||||
if (this.props._fileRecordingsServiceEnabled
|
||||
|| !this._areIntegrationsEnabled()) {
|
||||
selectedRecordingService = RECORDING_TYPES.JITSI_REC_SERVICE;
|
||||
} else if (this._areIntegrationsEnabled()) {
|
||||
if (props._localRecordingEnabled && supportsLocalRecording()) {
|
||||
@@ -184,12 +184,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
|
||||
} else {
|
||||
selectedRecordingService = RECORDING_TYPES.DROPBOX;
|
||||
}
|
||||
} else if (props._localRecordingEnabled && supportsLocalRecording()) {
|
||||
selectedRecordingService = RECORDING_TYPES.LOCAL;
|
||||
}
|
||||
// If no service is available, selectedRecordingService stays '' and
|
||||
// the Start Recording button will be disabled.
|
||||
|
||||
|
||||
this.state = {
|
||||
isTokenValid: false,
|
||||
@@ -402,9 +397,6 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
|
||||
}
|
||||
case RECORDING_TYPES.LOCAL: {
|
||||
dispatch(startLocalVideoRecording(this.state.localRecordingOnlySelf));
|
||||
_conference?.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
|
||||
isTranscribingEnabled: this.state.shouldRecordTranscription
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -482,9 +474,7 @@ export function mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
_isDropboxEnabled: isDropboxEnabled(state),
|
||||
_localRecordingEnabled: !localRecording?.disable,
|
||||
_rToken: state['features/dropbox'].rToken ?? '',
|
||||
recordAudioAndVideo:
|
||||
isJwtFeatureEnabled(state, MEET_FEATURES.RECORDING, false)
|
||||
? _ownProps.recordAudioAndVideo ?? recordings?.recordAudioAndVideo ?? true : false,
|
||||
recordAudioAndVideo: _ownProps.recordAudioAndVideo ?? recordings?.recordAudioAndVideo ?? true,
|
||||
_subtitlesLanguage,
|
||||
_tokenExpireDate: state['features/dropbox'].expireDate,
|
||||
_token: state['features/dropbox'].token ?? ''
|
||||
|
||||
@@ -279,7 +279,6 @@ const LocalRecordingManager: ILocalRecordingManager = {
|
||||
// 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?.close();
|
||||
this.audioContext = undefined;
|
||||
this.audioDestination = undefined;
|
||||
this.startTime = undefined;
|
||||
|
||||
@@ -51,11 +51,6 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow transcription-only start even without a recording service selected.
|
||||
if (!selectedRecordingService && shouldRecordTranscription) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
override render() {
|
||||
const {
|
||||
_canStartTranscribing,
|
||||
_localRecordingAvailable,
|
||||
_renderRecording,
|
||||
integrationsEnabled
|
||||
} = this.props;
|
||||
const hasRecordingService = _renderRecording || _localRecordingAvailable || integrationsEnabled;
|
||||
const transcriptionOnly = !hasRecordingService && _canStartTranscribing;
|
||||
const _renderRecording = this.props._renderRecording;
|
||||
|
||||
return (
|
||||
<Container className = 'recording-dialog'>
|
||||
@@ -57,57 +50,25 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
|
||||
</>
|
||||
)}
|
||||
{ this._renderLocalRecordingContent() }
|
||||
{ transcriptionOnly && this._renderTranscriptionOnly() }
|
||||
{ hasRecordingService && <> { this._renderAdvancedOptions() } </> }
|
||||
{ _renderRecording && <> { this._renderAdvancedOptions() } </> }
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the transcription toggle directly when no recording service
|
||||
* is available but transcription is.
|
||||
*
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
_renderTranscriptionOnly() {
|
||||
const { shouldRecordTranscription, t } = this.props;
|
||||
|
||||
return (
|
||||
<div className = 'recording-header'>
|
||||
<label
|
||||
className = 'recording-title-no-space'
|
||||
htmlFor = 'recording-switch-transcription'>
|
||||
{ t('recording.recordTranscription') }
|
||||
</label>
|
||||
<Switch
|
||||
checked = { shouldRecordTranscription }
|
||||
className = 'recording-switch'
|
||||
id = 'recording-switch-transcription'
|
||||
onChange = { this._onTranscriptionSwitchChange } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the switch for saving the transcription.
|
||||
*
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
_renderAdvancedOptions() {
|
||||
if (!this._canStartTranscribing()) {
|
||||
return null;
|
||||
}
|
||||
const { selectedRecordingService } = this.props;
|
||||
const validService = selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE
|
||||
|| selectedRecordingService === RECORDING_TYPES.LOCAL
|
||||
|| !selectedRecordingService;
|
||||
|
||||
if (!validService) {
|
||||
if (selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE || !this._canStartTranscribing()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { showAdvancedOptions } = this.state;
|
||||
const { _renderRecording, shouldRecordAudioAndVideo, shouldRecordTranscription, t } = this.props;
|
||||
const { shouldRecordAudioAndVideo, shouldRecordTranscription, t } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -139,20 +100,18 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
|
||||
id = 'recording-switch-transcription'
|
||||
onChange = { this._onTranscriptionSwitchChange } />
|
||||
</div>
|
||||
{_renderRecording && (
|
||||
<div className = 'recording-header space-top'>
|
||||
<label
|
||||
className = 'recording-title'
|
||||
htmlFor = 'recording-switch-audio-video'>
|
||||
{ t('recording.recordAudioAndVideo') }
|
||||
</label>
|
||||
<Switch
|
||||
checked = { shouldRecordAudioAndVideo }
|
||||
className = 'recording-switch'
|
||||
id = 'recording-switch-audio-video'
|
||||
onChange = { this._onRecordAudioAndVideoSwitchChange } />
|
||||
</div>
|
||||
)}
|
||||
<div className = 'recording-header space-top'>
|
||||
<label
|
||||
className = 'recording-title'
|
||||
htmlFor = 'recording-switch-transcription'>
|
||||
{ t('recording.recordAudioAndVideo') }
|
||||
</label>
|
||||
<Switch
|
||||
checked = { shouldRecordAudioAndVideo }
|
||||
className = 'recording-switch'
|
||||
id = 'recording-switch-transcription'
|
||||
onChange = { this._onRecordAudioAndVideoSwitchChange } />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||
import { getSoundFileSrc } from '../base/media/functions';
|
||||
import { getLocalParticipant, getRemoteParticipants } from '../base/participants/functions';
|
||||
import { registerSound, unregisterSound } from '../base/sounds/actions';
|
||||
import { isEmbedded, isEmbeddedFromSameDomain } from '../base/util/embedUtils';
|
||||
import { isEmbedded } from '../base/util/embedUtils';
|
||||
import { isSpotTV } from '../base/util/spot';
|
||||
import { isInBreakoutRoom as isInBreakoutRoomF } from '../breakout-rooms/functions';
|
||||
import { isEnabled as isDropboxEnabled } from '../dropbox/functions';
|
||||
@@ -153,7 +153,7 @@ export function getSessionStatusToShow(state: IReduxState, mode: string): string
|
||||
* @returns {boolean} - Whether local recording is supported or not.
|
||||
*/
|
||||
export function supportsLocalRecording() {
|
||||
return LocalRecordingManager.isSupported() && (!isEmbedded() || isEmbeddedFromSameDomain());
|
||||
return LocalRecordingManager.isSupported() && !isEmbedded();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,8 +201,7 @@ export function canStopRecording(state: IReduxState) {
|
||||
}
|
||||
|
||||
if (isCloudRecordingRunning(state) || isRecorderTranscriptionsRunning(state)) {
|
||||
return isJwtFeatureEnabled(state, MEET_FEATURES.RECORDING, false)
|
||||
|| isJwtFeatureEnabled(state, MEET_FEATURES.TRANSCRIPTION, false);
|
||||
return isJwtFeatureEnabled(state, MEET_FEATURES.RECORDING, false);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -256,21 +255,17 @@ export function getRecordButtonProps(state: IReduxState) {
|
||||
// its own to be visible or not.
|
||||
const {
|
||||
recordingService,
|
||||
localRecording,
|
||||
transcription
|
||||
localRecording
|
||||
} = state['features/base/config'];
|
||||
const localRecordingEnabled = !localRecording?.disable && supportsLocalRecording();
|
||||
|
||||
const dropboxEnabled = isDropboxEnabled(state);
|
||||
const recordingEnabled = recordingService?.enabled || dropboxEnabled;
|
||||
const transcriptionEnabled = transcription?.enabled;
|
||||
|
||||
if (localRecordingEnabled) {
|
||||
visible = true;
|
||||
} else if (isJwtFeatureEnabled(state, MEET_FEATURES.RECORDING, false)) {
|
||||
visible = recordingEnabled;
|
||||
} else if (isJwtFeatureEnabled(state, MEET_FEATURES.TRANSCRIPTION, false)) {
|
||||
visible = transcriptionEnabled;
|
||||
}
|
||||
|
||||
// disable the button if the livestreaming is running.
|
||||
|
||||
@@ -18,11 +18,12 @@ export function openLogoutDialog() {
|
||||
const state = getState();
|
||||
const { conference } = state['features/base/conference'];
|
||||
|
||||
const logoutUrl = state['features/base/config'].tokenLogoutUrl;
|
||||
const config = state['features/base/config'];
|
||||
const logoutUrl = config.tokenLogoutUrl;
|
||||
|
||||
dispatch(openDialog('LogoutDialog', LogoutDialog, {
|
||||
onLogout() {
|
||||
if (isTokenAuthEnabled(state)) {
|
||||
if (isTokenAuthEnabled(config)) {
|
||||
if (logoutUrl) {
|
||||
Linking.openURL(logoutUrl);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
import { silentLogout } from '../authentication/actions.web';
|
||||
import { isTokenAuthInline } from '../authentication/functions';
|
||||
import { setTokenAuthUrlSuccess } from '../authentication/actions.web';
|
||||
import { isTokenAuthEnabled } from '../authentication/functions';
|
||||
import {
|
||||
setStartMutedPolicy,
|
||||
setStartReactionsMuted
|
||||
@@ -11,7 +11,6 @@ import { getConferenceState } from '../base/conference/functions';
|
||||
import { hangup } from '../base/connection/actions.web';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import i18next from '../base/i18n/i18next';
|
||||
import { setJWT } from '../base/jwt/actions';
|
||||
import { browser } from '../base/lib-jitsi-meet';
|
||||
import { getNormalizedDisplayName } from '../base/participants/functions';
|
||||
import { updateSettings } from '../base/settings/actions';
|
||||
@@ -38,7 +37,6 @@ import {
|
||||
getProfileTabProps,
|
||||
getShortcutsTabProps
|
||||
} from './functions.web';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Opens {@code LogoutDialog}.
|
||||
@@ -53,26 +51,16 @@ export function openLogoutDialog() {
|
||||
const logoutUrl = config.tokenLogoutUrl;
|
||||
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { idToken } = state['features/base/jwt'];
|
||||
|
||||
if (!browser.isElectron() && logoutUrl && isTokenAuthInline(config)) {
|
||||
let url = logoutUrl;
|
||||
|
||||
if (idToken) {
|
||||
url += `${logoutUrl.indexOf('?') === -1 ? '?' : '&'}id_token_hint=${idToken}`;
|
||||
}
|
||||
|
||||
silentLogout(url)
|
||||
.then(() => {
|
||||
dispatch(setJWT());
|
||||
})
|
||||
.catch(() => logger.error('logout failed'));
|
||||
|
||||
return;
|
||||
}
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
|
||||
dispatch(openDialog('LogoutDialog', LogoutDialog, {
|
||||
onLogout() {
|
||||
if (isTokenAuthEnabled(config) && config.tokenAuthUrlAutoRedirect && jwt) {
|
||||
|
||||
// user is logging out remove auto redirect indication
|
||||
dispatch(setTokenAuthUrlSuccess(false));
|
||||
}
|
||||
|
||||
if (logoutUrl && browser.isElectron()) {
|
||||
const url = appendURLHashParam(logoutUrl, 'electron', 'true');
|
||||
|
||||
|
||||
@@ -272,13 +272,6 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
component: ProfileTab,
|
||||
labelKey: 'profile.title',
|
||||
props: getProfileTabProps(state),
|
||||
propsUpdateFunction: (tabState: any, newProps: ReturnType<typeof getProfileTabProps>) => {
|
||||
return {
|
||||
...newProps,
|
||||
displayName: tabState?.displayName,
|
||||
email: tabState?.email
|
||||
};
|
||||
},
|
||||
submit: submitProfileTab,
|
||||
icon: IconUser
|
||||
});
|
||||
|
||||
@@ -244,11 +244,11 @@ const AudioSettingsContent = ({
|
||||
* @returns {void}
|
||||
*/
|
||||
const _setTracks = async () => {
|
||||
if (!measureAudioLevels || browser.isWebKitBased()) {
|
||||
if (browser.isWebKitBased()) {
|
||||
|
||||
// It appears that at the time of this writing, creating audio tracks blocks the browser's main thread for
|
||||
// long time on safari. Wasn't able to confirm which part of track creation does the blocking exactly, but
|
||||
// not creating the tracks seems to help and makes the UI much more responsive.
|
||||
// Also skip when audio levels are disabled to avoid activating all microphones unnecessarily.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { IReduxState } from '../app/types';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { TRANSLATION_LANGUAGES, TRANSLATION_LANGUAGES_HEAD } from '../base/i18n/i18next';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
import { canAddTranscriber, isTranscribing } from '../transcribing/functions';
|
||||
|
||||
/**
|
||||
@@ -68,5 +67,5 @@ export function areClosedCaptionsEnabled(state: IReduxState) {
|
||||
export function isCCTabEnabled(state: IReduxState) {
|
||||
const { showSubtitlesOnStage = false } = state['features/base/settings'];
|
||||
|
||||
return areClosedCaptionsEnabled(state) && !showSubtitlesOnStage && !isInBreakoutRoom(state);
|
||||
return areClosedCaptionsEnabled(state) && !showSubtitlesOnStage;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
|
||||
import ClosedCaptionButton from './components/web/ClosedCaptionButton';
|
||||
import { areClosedCaptionsEnabled, canStartSubtitles } from './functions.any';
|
||||
@@ -21,9 +20,8 @@ export function useClosedCaptionButton() {
|
||||
const isStartSubtitlesButtonVisible = useSelector(canStartSubtitles);
|
||||
const { showSubtitlesOnStage = false } = useSelector((state: IReduxState) => state['features/base/settings']);
|
||||
const _areClosedCaptionsEnabled = useSelector(areClosedCaptionsEnabled);
|
||||
const _isInBreakoutRoom = useSelector((state: IReduxState) => isInBreakoutRoom(state));
|
||||
|
||||
if (!_areClosedCaptionsEnabled || _isInBreakoutRoom) {
|
||||
if (!_areClosedCaptionsEnabled) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user