mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-09-09 18:18:40 +00:00
Compare commits
38 Commits
8295
...
jitsi-meet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a836187620 | ||
|
|
a0cad4c595 | ||
|
|
19b9eac84a | ||
|
|
068e33fc0f | ||
|
|
00c6bee2fd | ||
|
|
748ead7e13 | ||
|
|
174c4418fb | ||
|
|
64494cab81 | ||
|
|
50d0092e30 | ||
|
|
fdbd7239ff | ||
|
|
a30958ab23 | ||
|
|
aca55172e4 | ||
|
|
c6cce9253c | ||
|
|
c23684e11c | ||
|
|
30595584a3 | ||
|
|
3d30489cc4 | ||
|
|
7288a6dce4 | ||
|
|
697c41bffa | ||
|
|
0361830122 | ||
|
|
6bb984acc8 | ||
|
|
938d112f6b | ||
|
|
d8cca36181 | ||
|
|
c9add0a9ef | ||
|
|
aabc50da60 | ||
|
|
4aa4382d2b | ||
|
|
6bf1b46609 | ||
|
|
9d7237dc24 | ||
|
|
3745c19857 | ||
|
|
cd6419d826 | ||
|
|
a05e8fd2ef | ||
|
|
239e5743dd | ||
|
|
01a731ab4d | ||
|
|
3d56538c69 | ||
|
|
2dc135b80f | ||
|
|
4e81d4461b | ||
|
|
68b16d7bc8 | ||
|
|
45f6550d30 | ||
|
|
5c65446ef4 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -53,6 +53,8 @@ jobs:
|
||||
npm -v
|
||||
- run: npm install
|
||||
- run: make
|
||||
- name: Check config.js syntax
|
||||
run: node config.js
|
||||
android-rn-bundle-build:
|
||||
name: Build mobile bundle (Android)
|
||||
runs-on: macos-15
|
||||
|
||||
@@ -42,7 +42,7 @@ public class JitsiMeetActivityDelegate {
|
||||
/**
|
||||
* Tells whether or not the permissions request is currently in progress.
|
||||
*
|
||||
* @return {@code true} if the permssions are being requested or {@code false} otherwise.
|
||||
* @return {@code true} if the permissions are being requested or {@code false} otherwise.
|
||||
*/
|
||||
static boolean arePermissionsBeingRequested() {
|
||||
return permissionListener != null;
|
||||
|
||||
@@ -155,7 +155,6 @@ import {
|
||||
NOTIFICATION_TIMEOUT_TYPE
|
||||
} from './react/features/notifications/constants';
|
||||
import { isModerationNotificationDisplayed } from './react/features/notifications/functions';
|
||||
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
|
||||
import { suspendDetected } from './react/features/power-monitor/actions';
|
||||
import { initPrejoin, isPrejoinPageVisible } from './react/features/prejoin/functions';
|
||||
import { disableReceiver, stopReceiver } from './react/features/remote-control/actions';
|
||||
@@ -435,15 +434,6 @@ export default {
|
||||
requestedVideo = true;
|
||||
}
|
||||
|
||||
if (!config.disableInitialGUM) {
|
||||
JitsiMeetJS.mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,
|
||||
browserName =>
|
||||
APP.store.dispatch(
|
||||
mediaPermissionPromptVisibilityChanged(true, browserName))
|
||||
);
|
||||
}
|
||||
|
||||
let tryCreateLocalTracks = Promise.resolve([]);
|
||||
|
||||
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
|
||||
@@ -452,8 +442,7 @@ export default {
|
||||
const timeout = browser.isElectron() ? 15000 : 60000;
|
||||
const audioOptions = {
|
||||
devices: [ MEDIA_TYPE.AUDIO ],
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true
|
||||
timeout
|
||||
};
|
||||
|
||||
// Spot uses the _desktopSharingSourceDevice config option to use an external video input device label as
|
||||
@@ -488,8 +477,7 @@ export default {
|
||||
} else if (requestedAudio || requestedVideo) {
|
||||
tryCreateLocalTracks = APP.store.dispatch(createInitialAVTracks({
|
||||
devices: initialDevices,
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true
|
||||
timeout
|
||||
}, recordTimeMetrics)).then(({ tracks, errors: pErrors }) => {
|
||||
Object.assign(errors, pErrors);
|
||||
|
||||
@@ -497,15 +485,6 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
// Hide the permissions prompt/overlay as soon as the tracks are created. Don't wait for the connection to
|
||||
// be established, as in some cases like when auth is required, connection won't be established until the user
|
||||
// inputs their credentials, but the dialog would be overshadowed by the overlay.
|
||||
tryCreateLocalTracks.then(tracks => {
|
||||
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
|
||||
|
||||
return tracks;
|
||||
});
|
||||
|
||||
return {
|
||||
tryCreateLocalTracks,
|
||||
errors
|
||||
|
||||
@@ -459,7 +459,7 @@ var config = {
|
||||
|
||||
// // Translation languages.
|
||||
// // Available languages can be found in
|
||||
// // ./src/react/features/transcribing/translation-languages.json.
|
||||
// // ./lang/translation-languages.json.
|
||||
// translationLanguages: ['en', 'es', 'fr', 'ro'],
|
||||
|
||||
// // Important languages to show on the top of the language list.
|
||||
@@ -907,7 +907,7 @@ var config = {
|
||||
// Overrides the buttons displayed in the main toolbar. Depending on the screen size the number of displayed
|
||||
// buttons varies from 2 buttons to 8 buttons. Every array in the mainToolbarButtons array will replace the
|
||||
// corresponding default buttons configuration matched by the number of buttons specified in the array. Arrays with
|
||||
// more than 8 buttons or less then 2 buttons will be ignored. When there there isn't an override for a cerain
|
||||
// more than 8 buttons or less then 2 buttons will be ignored. When there there isn't an override for a certain
|
||||
// configuration (for example when 3 buttons are displayed) the default jitsi-meet configuration will be used.
|
||||
// The order of the buttons in the array is preserved.
|
||||
// mainToolbarButtons: [
|
||||
@@ -1735,7 +1735,7 @@ var config = {
|
||||
// 'notify.participantsWantToJoin', // shown when lobby is enabled and participants request to join meeting
|
||||
// 'notify.passwordRemovedRemotely', // shown when a password has been removed remotely
|
||||
// 'notify.passwordSetRemotely', // shown when a password has been set remotely
|
||||
// 'notify.raisedHand', // shown when a partcipant used raise hand,
|
||||
// 'notify.raisedHand', // shown when a participant used raise hand,
|
||||
// 'notify.screenShareNoAudio', // shown when the audio could not be shared for the selected screen
|
||||
// 'notify.screenSharingAudioOnlyTitle', // shown when the best performance has been affected by screen sharing
|
||||
// 'notify.selfViewTitle', // show "You can always un-hide the self-view from settings"
|
||||
@@ -1766,7 +1766,7 @@ var config = {
|
||||
// disableFilmstripAutohiding: false,
|
||||
|
||||
// filmstrip: {
|
||||
// // Disable the vertical/horizonal filmstrip.
|
||||
// // Disable the vertical/horizontal filmstrip.
|
||||
// disabled: false,
|
||||
// // Disables user resizable filmstrip. Also, allows configuration of the filmstrip
|
||||
// // (width, tiles aspect ratios) through the interfaceConfig options.
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
color: #2f3237;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin-bottom: 16px;
|
||||
max-width: 436px;
|
||||
color: #2f3237;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin-bottom: 16px;
|
||||
max-width: 436px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,8 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.with-click-handler:hover {
|
||||
&.with-click-handler:hover,
|
||||
&.with-click-handler:focus {
|
||||
background-color: #c7ddff;
|
||||
}
|
||||
|
||||
@@ -155,14 +156,22 @@
|
||||
margin-right: 16px;
|
||||
position: absolute;
|
||||
|
||||
&> svg {
|
||||
&>svg {
|
||||
fill: #0074e0;
|
||||
}
|
||||
}
|
||||
|
||||
.item:hover, .item:focus, .item:focus-within {
|
||||
.item:hover,
|
||||
.item:focus,
|
||||
.item:focus-within {
|
||||
.delete-meeting {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.delete-meeting:hover {
|
||||
&>svg {
|
||||
fill: #4687ED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1726,7 +1726,7 @@ PODS:
|
||||
- React
|
||||
- RNCAsyncStorage (1.23.1):
|
||||
- React-Core
|
||||
- RNCClipboard (1.14.1):
|
||||
- RNCClipboard (1.14.3):
|
||||
- React-Core
|
||||
- RNDefaultPreference (1.4.4):
|
||||
- React-Core
|
||||
@@ -2197,7 +2197,7 @@ SPEC CHECKSUMS:
|
||||
ReactCommon: 6a952e50c2a4b694731d7682aaa6c79bc156e4ad
|
||||
RNCalendarEvents: 7e65eb4a94f53c1744d1e275f7fafcfaa619f7a3
|
||||
RNCAsyncStorage: 826b603ae9c0f88b5ac4e956801f755109fa4d5c
|
||||
RNCClipboard: 0a720adef5ec193aa0e3de24c3977222c7e52a37
|
||||
RNCClipboard: 2821ac938ef46f736a8de0c8814845dde2dcbdfb
|
||||
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
|
||||
RNDeviceInfo: 02ea8b23e2280fa18e00a06d7e62804d74028579
|
||||
RNGestureHandler: 939f21fabf5d45a725c0bf175eb819dd25cf2e70
|
||||
@@ -2211,4 +2211,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: 7ed908101076ca2c595b633c34648bcfc7e98614
|
||||
|
||||
COCOAPODS: 1.16.1
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -69,7 +69,7 @@ platform :ios do
|
||||
end
|
||||
)
|
||||
|
||||
# Inrement the build number by 1
|
||||
# Increment the build number by 1
|
||||
increment_build_number(
|
||||
build_number: Time.now.to_i,
|
||||
xcodeproj: "app/app.xcodeproj"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1388,7 +1388,7 @@
|
||||
"tileViewToggle": "Kachelansicht ein-/ausschalten",
|
||||
"toggleCamera": "Kamera wechseln",
|
||||
"unmute": "Stummschaltung aufheben",
|
||||
"videoSettings": "Kameraeinstellungen",
|
||||
"videoSettings": "Kamera-Einstellungen",
|
||||
"videomute": "Kamera stoppen",
|
||||
"videomuteGUMPending": "Verbinde Ihre Kamera",
|
||||
"videounmute": "Kamera einschalten"
|
||||
|
||||
@@ -334,6 +334,7 @@
|
||||
"kickParticipantButton": "Expulser",
|
||||
"kickParticipantDialog": "Êtes-vous sûr(e) de vouloir expulser ce participant ?",
|
||||
"kickParticipantTitle": "Expulser ce participant ?",
|
||||
"kickSystemTitle": "Oups ! Vous avez été expulsé de la réunion",
|
||||
"kickTitle": "Oups ! vous avez été expulsé(e) par {{participantDisplayName}}",
|
||||
"linkMeeting": "Relier la conférence",
|
||||
"linkMeetingTitle": "Relier la conférence à Salesforce",
|
||||
@@ -439,7 +440,10 @@
|
||||
"shareScreenWarningD2": "vous devez arrêter le partage d'audio, démarrer le partage d'écran et cocher l'option \"Partager l'audio\".",
|
||||
"shareScreenWarningH1": "Si vous voulez partager uniquement votre écran:",
|
||||
"shareScreenWarningTitle": "Vous devez cesser de partager votre audio avant de partager votre écran",
|
||||
"shareVideoConfirmPlay": "Vous êtes sur le point d'ouvrir un site web externe. Voulez-vous continuer ?",
|
||||
"shareVideoConfirmPlayTitle": "{{name}} a partagé une vidéo avec vous.",
|
||||
"shareVideoLinkError": "Veuillez renseigner un lien de diffusion vidéo fonctionnel.",
|
||||
"shareVideoLinkStopped": "La vidéo de {{name}} a été arrêtée.",
|
||||
"shareVideoTitle": "Partager une vidéo",
|
||||
"shareYourScreen": "Partager votre écran",
|
||||
"shareYourScreenDisabled": "Le partage d'écran est désactivé.",
|
||||
@@ -638,6 +642,7 @@
|
||||
"on": "En direct",
|
||||
"onBy": "{{name}} a démarré la diffusion en direct",
|
||||
"pending": "Lancement du direct…",
|
||||
"policyError": "Vous avez essayé de démarrer une diffusion en direct trop rapidement. Veuillez réessayer plus tard !",
|
||||
"serviceName": "Service de diffusion en direct",
|
||||
"sessionAlreadyActive": "Cette session est déjà en cours d'enregistrement ou de diffusion.",
|
||||
"signIn": "Se connecter avec Google",
|
||||
@@ -736,6 +741,7 @@
|
||||
"connectedOneMember": "{{name}} a rejoint la réunion",
|
||||
"connectedThreePlusMembers": "{{name}} et {{count}} autres personnes ont rejoint la réunion",
|
||||
"connectedTwoMembers": "{{first}} et {{second}} ont rejoint la réunion",
|
||||
"connectionFailed": "Connexion échouée. Veuillez réessayer plus tard !",
|
||||
"dataChannelClosed": "Qualité vidéo dégradée",
|
||||
"dataChannelClosedDescription": "Le canal de communication avec le Bridge a été interrompu, la qualité vidéo se trouve limitée à sa valeur la plus faible.",
|
||||
"dataChannelClosedDescriptionWithAudio": "Le canal de pont est fermé, ce qui peut entraîner des perturbations de l'audio et de la vidéo.",
|
||||
@@ -750,6 +756,9 @@
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "Notifications",
|
||||
"hostAskedUnmute": "Le modérateur souhaite vous donner la parole",
|
||||
"invalidTenant": "Tenant invalide",
|
||||
"invalidTenantHyphenDescription": "Le tenant que vous utilisez est invalide (commence ou se termine par '-').",
|
||||
"invalidTenantLengthDescription": "Le tenant que vous utilisez est trop long.",
|
||||
"invitedOneMember": "{{name}} a été invité(e)",
|
||||
"invitedThreePlusMembers": "{{name}} et {{count}} autres ont été invités",
|
||||
"invitedTwoMembers": "{{first}} et {{second}} ont été invités",
|
||||
@@ -786,6 +795,7 @@
|
||||
"newDeviceAction": "Utiliser",
|
||||
"newDeviceAudioTitle": "Nouveau périphérique audio détecté",
|
||||
"newDeviceCameraTitle": "Nouvelle caméra détectée",
|
||||
"nextToSpeak": "Vous êtes le prochain à prendre la parole",
|
||||
"noiseSuppressionDesktopAudioDescription": "La suppression de bruit ne peut pas être activée en même temps que la partage audio du système, veuillez le désactiver et réessayer.",
|
||||
"noiseSuppressionFailedTitle": "Échec du démarrage de la suppression de bruit",
|
||||
"noiseSuppressionStereoDescription": "La suppression de bruit d’une source stéréo n’est pas encore supportée.",
|
||||
@@ -820,6 +830,7 @@
|
||||
"videoUnmuteBlockedDescription": "Le rétablissement de la vidéo a été bloqué temporairement en raison de limites système.",
|
||||
"videoUnmuteBlockedTitle": "Rétablissement de la caméra bloqué !",
|
||||
"viewLobby": "Voir la salle d'attente",
|
||||
"viewParticipants": "Voir les participants",
|
||||
"viewVisitors": "Voir les visiteurs",
|
||||
"waitingParticipants": "{{waitingParticipants}} personnes",
|
||||
"waitingVisitors": "Visiteurs en attente dans la file : {{waitingVisitors}}",
|
||||
@@ -839,6 +850,8 @@
|
||||
"breakoutRooms": "Salles annexes",
|
||||
"goLive": "Passer en direct",
|
||||
"invite": "Inviter quelqu'un",
|
||||
"lowerAllHands": "Abaisser toutes les mains",
|
||||
"lowerHand": "Abaisser la main",
|
||||
"moreModerationActions": "Options de modération supplémentaires",
|
||||
"moreModerationControls": "Options de modération supplémentaires",
|
||||
"moreParticipantOptions": "Options supplémentaires pour les participants",
|
||||
@@ -875,6 +888,7 @@
|
||||
"submit": "Envoyer"
|
||||
},
|
||||
"by": "Par {{ name }}",
|
||||
"closeButton": "Fermer le sondage",
|
||||
"create": {
|
||||
"addOption": "Ajouter une option",
|
||||
"answerPlaceholder": "Option {{index}}",
|
||||
@@ -914,9 +928,11 @@
|
||||
"configuringDevices": "Configuration des appareils…",
|
||||
"connectedWithAudioQ": "Êtes-vous connecté avec le microphone ?",
|
||||
"connection": {
|
||||
"failed": "Le test de connexion a échoué !",
|
||||
"good": "Votre connexion Internet est bonne !",
|
||||
"nonOptimal": "Votre connexion n'est pas optimale",
|
||||
"poor": "Vous avez une mauvaise connexion"
|
||||
"poor": "Vous avez une mauvaise connexion",
|
||||
"running": "Exécution du test de connexion…"
|
||||
},
|
||||
"connectionDetails": {
|
||||
"audioClipping": "Attendez vous à ce que votre son soit coupé.",
|
||||
@@ -925,6 +941,7 @@
|
||||
"goodQuality": "Impressionnant ! La qualité de vos médias sera excellente",
|
||||
"noMediaConnectivity": "Nous n'avons pas pu trouver un moyen d'établir une connectivité multimédia pour ce test. Cela est généralement causé par un pare-feu ou un NAT.",
|
||||
"noVideo": "Attendez vous à ce que votre qualité vidéo soit très mauvaise.",
|
||||
"testFailed": "Le test de connexion a rencontré des problèmes inattendus, mais cela pourrait ne pas affecter votre expérience.",
|
||||
"undetectable": "Si vous ne parvenez toujours pas à passer des appels dans le navigateur, nous vous recommandons de vous assurer que vos haut-parleurs, microphone et caméra sont correctement configurés, que vous avez accordé à votre navigateur les droits d'utiliser votre microphone et votre caméra et que la version de votre navigateur est à jour. Si vous rencontrez toujours des difficultés pour appeler, vous devez contacter le développeur de l'application Web.",
|
||||
"veryPoorConnection": "Attendez vous à ce que la qualité de votre appel soit très mauvaise",
|
||||
"videoFreezing": "Attendez vous à ce que votre vidéo saute, soit noire, et pixelisée.",
|
||||
@@ -1040,6 +1057,7 @@
|
||||
"onBy": "{{name}} a démarré l'enregistrement",
|
||||
"onlyRecordSelf": "Enregistrer seulement mon audio et ma vidéo.",
|
||||
"pending": "Préparation de l'enregistrement de la réunion…",
|
||||
"policyError": "Vous avez essayé de démarrer un enregistrement trop rapidement. Veuillez réessayer plus tard !",
|
||||
"recordAudioAndVideo": "Enregistrer l'audio et la vidéo",
|
||||
"recordTranscription": "Enregistrer la transcription",
|
||||
"saveLocalRecording": "Sauvegarder l’enregistrement local (Beta)",
|
||||
@@ -1088,6 +1106,7 @@
|
||||
"desktopShareWarning": "Vous devez repartager l'écran pour que ces paramètres soient utilisés.",
|
||||
"devices": "Périphériques",
|
||||
"followMe": "Tout le monde me suit",
|
||||
"followMeRecorder": "L'enregistreur me suit",
|
||||
"framesPerSecond": "images par seconde",
|
||||
"incomingMessage": "un message arrive",
|
||||
"language": "Langue",
|
||||
@@ -1234,6 +1253,7 @@
|
||||
"lobbyButton": "Activer / Désactiver le mode salle d'attente",
|
||||
"localRecording": "Activer / Désactiver les contrôles d'enregistrement local",
|
||||
"lockRoom": "Activer / Désactiver le mot de passe de la réunion",
|
||||
"love": "Cœur",
|
||||
"lowerHand": "Baisser la main",
|
||||
"moreActions": "Activer / Désactiver le menu d'actions supplémentaires",
|
||||
"moreActionsMenu": "Menu d'actions supplémentaires",
|
||||
@@ -1251,6 +1271,7 @@
|
||||
"privateMessage": "Envoyer un message privé",
|
||||
"profile": "Éditer votre profil",
|
||||
"raiseHand": "Lever la main",
|
||||
"react": "Réactions aux messages",
|
||||
"reactions": "Réactions",
|
||||
"reactionsMenu": "Ouvrir / fermer le menu réactions",
|
||||
"recording": "Activer / Désactiver l'enregistrement",
|
||||
@@ -1322,6 +1343,7 @@
|
||||
"lobbyButtonEnable": "Activer le mode salle d'attente / contrôle des participant(e)s",
|
||||
"login": "Connexion",
|
||||
"logout": "Déconnexion",
|
||||
"love": "Cœur",
|
||||
"lowerYourHand": "Baisser la main",
|
||||
"moreActions": "Plus d'actions",
|
||||
"moreOptions": "Plus d'options",
|
||||
@@ -1347,6 +1369,7 @@
|
||||
"raiseYourHand": "Lever la main",
|
||||
"reactionBoo": "Envoyer réaction huer",
|
||||
"reactionClap": "Envoyer réaction applaudir",
|
||||
"reactionHeart": "Envoyer une réaction en forme de cœur",
|
||||
"reactionLaugh": "Envoyer réaction rire",
|
||||
"reactionLike": "Envoyer réaction approuver",
|
||||
"reactionSilence": "Envoyer réaction silence",
|
||||
@@ -1380,7 +1403,7 @@
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Activer / Désactiver les sous-titres",
|
||||
"expandedLabel": "La transcription est actuellement activée",
|
||||
"failedToStart": "Échec de démarrage de la transcription",
|
||||
"failed": "La transcription a échoué",
|
||||
"labelToolTip": "La transcription de la réunion est en cours",
|
||||
"sourceLanguageDesc": "Actuellement, la langue de la réunion est sélectionnée à <b>{{sourceLanguage}}</b>. <br/> Vous pouvez la changer à partir de ",
|
||||
"sourceLanguageHere": "ici",
|
||||
@@ -1481,10 +1504,15 @@
|
||||
},
|
||||
"visitors": {
|
||||
"chatIndicator": "(visiteur)",
|
||||
"labelTooltip": "Nombre de Visiteurs",
|
||||
"joinMeeting": {
|
||||
"description": "Vous êtes actuellement un observateur dans cette conférence.",
|
||||
"raiseHand": "Levez la main",
|
||||
"title": "Rejoindre la réunion",
|
||||
"wishToSpeak": "Si vous souhaitez prendre la parole, veuillez lever la main ci-dessous et attendre l'approbation du modérateur."
|
||||
},
|
||||
"labelTooltip": "Nombre de Visiteurs: {{count}}",
|
||||
"notification": {
|
||||
"demoteDescription": "Envoyé ici par {{actor}}, levez la main pour participer",
|
||||
"description": "Pour participer lever la main.",
|
||||
"noMainParticipantsDescription": "Un participant doit démarrer la réunion. Veuillez réessayer dans un moment.",
|
||||
"noMainParticipantsTitle": "Cette réunion n'a pas encore commencé.",
|
||||
"noVisitorLobby": "Vous ne pouvez pas rejoindre tant qu'une salle d'attente est activée pour la réunion.",
|
||||
|
||||
@@ -483,8 +483,8 @@
|
||||
"transcribing": "Notiek transkripcija",
|
||||
"unlockRoom": "Noņemt $t(lockRoomPassword)",
|
||||
"user": "Lietotājs",
|
||||
"userIdentifier": "Lietotāja identifikators",
|
||||
"userPassword": "lietotāja parole",
|
||||
"userIdentifier": "Lietotājvārds",
|
||||
"userPassword": "Parole",
|
||||
"verifyParticipantConfirm": "Sakrīt",
|
||||
"verifyParticipantDismiss": "Nesakrīt",
|
||||
"verifyParticipantQuestion": "EKSPERIMENTĀLS: pajautājiet dalībniekam {{participantName}}, vai viņš redz to pašu saturu tādā pašā secībā.",
|
||||
@@ -641,6 +641,7 @@
|
||||
"on": "Tiešraide ieslēgta",
|
||||
"onBy": "{{name}} ieslēdza tiešraidi",
|
||||
"pending": "Sākam tiešraidi…",
|
||||
"policyError": "Jūs mēģinājāt pārāk ātri sākt tiešraides straumi. Lūdzu, vēlāk mēģiniet vēlreiz!",
|
||||
"serviceName": "Tiešsaistes pakalpojums",
|
||||
"sessionAlreadyActive": "Šī sesija jau tiek ierakstīta vai straumēta tiešraidē.",
|
||||
"signIn": "Ierakstīties ar Google kontu",
|
||||
@@ -739,6 +740,7 @@
|
||||
"connectedOneMember": "{{name}} ir pievienojies sapulcei",
|
||||
"connectedThreePlusMembers": "{{name}} un {{count}} citi ir pievienojušies sapulcei",
|
||||
"connectedTwoMembers": "{{first}} un {{second}} ir pievienojušies sapulcei",
|
||||
"connectionFailed": "Savienojums neizdevās. Lūdzu, vēlāk mēģiniet vēlreiz!",
|
||||
"dataChannelClosed": "Video kvalitāte var būt traucēta",
|
||||
"dataChannelClosedDescription": "Savienojuma kanāls nedarbojas, tāpēc video kvalitāte var būt ierobežota līdz zemākajam iestatījumam.",
|
||||
"dataChannelClosedDescriptionWithAudio": "Savienojuma kanāls nedarbojas, tāpēc var rasties audio un video traucējumi.",
|
||||
@@ -753,6 +755,9 @@
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "Paziņojumi",
|
||||
"hostAskedUnmute": "Moderators vēlas, lai jūs runātu",
|
||||
"invalidTenant": "Nederīgs tenants",
|
||||
"invalidTenantHyphenDescription": "Jūsu izmantotais tenants nav derīgs (sākas vai beidzas ar '-').",
|
||||
"invalidTenantLengthDescription": "Jūsu izmantotais tenants ir pārāk garš.",
|
||||
"invitedOneMember": "{{displayName}} tika uzaicināts",
|
||||
"invitedThreePlusMembers": "Uzaicināts {{name}} un {{count}} citi lietotāji",
|
||||
"invitedTwoMembers": "{{first}} un {{second}} tika uzaicināti uz sapulci",
|
||||
@@ -1051,6 +1056,7 @@
|
||||
"onBy": "{{name}} ieslēdza ierakstu",
|
||||
"onlyRecordSelf": "Ierakstīt tikai manas audio un video straumes",
|
||||
"pending": "Gatavojas ierakstīt sapulci…",
|
||||
"policyError": "Jūs mēģinājāt pārāk ātri sākt ierakstīšanu. Lūdzu, vēlāk mēģiniet vēlreiz!",
|
||||
"recordAudioAndVideo": "Ierakstīt audio un video",
|
||||
"recordTranscription": "Ierakstīt transkripciju",
|
||||
"saveLocalRecording": "Ieraksta faila saglabāšana lokāli (beta)",
|
||||
@@ -1246,6 +1252,7 @@
|
||||
"lobbyButton": "Iespējot/atspējot vestibila režīmu",
|
||||
"localRecording": "Lokālā ieraksta vadības rīki (iesl./izsl.)",
|
||||
"lockRoom": "Telpas slēgšana ar paroli (iesl./izsl.)",
|
||||
"love": "Sirds",
|
||||
"lowerHand": "Nolaist roku",
|
||||
"moreActions": "Papildus iestatījumu izvēlne (rādīt/nerādīt)",
|
||||
"moreActionsMenu": "Papildus iestatījumu izvēlne",
|
||||
@@ -1335,6 +1342,7 @@
|
||||
"lobbyButtonEnable": "Iespējot vestibila režīmu",
|
||||
"login": "Ierakstīties",
|
||||
"logout": "Izrakstīties",
|
||||
"love": "Sirds",
|
||||
"lowerYourHand": "Nolaist roku",
|
||||
"moreActions": "Vairāk rīcību",
|
||||
"moreOptions": "Vairāk opciju",
|
||||
@@ -1360,6 +1368,7 @@
|
||||
"raiseYourHand": "Pacelt roku",
|
||||
"reactionBoo": "Nosūtīt būū reakciju",
|
||||
"reactionClap": "Nosūtīt aplausu reakciju",
|
||||
"reactionHeart": "Nosūtīt sirds reakciju",
|
||||
"reactionLaugh": "Nosūtīt smieklu reakciju",
|
||||
"reactionLike": "Nosūtīt īkšķi augšup reakciju",
|
||||
"reactionSilence": "Nosūtīt klusuma reakciju",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -334,6 +334,7 @@
|
||||
"kickParticipantButton": "Kick",
|
||||
"kickParticipantDialog": "Are you sure you want to kick this participant?",
|
||||
"kickParticipantTitle": "Kick this participant?",
|
||||
"kickSystemTitle": "Ouch! You were kicked out of the meeting",
|
||||
"kickTitle": "Ouch! {{participantDisplayName}} kicked you out of the meeting",
|
||||
"linkMeeting": "Link meeting",
|
||||
"linkMeetingTitle": "Link meeting to Salesforce",
|
||||
|
||||
@@ -115,7 +115,7 @@ import { toggleScreenshotCaptureSummary } from '../../react/features/screenshot-
|
||||
import { isScreenshotCaptureEnabled } from '../../react/features/screenshot-capture/functions';
|
||||
import SettingsDialog from '../../react/features/settings/components/web/SettingsDialog';
|
||||
import { SETTINGS_TABS } from '../../react/features/settings/constants';
|
||||
import { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions.any';
|
||||
import { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions';
|
||||
import { extractYoutubeIdOrURL } from '../../react/features/shared-video/functions';
|
||||
import { setRequestingSubtitles, toggleRequestingSubtitles } from '../../react/features/subtitles/actions';
|
||||
import { isAudioMuteButtonDisabled } from '../../react/features/toolbox/functions';
|
||||
@@ -1571,8 +1571,8 @@ class API {
|
||||
formattedArgument += `${arg.toString()}: ${arg.stack}`;
|
||||
} else if (typeof arg === 'object') {
|
||||
// NOTE: The non-enumerable properties of the objects wouldn't be included in the string after
|
||||
// JSON.strigify. For example Map instance will be translated to '{}'. So I think we have to eventually
|
||||
// do something better for parsing the arguments. But since this option for strigify is part of the
|
||||
// JSON.stringify. For example Map instance will be translated to '{}'. So I think we have to eventually
|
||||
// do something better for parsing the arguments. But since this option for stringify is part of the
|
||||
// public interface and I think it could be useful in some cases I will it for now.
|
||||
try {
|
||||
formattedArgument += JSON.stringify(arg);
|
||||
|
||||
2307
package-lock.json
generated
2307
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@@ -32,7 +32,7 @@
|
||||
"@microsoft/microsoft-graph-client": "3.0.1",
|
||||
"@mui/material": "5.12.1",
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
"@react-native-clipboard/clipboard": "1.14.1",
|
||||
"@react-native-clipboard/clipboard": "1.14.3",
|
||||
"@react-native-community/netinfo": "11.1.0",
|
||||
"@react-native-community/slider": "4.4.3",
|
||||
"@react-native-google-signin/google-signin": "10.1.0",
|
||||
@@ -139,13 +139,15 @@
|
||||
"@types/amplitude-js": "8.16.5",
|
||||
"@types/audioworklet": "0.0.29",
|
||||
"@types/dom-screen-wake-lock": "1.0.1",
|
||||
"@types/jasmine": "5.1.4",
|
||||
"@types/js-md5": "0.4.3",
|
||||
"@types/jsonwebtoken": "9.0.7",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/minimatch": "5.1.2",
|
||||
"@types/mocha": "10.0.10",
|
||||
"@types/moment-duration-format": "2.2.6",
|
||||
"@types/offscreencanvas": "2019.7.2",
|
||||
"@types/pixelmatch": "5.2.5",
|
||||
"@types/pretty": "2.0.3",
|
||||
"@types/punycode": "2.1.0",
|
||||
"@types/react": "17.0.14",
|
||||
"@types/react-dom": "17.0.14",
|
||||
@@ -161,12 +163,12 @@
|
||||
"@types/zxcvbn": "4.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.5",
|
||||
"@typescript-eslint/parser": "5.59.5",
|
||||
"@wdio/allure-reporter": "9.2.14",
|
||||
"@wdio/cli": "9.4.1",
|
||||
"@wdio/globals": "9.4.1",
|
||||
"@wdio/jasmine-framework": "9.4.1",
|
||||
"@wdio/junit-reporter": "9.2.14",
|
||||
"@wdio/local-runner": "9.4.1",
|
||||
"@wdio/allure-reporter": "9.4.3",
|
||||
"@wdio/cli": "9.4.3",
|
||||
"@wdio/globals": "9.4.3",
|
||||
"@wdio/junit-reporter": "9.4.3",
|
||||
"@wdio/local-runner": "9.4.3",
|
||||
"@wdio/mocha-framework": "9.4.3",
|
||||
"babel-loader": "9.1.0",
|
||||
"babel-plugin-optional-require": "0.3.1",
|
||||
"circular-dependency-plugin": "5.2.0",
|
||||
@@ -182,14 +184,15 @@
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"metro-react-native-babel-preset": "0.77.0",
|
||||
"patch-package": "6.4.7",
|
||||
"pretty": "2.0.0",
|
||||
"process": "0.11.10",
|
||||
"sass": "1.26.8",
|
||||
"style-loader": "3.3.1",
|
||||
"traverse": "0.6.6",
|
||||
"ts-loader": "9.4.2",
|
||||
"typescript": "5.0.4",
|
||||
"typescript": "5.7.2",
|
||||
"unorm": "1.6.0",
|
||||
"webdriverio": "9.4.1",
|
||||
"webdriverio": "9.4.3",
|
||||
"webpack": "5.95.0",
|
||||
"webpack-bundle-analyzer": "4.4.2",
|
||||
"webpack-cli": "5.1.4",
|
||||
@@ -199,7 +202,7 @@
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0",
|
||||
"node": ">=22.0.0",
|
||||
"npm": ">=10.0.0"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
"peerDependencies": {
|
||||
"@amplitude/react-native": "0.0.0",
|
||||
"@giphy/react-native-sdk": "0.0.0",
|
||||
"@react-native/metro-config": "*",
|
||||
"@react-native-async-storage/async-storage": "0.0.0",
|
||||
"@react-native-clipboard/clipboard": "0.0.0",
|
||||
"@react-native-community/netinfo": "0.0.0",
|
||||
|
||||
22
react-native-sdk/update_sdk_dependencies.js
vendored
22
react-native-sdk/update_sdk_dependencies.js
vendored
@@ -1,9 +1,13 @@
|
||||
const fs = require('fs');
|
||||
const semver = require('semver');
|
||||
|
||||
const packageJSON = require('../package.json');
|
||||
|
||||
const SDKPackageJSON = require('./package.json');
|
||||
|
||||
// Skip checking these.
|
||||
const skipDeps = [ 'react', 'react-native' ];
|
||||
|
||||
/**
|
||||
* Merges the dependency versions from the root package.json with the dependencies of the SDK package.json.
|
||||
*/
|
||||
@@ -18,15 +22,21 @@ function mergeDependencyVersions() {
|
||||
|
||||
// Updates SDK peer dependencies.
|
||||
for (const key in packageJSON.dependencies) {
|
||||
if (SDKPackageJSON.peerDependencies.hasOwnProperty(key)) {
|
||||
|
||||
// Updates all peer dependencies except react and react-native.
|
||||
if (key !== 'react' && key !== 'react-native') {
|
||||
SDKPackageJSON.peerDependencies[key] = packageJSON.dependencies[key];
|
||||
}
|
||||
if (SDKPackageJSON.peerDependencies.hasOwnProperty(key) && !skipDeps.includes(key)) {
|
||||
SDKPackageJSON.peerDependencies[key] = packageJSON.dependencies[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Set RN peer dependency.
|
||||
const rnVersion = semver.parse(packageJSON.dependencies['react-native']);
|
||||
|
||||
if (!rnVersion) {
|
||||
throw new Error('failed to parse React Native version');
|
||||
}
|
||||
|
||||
// In RN the "major" version is the Semver minor.
|
||||
SDKPackageJSON.peerDependencies['react-native'] = `~0.${rnVersion.minor}.0`;
|
||||
|
||||
// Updates SDK overrides dependencies.
|
||||
for (const key in packageJSON.overrides) {
|
||||
if (SDKPackageJSON.overrides.hasOwnProperty(key)) {
|
||||
|
||||
@@ -185,13 +185,20 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
|
||||
inIframe?: boolean;
|
||||
isPromotedFromVisitor?: boolean;
|
||||
isVisitor?: boolean;
|
||||
overwritesCustomButtonsWithURL?: boolean;
|
||||
overwritesDefaultLogoUrl?: boolean;
|
||||
overwritesDeploymentUrls?: boolean;
|
||||
overwritesEtherpadBase?: boolean;
|
||||
overwritesHosts?: boolean;
|
||||
overwritesIceServers?: boolean;
|
||||
overwritesLiveStreamingUrls?: boolean;
|
||||
overwritesPeopleSearchUrl?: boolean;
|
||||
overwritesPrejoinConfigICEUrl?: boolean;
|
||||
overwritesSalesforceUrl?: boolean;
|
||||
overwritesSupportUrl?: boolean;
|
||||
overwritesWatchRTCConfigParams?: boolean;
|
||||
overwritesWatchRTCProxyUrl?: boolean;
|
||||
overwritesWatchRTCWSUrl?: boolean;
|
||||
server?: string;
|
||||
tenant?: string;
|
||||
wasLobbyVisible?: boolean;
|
||||
@@ -235,6 +242,21 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
|
||||
permanentProperties.overwritesSalesforceUrl = 'config.salesforceUrl' in params;
|
||||
permanentProperties.overwritesPeopleSearchUrl = 'config.peopleSearchUrl' in params;
|
||||
permanentProperties.overwritesDefaultLogoUrl = 'config.defaultLogoUrl' in params;
|
||||
permanentProperties.overwritesEtherpadBase = 'config.etherpad_base' in params;
|
||||
const hosts = params['config.hosts'] ?? {};
|
||||
const hostsProps = [ 'anonymousdomain', 'authdomain', 'domain', 'focus', 'muc', 'visitorFocus' ];
|
||||
|
||||
permanentProperties.overwritesHosts = 'config.hosts' in params
|
||||
|| Boolean(hostsProps.find(p => `config.hosts.${p}` in params || (typeof hosts === 'object' && p in hosts)));
|
||||
|
||||
permanentProperties.overwritesWatchRTCConfigParams = 'config.watchRTCConfigParams' in params;
|
||||
const watchRTCConfigParams = params['config.watchRTCConfigParams'] ?? {};
|
||||
|
||||
permanentProperties.overwritesWatchRTCProxyUrl = ('config.watchRTCConfigParams.proxyUrl' in params)
|
||||
|| (typeof watchRTCConfigParams === 'object' && 'proxyUrl' in watchRTCConfigParams);
|
||||
permanentProperties.overwritesWatchRTCWSUrl = ('config.watchRTCConfigParams.wsUrl' in params)
|
||||
|| (typeof watchRTCConfigParams === 'object' && 'wsUrl' in watchRTCConfigParams);
|
||||
|
||||
const prejoinConfig = params['config.prejoinConfig'] ?? {};
|
||||
|
||||
permanentProperties.overwritesPrejoinConfigICEUrl = ('config.prejoinConfig.preCallTestICEUrl' in params)
|
||||
@@ -260,6 +282,29 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
|
||||
)
|
||||
);
|
||||
|
||||
permanentProperties.overwritesIceServers = Boolean(Object.keys(params).find(k => k.startsWith('iceServers')));
|
||||
|
||||
const customToolbarButtons = params['config.customToolbarButtons'] ?? [];
|
||||
|
||||
permanentProperties.overwritesCustomButtonsWithURL = Boolean(
|
||||
customToolbarButtons.find(({ icon }: { icon: string; }) => {
|
||||
if (typeof icon !== 'string') { // The icon will be ignored
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(icon);
|
||||
|
||||
if (url.protocol === 'data:') {
|
||||
return false;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}));
|
||||
|
||||
// Optionally, include local deployment information based on the
|
||||
// contents of window.config.deploymentInfo.
|
||||
if (deploymentInfo) {
|
||||
|
||||
@@ -38,7 +38,6 @@ import '../jaas/reducer';
|
||||
import '../large-video/reducer';
|
||||
import '../lobby/reducer';
|
||||
import '../notifications/reducer';
|
||||
import '../overlay/reducer';
|
||||
import '../participants-pane/reducer';
|
||||
import '../polls/reducer';
|
||||
import '../polls-history/reducer';
|
||||
|
||||
@@ -57,7 +57,6 @@ import { INoAudioSignalState } from '../no-audio-signal/reducer';
|
||||
import { INoiseDetectionState } from '../noise-detection/reducer';
|
||||
import { INoiseSuppressionState } from '../noise-suppression/reducer';
|
||||
import { INotificationsState } from '../notifications/reducer';
|
||||
import { IOverlayState } from '../overlay/reducer';
|
||||
import { IParticipantsPaneState } from '../participants-pane/reducer';
|
||||
import { IPollsState } from '../polls/reducer';
|
||||
import { IPollsHistoryState } from '../polls-history/reducer';
|
||||
@@ -147,7 +146,6 @@ export interface IReduxState {
|
||||
'features/noise-detection': INoiseDetectionState;
|
||||
'features/noise-suppression': INoiseSuppressionState;
|
||||
'features/notifications': INotificationsState;
|
||||
'features/overlay': IOverlayState;
|
||||
'features/participants-pane': IParticipantsPaneState;
|
||||
'features/polls': IPollsState;
|
||||
'features/polls-history': IPollsHistoryState;
|
||||
|
||||
@@ -121,7 +121,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
if (isTokenAuthEnabled(config)
|
||||
&& config.tokenAuthUrlAutoRedirect
|
||||
&& state['features/base/jwt'].jwt) {
|
||||
// auto redirect is turned on and we have succesfully logged in
|
||||
// auto redirect is turned on and we have successfully logged in
|
||||
// let's mark that
|
||||
dispatch(setTokenAuthUrlSuccess(true));
|
||||
}
|
||||
|
||||
@@ -404,6 +404,7 @@ export interface IConfig {
|
||||
guestDialOutStatusUrl?: string;
|
||||
guestDialOutUrl?: string;
|
||||
helpCentreURL?: string;
|
||||
hiddenDomain?: string;
|
||||
hiddenPremeetingButtons?: Array<'microphone' | 'camera' | 'select-background' | 'invite' | 'settings'>;
|
||||
hideAddRoomButton?: boolean;
|
||||
hideConferenceSubject?: boolean;
|
||||
|
||||
@@ -131,7 +131,7 @@ export default [
|
||||
'displayJids',
|
||||
'doNotStoreRoom',
|
||||
'doNotFlipLocalVideo',
|
||||
'dropbox',
|
||||
'dropbox.appKey',
|
||||
'e2eeLabels',
|
||||
'e2ee',
|
||||
'e2eping',
|
||||
@@ -169,14 +169,12 @@ export default [
|
||||
'hideRecordingLabel',
|
||||
'hideParticipantsStats',
|
||||
'hideConferenceTimer',
|
||||
'hiddenDomain',
|
||||
'hideAddRoomButton',
|
||||
'hideEmailInSettings',
|
||||
'hideLobbyButton',
|
||||
'hosts',
|
||||
'iAmRecorder',
|
||||
'iAmSipGateway',
|
||||
'iceTransportPolicy',
|
||||
'ignoreStartMuted',
|
||||
'inviteAppName',
|
||||
'liveStreaming.enabled',
|
||||
@@ -192,7 +190,11 @@ export default [
|
||||
'notificationTimeouts',
|
||||
'openSharedDocumentOnJoin',
|
||||
'opusMaxAverageBitrate',
|
||||
'p2p',
|
||||
'p2p.backToP2PDelay',
|
||||
'p2p.codecPreferenceOrder',
|
||||
'p2p.enabled',
|
||||
'p2p.iceTransportPolicy',
|
||||
'p2p.mobileCodecPreferenceOrder',
|
||||
'participantMenuButtonsWithNotifyClick',
|
||||
'participantsPane',
|
||||
'pcStatsInterval',
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
/**
|
||||
* Prefixes of devices that will be filtered from the device list.
|
||||
*
|
||||
* NOTE: Currently we filter only 'Microsoft Teams Audio Device' virtual device. It seems that it can't be set
|
||||
* NOTE: It seems that the filtered devices can't be set
|
||||
* as default device on the OS level and this use case is not handled in the code. If we add more device prefixes that
|
||||
* can be default devices we should make sure to handle the default device use case.
|
||||
*/
|
||||
export const DEVICE_LABEL_PREFIXES_TO_IGNORE = [ 'Microsoft Teams Audio Device' ];
|
||||
export const DEVICE_LABEL_PREFIXES_TO_IGNORE = [
|
||||
'Microsoft Teams Audio Device',
|
||||
'ZoomAudioDevice'
|
||||
];
|
||||
|
||||
@@ -96,7 +96,7 @@ function _audio(state: IAudioState = _AUDIO_INITIAL_MEDIA_STATE, action: AnyActi
|
||||
const DEFAULT_INITIAL_PROMISE_STATE = createDeferred<IInitialGUMPromiseResult>();
|
||||
|
||||
/**
|
||||
* Reducer fot the common properties in media state.
|
||||
* Reducer for the common properties in media state.
|
||||
*
|
||||
* @param {ICommonState} state - Common media state.
|
||||
* @param {Object} action - Action object.
|
||||
|
||||
@@ -240,7 +240,7 @@ export function participantJoined(participant: IParticipant) {
|
||||
// conference. The following check is really necessary because a
|
||||
// JitsiConference may have moved into leaving but may still manage to
|
||||
// sneak a PARTICIPANT_JOINED in if its leave is delayed for any purpose
|
||||
// (which is not outragous given that leaving involves network
|
||||
// (which is not outrageous given that leaving involves network
|
||||
// requests.)
|
||||
const stateFeaturesBaseConference
|
||||
= getState()['features/base/conference'];
|
||||
|
||||
@@ -52,7 +52,7 @@ StateListenerRegistry.register(
|
||||
|
||||
/**
|
||||
* Compares the old and new screenshare lists provided and creates/removes the virtual screenshare participant
|
||||
* tiles accodingly.
|
||||
* tiles accordingly.
|
||||
*
|
||||
* @param {Array<string>} oldScreenshareSourceNames - List of old screenshare source names.
|
||||
* @param {Array<string>} newScreenshareSourceNames - Current list of screenshare source names.
|
||||
|
||||
@@ -227,14 +227,14 @@ class MeetingsList extends Component<IProps> {
|
||||
<Container
|
||||
className = { rootClassName }
|
||||
key = { index }
|
||||
onClick = { onPress }>
|
||||
onClick = { onPress }
|
||||
tabIndex = { 0 }>
|
||||
<Container className = 'right-column'>
|
||||
<Text
|
||||
className = 'title'
|
||||
onClick = { onPress }
|
||||
onKeyPress = { onKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
role = 'button'>
|
||||
{ title }
|
||||
</Text>
|
||||
{
|
||||
|
||||
@@ -41,8 +41,8 @@ export function formatURLText(text = '') {
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
// This will be the case for invalid URLs or URLs without a host (emails for example). In this case beacuse
|
||||
// of the issue with PunycodeJS that truncates parts of the text when there is '@' we split the text by '@'
|
||||
// This will be the case for invalid URLs or URLs without a host (emails for example). In this case due to
|
||||
// the issue with PunycodeJS that truncates parts of the text when there is '@' we split the text by '@'
|
||||
// and use punycode for every separate part to prevent homograph attacks.
|
||||
result = text.split('@').map(punycode.toASCII)
|
||||
.join('@');
|
||||
@@ -59,7 +59,7 @@ export function formatURLText(text = '') {
|
||||
*/
|
||||
export function getSupportUrl(stateful: IStateful) {
|
||||
|
||||
// TODO: Once overwriting trough interface config is completelly gone we should think of a way to be able to set
|
||||
// TODO: Once overwriting through interface config is completely gone we should think of a way to be able to set
|
||||
// the value in the branding and not return the default value from interface config.
|
||||
return toState(stateful)['features/dynamic-branding'].supportUrl || interfaceConfig?.SUPPORT_URL;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ declare let __DEV__: any;
|
||||
/**
|
||||
* Mixed type of the element (subtree) config. If it's a {@code boolean} (and is
|
||||
* {@code true}), we persist the entire subtree. If it's an {@code Object}, we
|
||||
* perist a filtered subtree based on the properties of the config object.
|
||||
* persist a filtered subtree based on the properties of the config object.
|
||||
*/
|
||||
declare type ElementConfig = boolean | Object;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { setAspectRatio, setReducedUI } from './actions';
|
||||
|
||||
|
||||
/**
|
||||
* Middleware that handles widnow dimension changes and updates the aspect ratio and
|
||||
* Middleware that handles window dimension changes and updates the aspect ratio and
|
||||
* reduced UI modes accordingly.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
|
||||
@@ -359,8 +359,7 @@ export function createInitialAVTracks(options: ICreateInitialTracksOptions, reco
|
||||
return (dispatch: IStore['dispatch'], _getState: IStore['getState']) => {
|
||||
const {
|
||||
devices,
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent
|
||||
timeout
|
||||
} = options;
|
||||
|
||||
dispatch(gumPending(devices, IGUMPendingState.PENDING_UNMUTE));
|
||||
@@ -400,16 +399,14 @@ export function createInitialAVTracks(options: ICreateInitialTracksOptions, reco
|
||||
if (devices.includes(MEDIA_TYPE.AUDIO)) {
|
||||
gUMPromises.push(createLocalTracksF({
|
||||
devices: [ MEDIA_TYPE.AUDIO ],
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent
|
||||
timeout
|
||||
}));
|
||||
}
|
||||
|
||||
if (devices.includes(MEDIA_TYPE.VIDEO)) {
|
||||
gUMPromises.push(createLocalTracksF({
|
||||
devices: [ MEDIA_TYPE.VIDEO ],
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent
|
||||
timeout
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,7 @@ export * from './functions.any';
|
||||
* and/or 'video'.
|
||||
* @param {string|null} [options.micDeviceId] - Microphone device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
|
||||
* @param {boolean} [options.firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
|
||||
* should check for a {@code getUserMedia} permission prompt and fire a
|
||||
* corresponding event.
|
||||
* @param {number|undefined} [options.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
|
||||
* @param {IStore} store - The redux store in the context of which the function
|
||||
* is to execute and from which state such as {@code config} is to be retrieved.
|
||||
* @returns {Promise<JitsiLocalTrack[]>}
|
||||
|
||||
@@ -31,10 +31,7 @@ export * from './functions.any';
|
||||
* and/or 'video'.
|
||||
* @param {string|null} [options.micDeviceId] - Microphone device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
|
||||
* @param {boolean} [options.firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
|
||||
* should check for a {@code getUserMedia} permission prompt and fire a
|
||||
* corresponding event.
|
||||
* @param {number|undefined} [options.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
|
||||
* @param {IStore} store - The redux store in the context of which the function
|
||||
* is to execute and from which state such as {@code config} is to be retrieved.
|
||||
* @param {boolean} recordTimeMetrics - If true time metrics will be recorded.
|
||||
@@ -45,7 +42,6 @@ export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore,
|
||||
const {
|
||||
desktopSharingSourceDevice,
|
||||
desktopSharingSources,
|
||||
firePermissionPromptIsShownEvent,
|
||||
timeout
|
||||
} = options;
|
||||
|
||||
@@ -91,7 +87,6 @@ export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore,
|
||||
effects,
|
||||
facingMode: options.facingMode || getCameraFacingMode(state),
|
||||
firefox_fake_device, // eslint-disable-line camelcase
|
||||
firePermissionPromptIsShownEvent,
|
||||
micDeviceId,
|
||||
resolution,
|
||||
timeout
|
||||
@@ -146,7 +141,6 @@ export function createPrejoinTracks() {
|
||||
if (requestedAudio || requestedVideo) {
|
||||
tryCreateLocalTracks = createLocalTracksF({
|
||||
devices: initialDevices,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
timeout
|
||||
}, APP.store)
|
||||
.catch(async (err: Error) => {
|
||||
@@ -163,7 +157,6 @@ export function createPrejoinTracks() {
|
||||
if (requestedAudio) {
|
||||
gUMPromises.push(createLocalTracksF({
|
||||
devices: [ MEDIA_TYPE.AUDIO ],
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
timeout
|
||||
}));
|
||||
}
|
||||
@@ -171,7 +164,6 @@ export function createPrejoinTracks() {
|
||||
if (requestedVideo) {
|
||||
gUMPromises.push(createLocalTracksF({
|
||||
devices: [ MEDIA_TYPE.VIDEO ],
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
timeout
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ export interface ITrackOptions {
|
||||
desktopSharingSources?: string[];
|
||||
devices?: string[];
|
||||
facingMode?: string;
|
||||
firePermissionPromptIsShownEvent?: boolean;
|
||||
micDeviceId?: string | null;
|
||||
timeout?: number;
|
||||
}
|
||||
@@ -76,7 +75,6 @@ export interface IShareOptions {
|
||||
|
||||
export interface ICreateInitialTracksOptions {
|
||||
devices: Array<MediaType>;
|
||||
firePermissionPromptIsShownEvent?: boolean;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ const IconButton: React.FC<IIconButtonProps> = ({
|
||||
underlayColor = { underlayColor }>
|
||||
<Icon
|
||||
color = { color }
|
||||
size = { 20 || size }
|
||||
size = { size ?? 20 }
|
||||
src = { src } />
|
||||
</TouchableHighlight>
|
||||
);
|
||||
|
||||
@@ -98,6 +98,9 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
'&.error': {
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.textError}`
|
||||
},
|
||||
'&.clearable-input': {
|
||||
paddingRight: '46px'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -121,10 +124,6 @@ const useStyles = makeStyles()(theme => {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
|
||||
clearableInput: {
|
||||
paddingRight: '46px'
|
||||
},
|
||||
|
||||
clearButton: {
|
||||
position: 'absolute',
|
||||
right: '16px',
|
||||
@@ -185,6 +184,7 @@ const Input = React.forwardRef<any, IProps>(({
|
||||
}: IProps, ref) => {
|
||||
const { classes: styles, cx } = useStyles();
|
||||
const isMobile = isMobileBrowser();
|
||||
const showClearIcon = clearable && value !== '' && !disabled;
|
||||
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
|
||||
onChange?.(e.target.value), []);
|
||||
@@ -211,7 +211,7 @@ const Input = React.forwardRef<any, IProps>(({
|
||||
autoComplete = { autoComplete }
|
||||
autoFocus = { autoFocus }
|
||||
className = { cx(styles.input, isMobile && 'is-mobile',
|
||||
error && 'error', clearable && styles.clearableInput, icon && 'icon-input') }
|
||||
error && 'error', showClearIcon && 'clearable-input', icon && 'icon-input') }
|
||||
disabled = { disabled }
|
||||
id = { id }
|
||||
maxLength = { maxLength }
|
||||
@@ -232,7 +232,7 @@ const Input = React.forwardRef<any, IProps>(({
|
||||
autoComplete = { autoComplete }
|
||||
autoFocus = { autoFocus }
|
||||
className = { cx(styles.input, isMobile && 'is-mobile',
|
||||
error && 'error', clearable && styles.clearableInput, icon && 'icon-input') }
|
||||
error && 'error', showClearIcon && 'clearable-input', icon && 'icon-input') }
|
||||
data-testid = { testId }
|
||||
disabled = { disabled }
|
||||
id = { id }
|
||||
@@ -252,7 +252,7 @@ const Input = React.forwardRef<any, IProps>(({
|
||||
type = { type }
|
||||
value = { value } />
|
||||
)}
|
||||
{clearable && !disabled && value !== '' && <button className = { styles.clearButton }>
|
||||
{showClearIcon && <button className = { styles.clearButton }>
|
||||
<Icon
|
||||
onClick = { clearInput }
|
||||
size = { 20 }
|
||||
|
||||
@@ -19,12 +19,12 @@ interface ISelectProps {
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* Wether or not the select is disabled.
|
||||
* Whether or not the select is disabled.
|
||||
*/
|
||||
disabled?: boolean;
|
||||
|
||||
/**
|
||||
* Wether or not the select is in the error state.
|
||||
* Whether or not the select is in the error state.
|
||||
*/
|
||||
error?: boolean;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { SECURITY_URL } from './contants';
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @param {Function} t - The translation function.
|
||||
* @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warining.
|
||||
* @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warning.
|
||||
* @returns {Text}
|
||||
*/
|
||||
export default function getUnsafeRoomText(state: IReduxState, t: Function, context: 'meeting' | 'prejoin' | 'welcome') {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { SECURITY_URL } from './contants';
|
||||
* Gets the unsafe room text for the given context.
|
||||
*
|
||||
* @param {Function} t - The translation function.
|
||||
* @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warining.
|
||||
* @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warning.
|
||||
* @returns {string}
|
||||
*/
|
||||
export default function getUnsafeRoomText(t: Function, context: 'meeting' | 'prejoin' | 'welcome') {
|
||||
|
||||
@@ -18,7 +18,7 @@ import CalendarListContent from './CalendarListContent.native';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The tyoe of the React {@code Component} props of {@link CalendarList}.
|
||||
* The type of the React {@code Component} props of {@link CalendarList}.
|
||||
*/
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ export default createStyleSheet({
|
||||
},
|
||||
|
||||
/**
|
||||
* The container for all the lines if the norification.
|
||||
* The container for all the lines if the notification.
|
||||
*/
|
||||
notificationTextContainer: {
|
||||
flexDirection: 'column',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Theme } from '@mui/material';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
@@ -264,7 +264,7 @@ const ChatMessage = ({
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
const renderReactions = () => {
|
||||
const renderReactions = useMemo(() => {
|
||||
if (!message.reactions || message.reactions.size === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -321,7 +321,7 @@ const ChatMessage = ({
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
}, [ message?.reactions, isHovered, isReactionsOpen ]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -366,7 +366,7 @@ const ChatMessage = ({
|
||||
<div className = { classes.chatMessageFooterLeft }>
|
||||
{message.reactions && message.reactions.size > 0 && (
|
||||
<>
|
||||
{renderReactions()}
|
||||
{renderReactions}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -11,12 +11,12 @@ import { DISMISS_CALENDAR_NOTIFICATION } from './actionTypes';
|
||||
*
|
||||
* @param {JitsiParticipant} participant - The {@link JitsiParticipant}
|
||||
* instance which initiated the kick event.
|
||||
* @param {?Function} submit - The function to execute after submiting the dialog.
|
||||
* @param {?Function} submit - The function to execute after submitting the dialog.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function notifyKickedOut(participant: any, submit?: Function) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
if (!participant || participant?.isReplaced?.()) {
|
||||
if (participant?.isReplaced?.()) {
|
||||
submit?.();
|
||||
|
||||
return;
|
||||
@@ -24,9 +24,9 @@ export function notifyKickedOut(participant: any, submit?: Function) {
|
||||
|
||||
dispatch(openDialog(AlertDialog, {
|
||||
contentKey: {
|
||||
key: 'dialog.kickTitle',
|
||||
key: participant ? 'dialog.kickTitle' : 'dialog.kickSystemTitle',
|
||||
params: {
|
||||
participantDisplayName: getParticipantDisplayName(getState, participant.getId())
|
||||
participantDisplayName: participant && getParticipantDisplayName(getState, participant.getId())
|
||||
}
|
||||
},
|
||||
onSubmit: submit
|
||||
@@ -38,7 +38,7 @@ export function notifyKickedOut(participant: any, submit?: Function) {
|
||||
* Notify that we've been kicked out of the conference.
|
||||
*
|
||||
* @param {string} reasonKey - The translation key for the reason why the conference failed.
|
||||
* @param {?Function} submit - The function to execute after submiting the dialog.
|
||||
* @param {?Function} submit - The function to execute after submitting the dialog.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function notifyConferenceFailed(reasonKey: string, submit?: Function) {
|
||||
|
||||
@@ -27,12 +27,17 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const { dispatch } = store;
|
||||
const { participant } = action;
|
||||
|
||||
// we need to first finish dispatching or the notification can be cleared out
|
||||
const result = next(action);
|
||||
|
||||
const participantDisplayName
|
||||
= getParticipantDisplayName(store.getState, participant.getId());
|
||||
= participant && getParticipantDisplayName(store.getState, participant.getId());
|
||||
|
||||
dispatch(hangup(true, i18next.t('dialog.kickTitle', { participantDisplayName })));
|
||||
dispatch(hangup(true,
|
||||
participantDisplayName ? i18next.t('dialog.kickTitle', { participantDisplayName })
|
||||
: i18next.t('dialog.kickSystemTitle')));
|
||||
|
||||
break;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ interface IProps extends AbstractDialogTabProps, WithTranslation {
|
||||
iAmVisitor: boolean;
|
||||
|
||||
/**
|
||||
* Wether noise suppression is on or not.
|
||||
* Whether noise suppression is on or not.
|
||||
*/
|
||||
noiseSuppressionEnabled: boolean;
|
||||
|
||||
|
||||
@@ -106,7 +106,8 @@ const StageParticipantNameLabel = () => {
|
||||
classes.badgeContainer,
|
||||
toolboxVisible && classes.containerElevated,
|
||||
_isScreenShareParticipant && classes.screenSharing
|
||||
) }>
|
||||
) }
|
||||
data-testid = 'stage-display-name' >
|
||||
<DisplayNameBadge name = { nameToDisplay } />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -50,7 +50,7 @@ export function setMediaEncryptionKey(keyInfo: Object) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to start participant e2ee verficiation process.
|
||||
* Dispatches an action to start participant e2ee verification process.
|
||||
*
|
||||
* @param {string} pId - The participant id.
|
||||
* @returns {{
|
||||
@@ -69,7 +69,7 @@ export function startVerification(pId: string) {
|
||||
* Dispatches an action to set participant e2ee verification status.
|
||||
*
|
||||
* @param {string} pId - The participant id.
|
||||
* @param {boolean} isVerified - The verifcation status.
|
||||
* @param {boolean} isVerified - The verification status.
|
||||
* @returns {{
|
||||
* type: PARTICIPANT_VERIFIED,
|
||||
* pId: string,
|
||||
|
||||
@@ -153,8 +153,8 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
local: true
|
||||
},
|
||||
{
|
||||
id: actor.getId(),
|
||||
name: actor.getDisplayName()
|
||||
id: actor?.getId(),
|
||||
name: actor?.getDisplayName()
|
||||
}
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -57,7 +57,7 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
|
||||
remoteParticipants.delete(speaker);
|
||||
}
|
||||
|
||||
// Always update the order of the thumnails.
|
||||
// Always update the order of the thubmnails.
|
||||
const participantsWithScreenShare = screenShareParticipants.reduce<string[]>((acc, screenshare) => {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ export function getFilmstripDimensions({
|
||||
/**
|
||||
* Returns true if the local thumbnail should be displayed separately and false otherwise.
|
||||
*
|
||||
* @returns {boolean} - True if the local thumbnail should be displayed separately and flase otherwise.
|
||||
* @returns {boolean} - True if the local thumbnail should be displayed separately and false otherwise.
|
||||
*/
|
||||
export function shouldDisplayLocalThumbnailSeparately() {
|
||||
// XXX Our current design is to have the local participant separate from
|
||||
|
||||
@@ -322,7 +322,7 @@ export function calculateResponsiveTileViewDimensions({
|
||||
for (let c = 1; c <= Math.min(maxColumns, numberOfParticipants, desiredNumberOfVisibleTiles); c++) {
|
||||
const r = Math.ceil(numberOfParticipants / c);
|
||||
|
||||
// we want to display as much as possible tumbnails up to desiredNumberOfVisibleTiles
|
||||
// we want to display as much as possible thumbnails up to desiredNumberOfVisibleTiles
|
||||
const visibleRows
|
||||
= numberOfParticipants <= desiredNumberOfVisibleTiles ? r : Math.floor(desiredNumberOfVisibleTiles / c);
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ export function updateKnownLargeVideoResolution(resolution: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dimenstions of the large video in redux.
|
||||
* Sets the dimensions of the large video in redux.
|
||||
*
|
||||
* @param {number} height - The height of the large video.
|
||||
* @param {number} width - The width of the large video.
|
||||
|
||||
@@ -9,7 +9,7 @@ import { isLocalVideoTrackDesktop } from '../../base/tracks/functions.any';
|
||||
* Checks whether full-screen state should be used or not.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {boolean} - Whether full-screen state shuld be used or not.
|
||||
* @returns {boolean} - Whether full-screen state should be used or not.
|
||||
*/
|
||||
export function shouldUseFullScreen(state: IReduxState) {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
|
||||
@@ -350,6 +350,7 @@ const Notification = ({
|
||||
onClick = { onDismiss }
|
||||
size = { 20 }
|
||||
src = { IconCloseLarge }
|
||||
tabIndex = { 0 }
|
||||
testId = { `${titleKey || descriptionKey}-dismiss` } />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* The type of the Redux action which signals that the prompt for media
|
||||
* permission is visible or not.
|
||||
*
|
||||
* {
|
||||
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
* isVisible: {boolean},
|
||||
* browser: {string}
|
||||
* }
|
||||
* @public
|
||||
*/
|
||||
export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
|
||||
= 'MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED';
|
||||
@@ -3,23 +3,6 @@ import { openDialog } from '../base/dialog/actions';
|
||||
import PageReloadDialog from '../base/dialog/components/native/PageReloadDialog';
|
||||
|
||||
|
||||
/**
|
||||
* Signals that the prompt for media permission is visible or not.
|
||||
*
|
||||
* @param {boolean} _isVisible - If the value is true - the prompt for media
|
||||
* permission is visible otherwise the value is false/undefined.
|
||||
* @param {string} _browser - The name of the current browser.
|
||||
* @public
|
||||
* @returns {{
|
||||
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
* browser: {string},
|
||||
* isVisible: {boolean}
|
||||
* }}
|
||||
*/
|
||||
export function mediaPermissionPromptVisibilityChanged(_isVisible: boolean, _browser: string) {
|
||||
// Dummy.
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@link PageReloadDialog}.
|
||||
*
|
||||
|
||||
@@ -1,26 +1,3 @@
|
||||
import { MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED } from './actionTypes';
|
||||
|
||||
|
||||
/**
|
||||
* Signals that the prompt for media permission is visible or not.
|
||||
*
|
||||
* @param {boolean} isVisible - If the value is true - the prompt for media
|
||||
* permission is visible otherwise the value is false/undefined.
|
||||
* @param {string} browser - The name of the current browser.
|
||||
* @public
|
||||
* @returns {{
|
||||
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
* browser: {string},
|
||||
* isVisible: {boolean}
|
||||
* }}
|
||||
*/
|
||||
export function mediaPermissionPromptVisibilityChanged(isVisible: boolean, browser: string) {
|
||||
return {
|
||||
type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
browser,
|
||||
isVisible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@link PageReloadDialog}.
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate, translateToHTML } from '../../../base/i18n/functions';
|
||||
|
||||
import OverlayFrame from './OverlayFrame';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link UserMediaPermissionsOverlay}.
|
||||
*/
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
_premeetingBackground?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a React Component for overlay with guidance how to proceed with
|
||||
* gUM prompt.
|
||||
*/
|
||||
class UserMediaPermissionsOverlay extends Component<IProps> {
|
||||
/**
|
||||
* Determines whether this overlay needs to be rendered (according to a
|
||||
* specific redux state). Called by {@link OverlayContainer}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean} - If this overlay needs to be rendered, {@code true};
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
static needsRender(state: IReduxState) {
|
||||
return state['features/overlay'].isMediaPermissionPromptVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _premeetingBackground, t } = this.props;
|
||||
const style = _premeetingBackground ? {
|
||||
background: _premeetingBackground,
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: 'cover'
|
||||
} : {};
|
||||
|
||||
return (
|
||||
<OverlayFrame style = { style }>
|
||||
<div className = 'inlay'>
|
||||
<span className = 'inlay__icon icon-microphone' />
|
||||
<span className = 'inlay__icon icon-camera' />
|
||||
<h3
|
||||
aria-label = { t('startupoverlay.genericTitle') }
|
||||
className = 'inlay__title'
|
||||
role = 'alert' >
|
||||
{
|
||||
t('startupoverlay.genericTitle')
|
||||
}
|
||||
</h3>
|
||||
<span
|
||||
className = 'inlay__text'
|
||||
role = 'alert' >
|
||||
{
|
||||
t('userMedia.grantPermissions')
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div className = 'policy overlay__policy'>
|
||||
<p
|
||||
className = 'policy__text'
|
||||
role = 'alert'>
|
||||
{ translateToHTML(t, 'startupoverlay.policyText') }
|
||||
</p>
|
||||
{
|
||||
this._renderPolicyLogo()
|
||||
}
|
||||
</div>
|
||||
</OverlayFrame>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the policy logo.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement|null}
|
||||
*/
|
||||
_renderPolicyLogo() {
|
||||
const policyLogoSrc = interfaceConfig.POLICY_LOGO;
|
||||
|
||||
if (policyLogoSrc) {
|
||||
return (
|
||||
<div className = 'policy__logo'>
|
||||
<img
|
||||
alt = { this.props.t('welcomepage.logo.policyLogo') }
|
||||
src = { policyLogoSrc } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the React {@code Component} props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {Object} ownProps - The props passed to the component.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
const { premeetingBackground } = state['features/dynamic-branding'];
|
||||
|
||||
return {
|
||||
_premeetingBackground: premeetingBackground
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(UserMediaPermissionsOverlay));
|
||||
@@ -2,7 +2,6 @@ import { IReduxState } from '../app/types';
|
||||
|
||||
import PageReloadOverlay from './components/web/PageReloadOverlay';
|
||||
import SuspendedOverlay from './components/web/SuspendedOverlay';
|
||||
import UserMediaPermissionsOverlay from './components/web/UserMediaPermissionsOverlay';
|
||||
|
||||
/**
|
||||
* Returns the overlay to be currently rendered.
|
||||
@@ -13,8 +12,7 @@ import UserMediaPermissionsOverlay from './components/web/UserMediaPermissionsOv
|
||||
export function getOverlayToRender(state: IReduxState) {
|
||||
const overlays = [
|
||||
PageReloadOverlay,
|
||||
SuspendedOverlay,
|
||||
UserMediaPermissionsOverlay
|
||||
SuspendedOverlay
|
||||
];
|
||||
|
||||
for (const overlay of overlays) {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
import { assign } from '../base/redux/functions';
|
||||
|
||||
import { MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED } from './actionTypes';
|
||||
|
||||
|
||||
export interface IOverlayState {
|
||||
browser?: string;
|
||||
isMediaPermissionPromptVisible?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the redux actions of the feature overlay.
|
||||
*
|
||||
* FIXME: these pieces of state should probably be in a different place.
|
||||
*/
|
||||
ReducerRegistry.register<IOverlayState>('features/overlay', (state = {}, action): IOverlayState => {
|
||||
switch (action.type) {
|
||||
case MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED:
|
||||
return _mediaPermissionPromptVisibilityChanged(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
|
||||
/**
|
||||
* Reduces a specific redux action MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED of
|
||||
* the feature overlay.
|
||||
*
|
||||
* @param {Object} state - The redux state of the feature overlay.
|
||||
* @param {Action} action - The redux action to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature overlay after the reduction of
|
||||
* the specified action.
|
||||
*/
|
||||
function _mediaPermissionPromptVisibilityChanged(
|
||||
state: IOverlayState,
|
||||
{ browser, isVisible }: { browser?: string; isVisible?: boolean; }) {
|
||||
return assign(state, {
|
||||
browser,
|
||||
isMediaPermissionPromptVisible: isVisible
|
||||
});
|
||||
}
|
||||
@@ -286,7 +286,7 @@ export const isMuteAllVisible = (state: IReduxState) => {
|
||||
* Returns true if renaming the currently joined breakout room is allowed and false otherwise.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {boolean} - True if reanming the currently joined breakout room is allowed and false otherwise.
|
||||
* @returns {boolean} - True if renaming the currently joined breakout room is allowed and false otherwise.
|
||||
*/
|
||||
export function isCurrentRoomRenamable(state: IReduxState) {
|
||||
return isInBreakoutRoom(state) && isBreakoutRoomRenameAllowed(state);
|
||||
|
||||
@@ -117,7 +117,7 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
|
||||
return () => BackHandler.removeEventListener('hardwareBackPress', goBack);
|
||||
|
||||
}, []); // dispatch is not in the dependancy list because we want the action to be dispatched only once when
|
||||
}, []); // dispatch is not in the dependency list because we want the action to be dispatched only once when
|
||||
// the component is mounted.
|
||||
|
||||
const headerLeft = () => {
|
||||
|
||||
@@ -274,7 +274,6 @@ export function showStartedRecordingNotification(
|
||||
if (recordingSharingUrl
|
||||
&& isVpaasMeeting(state)
|
||||
&& iAmRecordingInitiator
|
||||
&& showRecordingLink
|
||||
&& !isSavingRecordingOnDropbox(state)) {
|
||||
const region = getMeetingRegion(state);
|
||||
const tenant = getVpaasTenant(state);
|
||||
@@ -288,15 +287,17 @@ export function showStartedRecordingNotification(
|
||||
}
|
||||
|
||||
// add the option to copy recording link
|
||||
notifyProps.dialogProps = {
|
||||
...notifyProps.dialogProps,
|
||||
customActionNameKey: [ 'recording.copyLink' ],
|
||||
customActionHandler: [ () => copyText(link) ],
|
||||
titleKey: 'recording.on',
|
||||
descriptionKey: 'recording.linkGenerated'
|
||||
};
|
||||
if (showRecordingLink) {
|
||||
notifyProps.dialogProps = {
|
||||
...notifyProps.dialogProps,
|
||||
customActionNameKey: [ 'recording.copyLink' ],
|
||||
customActionHandler: [ () => copyText(link) ],
|
||||
titleKey: 'recording.on',
|
||||
descriptionKey: 'recording.linkGenerated'
|
||||
};
|
||||
|
||||
notifyProps.type = NOTIFICATION_TIMEOUT_TYPE.STICKY;
|
||||
notifyProps.type = NOTIFICATION_TIMEOUT_TYPE.STICKY;
|
||||
}
|
||||
} catch (err) {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'recording.errorFetchingLink'
|
||||
|
||||
@@ -216,11 +216,11 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
if (updatedSessionData?.status === ON) {
|
||||
|
||||
// We receive 2 updates of the session status ON. The first one is from jibri when it joins.
|
||||
// The second one is from jicofo which will deliever the initiator value. Since the start
|
||||
// The second one is from jicofo which will deliver the initiator value. Since the start
|
||||
// recording notification uses the initiator value we skip the jibri update and show the
|
||||
// notification on the update from jicofo.
|
||||
// FIXE: simplify checks when the backend start sending only one status ON update containing the
|
||||
// initiator.
|
||||
// FIXME: simplify checks when the backend start sending only one status ON update containing
|
||||
// the initiator.
|
||||
if (initiator && !oldSessionData?.initiator) {
|
||||
if (typeof recordingLimit === 'object') {
|
||||
dispatch(showRecordingLimitNotification(mode));
|
||||
|
||||
@@ -113,7 +113,7 @@ ReducerRegistry.register<IRecordingState>(STORE_NAME,
|
||||
* @param {Array} sessionDatas - The current sessions in the redux store.
|
||||
* @param {Object} newSessionData - The updated session data.
|
||||
* @private
|
||||
* @returns {Array} The session datas with the updated session data added.
|
||||
* @returns {Array} The session data with the updated session data added.
|
||||
*/
|
||||
function _updateSessionDatas(sessionDatas: ISessionData[], newSessionData: ISessionData) {
|
||||
const hasExistingSessionData = sessionDatas.find(
|
||||
@@ -133,7 +133,7 @@ function _updateSessionDatas(sessionDatas: ISessionData[], newSessionData: ISess
|
||||
});
|
||||
} else {
|
||||
// If the session data is not present, then there is nothing to update
|
||||
// and instead it needs to be added to the known session datas.
|
||||
// and instead it needs to be added to the known session data.
|
||||
newSessionDatas = [
|
||||
...sessionDatas,
|
||||
{ ...newSessionData }
|
||||
|
||||
@@ -107,7 +107,7 @@ export function requestRemoteControl(userId: string) {
|
||||
|
||||
dispatch(setRemoteControlActive(true));
|
||||
|
||||
logger.log(`Requsting remote control permissions from: ${userId}`);
|
||||
logger.log(`Requesting remote control permissions from: ${userId}`);
|
||||
|
||||
const { conference } = state['features/base/conference'];
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ export interface IProps extends AbstractDialogTabProps, WithTranslation {
|
||||
showPrejoinSettings: boolean;
|
||||
|
||||
/**
|
||||
* Wether or not the stage filmstrip is enabled.
|
||||
* Whether or not the stage filmstrip is enabled.
|
||||
*/
|
||||
stageFilmstripEnabled: boolean;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface IProps extends AbstractDialogTabProps, WithTranslation {
|
||||
displayShortcuts: boolean;
|
||||
|
||||
/**
|
||||
* Wether the keyboard shortcuts are enabled or not.
|
||||
* Whether the keyboard shortcuts are enabled or not.
|
||||
*/
|
||||
keyboardShortcutsEnabled: boolean;
|
||||
|
||||
|
||||
@@ -28,17 +28,6 @@ export const RESET_SHARED_VIDEO_STATUS = 'RESET_SHARED_VIDEO_STATUS';
|
||||
*/
|
||||
export const SET_CONFIRM_SHOW_VIDEO = 'SET_CONFIRM_SHOW_VIDEO';
|
||||
|
||||
|
||||
/**
|
||||
* The type of the action which signals to disable or enable the shared video
|
||||
* button.
|
||||
*
|
||||
* {
|
||||
* type: SET_DISABLE_BUTTON
|
||||
* }
|
||||
*/
|
||||
export const SET_DISABLE_BUTTON = 'SET_DISABLE_BUTTON';
|
||||
|
||||
/**
|
||||
* The type of the action which sets an array of whitelisted urls.
|
||||
*
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './actions.any';
|
||||
@@ -1,19 +0,0 @@
|
||||
import { SET_DISABLE_BUTTON } from './actionTypes';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Disabled share video button.
|
||||
*
|
||||
* @param {boolean} disabled - The current state of the share video button.
|
||||
* @returns {{
|
||||
* type: SET_DISABLE_BUTTON,
|
||||
* disabled: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setDisableButton(disabled: boolean) {
|
||||
return {
|
||||
type: SET_DISABLE_BUTTON,
|
||||
disabled
|
||||
};
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { IReduxState, IStore } from '../../../app/types';
|
||||
import { getCurrentConference } from '../../../base/conference/functions';
|
||||
import { IJitsiConference } from '../../../base/conference/reducer';
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import { setSharedVideoStatus } from '../../actions.any';
|
||||
import { setSharedVideoStatus } from '../../actions';
|
||||
import { PLAYBACK_STATUSES } from '../../constants';
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@ import { translate } from '../../../base/i18n/functions';
|
||||
import { IconPlay } from '../../../base/icons/svg';
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { toggleSharedVideo } from '../../actions.native';
|
||||
import { toggleSharedVideo } from '../../actions';
|
||||
import { isSharingStatus } from '../../functions';
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,7 @@ import { showWarningNotification } from '../../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
|
||||
import { dockToolbox } from '../../../toolbox/actions';
|
||||
import { muteLocal } from '../../../video-menu/actions.any';
|
||||
import { setSharedVideoStatus, stopSharedVideo } from '../../actions.any';
|
||||
import { setSharedVideoStatus, stopSharedVideo } from '../../actions';
|
||||
import { PLAYBACK_STATUSES } from '../../constants';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
||||
// @ts-expect-error
|
||||
import Filmstrip from '../../../../../modules/UI/videolayout/Filmstrip';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import { getVerticalViewMaxWidth } from '../../../filmstrip/functions.web';
|
||||
import { getToolboxHeight } from '../../../toolbox/functions.web';
|
||||
import { isSharedVideoEnabled } from '../../functions';
|
||||
@@ -39,11 +38,6 @@ interface IProps {
|
||||
*/
|
||||
isEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Is the video shared by the local user.
|
||||
*/
|
||||
isOwner: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the user is actively resizing the filmstrip.
|
||||
*/
|
||||
@@ -124,17 +118,15 @@ class SharedVideo extends Component<IProps> {
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
render() {
|
||||
const { isEnabled, isOwner, isResizing } = this.props;
|
||||
const { isEnabled, isResizing } = this.props;
|
||||
|
||||
if (!isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const className = !isResizing && isOwner ? '' : 'disable-pointer';
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { className }
|
||||
className = { (isResizing && 'disable-pointer') || '' }
|
||||
id = 'sharedVideo'
|
||||
style = { this.getDimensions() }>
|
||||
{this.getManager()}
|
||||
@@ -152,19 +144,16 @@ class SharedVideo extends Component<IProps> {
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { ownerId, videoUrl } = state['features/shared-video'];
|
||||
const { videoUrl } = state['features/shared-video'];
|
||||
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
|
||||
const { visible, isResizing } = state['features/filmstrip'];
|
||||
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
return {
|
||||
clientHeight,
|
||||
clientWidth,
|
||||
filmstripVisible: visible,
|
||||
filmstripWidth: getVerticalViewMaxWidth(state),
|
||||
isEnabled: isSharedVideoEnabled(state),
|
||||
isOwner: ownerId === localParticipant?.id,
|
||||
isResizing,
|
||||
videoUrl
|
||||
};
|
||||
|
||||
@@ -3,8 +3,9 @@ import { connect } from 'react-redux';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconPlay } from '../../../base/icons/svg';
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { toggleSharedVideo } from '../../actions.any';
|
||||
import { toggleSharedVideo } from '../../actions';
|
||||
import { isSharingStatus } from '../../functions';
|
||||
|
||||
interface IProps extends AbstractButtonProps {
|
||||
@@ -83,16 +84,14 @@ class SharedVideoButton extends AbstractButton<IProps> {
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const {
|
||||
disabled: sharedVideoBtnDisabled,
|
||||
status: sharedVideoStatus
|
||||
} = state['features/shared-video'];
|
||||
const { ownerId, status: sharedVideoStatus } = state['features/shared-video'];
|
||||
const localParticipantId = getLocalParticipant(state)?.id;
|
||||
const isSharing = isSharingStatus(sharedVideoStatus ?? '');
|
||||
|
||||
return {
|
||||
_isDisabled: Boolean(sharedVideoBtnDisabled),
|
||||
_sharingVideo: isSharingStatus(sharedVideoStatus ?? '')
|
||||
_isDisabled: isSharing && ownerId !== localParticipantId,
|
||||
_sharingVideo: isSharing
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export default translate(connect(_mapStateToProps)(SharedVideoButton));
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
import './middleware.any';
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
setAllowedUrlDomians,
|
||||
setSharedVideoStatus,
|
||||
showConfirmPlayingDialog
|
||||
} from './actions.any';
|
||||
} from './actions';
|
||||
import {
|
||||
DEFAULT_ALLOWED_URL_DOMAINS,
|
||||
PLAYBACK_START,
|
||||
@@ -55,27 +55,41 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
conference.addCommandListener(SHARED_VIDEO,
|
||||
({ value, attributes }: { attributes: {
|
||||
from: string; muted: string; state: string; time: string; }; value: string; }) => {
|
||||
muted: string; state: string; time: string; }; value: string; },
|
||||
from: string) => {
|
||||
const state = getState();
|
||||
|
||||
const { from } = attributes;
|
||||
const sharedVideoStatus = attributes.state;
|
||||
|
||||
const { ownerId } = state['features/shared-video'];
|
||||
|
||||
if (ownerId && ownerId !== from) {
|
||||
logger.warn(
|
||||
`User with id: ${from} sent shared video command: ${sharedVideoStatus} while we are playing.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSharingStatus(sharedVideoStatus)) {
|
||||
// confirmShowVideo is undefined the first time we receive
|
||||
// when confirmShowVideo is false we ignore everything except stop that resets it
|
||||
if (getState()['features/shared-video'].confirmShowVideo === false) {
|
||||
if (state['features/shared-video'].confirmShowVideo === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isURLAllowedForSharedVideo(value, getState()['features/shared-video'].allowedUrlDomains, true)
|
||||
if (isURLAllowedForSharedVideo(value, state['features/shared-video'].allowedUrlDomains, true)
|
||||
|| localParticipantId === from
|
||||
|| getState()['features/shared-video'].confirmShowVideo) { // if confirmed skip asking again
|
||||
handleSharingVideoStatus(store, value, attributes, conference);
|
||||
|| state['features/shared-video'].confirmShowVideo) { // if confirmed skip asking again
|
||||
handleSharingVideoStatus(store, value, {
|
||||
...attributes,
|
||||
from
|
||||
}, conference);
|
||||
} else {
|
||||
dispatch(showConfirmPlayingDialog(getParticipantDisplayName(getState(), from), () => {
|
||||
dispatch(showConfirmPlayingDialog(getParticipantDisplayName(state, from), () => {
|
||||
|
||||
handleSharingVideoStatus(store, value, attributes, conference);
|
||||
handleSharingVideoStatus(store, value, {
|
||||
...attributes,
|
||||
from
|
||||
}, conference);
|
||||
|
||||
return true; // on mobile this is used to close the dialog
|
||||
}));
|
||||
@@ -87,11 +101,11 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
if (sharedVideoStatus === 'stop') {
|
||||
const videoParticipant = getParticipantById(state, value);
|
||||
|
||||
if (getState()['features/shared-video'].confirmShowVideo === false) {
|
||||
if (state['features/shared-video'].confirmShowVideo === false) {
|
||||
dispatch(showWarningNotification({
|
||||
titleKey: 'dialog.shareVideoLinkStopped',
|
||||
titleArguments: {
|
||||
name: getParticipantDisplayName(getState(), from)
|
||||
name: getParticipantDisplayName(state, from)
|
||||
}
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
|
||||
import { getLocalParticipant } from '../base/participants/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
|
||||
import { setDisableButton } from './actions.web';
|
||||
import { PLAYBACK_STATUSES, SHARED_VIDEO } from './constants';
|
||||
import { isSharedVideoEnabled } from './functions';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const state = getState();
|
||||
const localParticipantId = getLocalParticipant(state)?.id;
|
||||
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOIN_IN_PROGRESS: {
|
||||
if (!isSharedVideoEnabled(state)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const { conference } = action;
|
||||
|
||||
conference.addCommandListener(SHARED_VIDEO, ({ attributes }: { attributes:
|
||||
{ from: string; state: string; }; }) => {
|
||||
const { from } = attributes;
|
||||
const status = attributes.state;
|
||||
|
||||
if (status === PLAYBACK_STATUSES.PLAYING) {
|
||||
if (localParticipantId !== from) {
|
||||
dispatch(setDisableButton(true));
|
||||
}
|
||||
} else if (status === 'stop') {
|
||||
dispatch(setDisableButton(false));
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
RESET_SHARED_VIDEO_STATUS,
|
||||
SET_ALLOWED_URL_DOMAINS,
|
||||
SET_CONFIRM_SHOW_VIDEO,
|
||||
SET_DISABLE_BUTTON,
|
||||
SET_SHARED_VIDEO_STATUS
|
||||
} from './actionTypes';
|
||||
import { DEFAULT_ALLOWED_URL_DOMAINS } from './constants';
|
||||
@@ -16,7 +15,6 @@ const initialState = {
|
||||
export interface ISharedVideoState {
|
||||
allowedUrlDomains: Array<string>;
|
||||
confirmShowVideo?: boolean;
|
||||
disabled?: boolean;
|
||||
muted?: boolean;
|
||||
ownerId?: string;
|
||||
status?: string;
|
||||
@@ -30,7 +28,7 @@ export interface ISharedVideoState {
|
||||
*/
|
||||
ReducerRegistry.register<ISharedVideoState>('features/shared-video',
|
||||
(state = initialState, action): ISharedVideoState => {
|
||||
const { videoUrl, status, time, ownerId, disabled, muted, volume } = action;
|
||||
const { videoUrl, status, time, ownerId, muted, volume } = action;
|
||||
|
||||
switch (action.type) {
|
||||
case RESET_SHARED_VIDEO_STATUS:
|
||||
@@ -55,12 +53,6 @@ ReducerRegistry.register<ISharedVideoState>('features/shared-video',
|
||||
volume
|
||||
};
|
||||
|
||||
case SET_DISABLE_BUTTON:
|
||||
return {
|
||||
...state,
|
||||
disabled
|
||||
};
|
||||
|
||||
case SET_ALLOWED_URL_DOMAINS: {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -196,7 +196,7 @@ function _endpointMessageReceived(store: IStore, next: Function, action: AnyActi
|
||||
// Regex to filter out all possible country codes after language code:
|
||||
// this should catch all notations like 'en-GB' 'en_GB' and 'enGB'
|
||||
// and be independent of the country code length
|
||||
if (json.language.replace(/[-_A-Z].*/, '') !== language) {
|
||||
if (_getPrimaryLanguageCode(json.language) !== _getPrimaryLanguageCode(language)) {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
@@ -260,6 +260,17 @@ function _endpointMessageReceived(store: IStore, next: Function, action: AnyActi
|
||||
return next(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to extract the primary language code like 'en-GB' 'en_GB'
|
||||
* 'enGB' 'zh-CN' and 'zh-TW'.
|
||||
*
|
||||
* @param {string} language - The language to use for translation or user requested.
|
||||
* @returns {string}
|
||||
*/
|
||||
function _getPrimaryLanguageCode(language: string) {
|
||||
return language.replace(/[-_A-Z].*/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the local property 'requestingTranscription'. This will cause Jicofo
|
||||
* and Jigasi to decide whether the transcriber needs to be in the room.
|
||||
|
||||
@@ -10,6 +10,7 @@ import styles from './styles';
|
||||
|
||||
|
||||
interface IProps extends AbstractButtonProps {
|
||||
backgroundColor?: string;
|
||||
icon: any;
|
||||
id?: string;
|
||||
text: string;
|
||||
@@ -21,6 +22,7 @@ interface IProps extends AbstractButtonProps {
|
||||
* @returns {Component}
|
||||
*/
|
||||
class CustomOptionButton extends AbstractButton<IProps> {
|
||||
backgroundColor = this.props.backgroundColor;
|
||||
iconSrc = this.props.icon;
|
||||
id = this.props.id;
|
||||
text = this.props.text;
|
||||
@@ -48,8 +50,7 @@ class CustomOptionButton extends AbstractButton<IProps> {
|
||||
= (
|
||||
<Image
|
||||
source = {{ uri: this.iconSrc }}
|
||||
style = { styles.iconImageStyles }
|
||||
tintColor = { 'white' } />);
|
||||
style = { styles.iconImageStyles } />);
|
||||
}
|
||||
|
||||
return iconComponent;
|
||||
|
||||
@@ -228,11 +228,10 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
_customToolbarButtons.map(({ id, text, icon, ...rest }) => (
|
||||
_customToolbarButtons.map(({ id, text, icon, backgroundColor }) => (
|
||||
<CustomOptionButton
|
||||
{ ...rest }
|
||||
{ ...topButtonProps }
|
||||
|
||||
backgroundColor = { backgroundColor }
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
handleClick = { () =>
|
||||
dispatch(customOverflowMenuButtonPressed(id, text))
|
||||
|
||||
@@ -202,7 +202,7 @@ export function getVisibleButtons({
|
||||
setButtonsNotifyClickMode(allButtons, buttonsWithNotifyClick);
|
||||
|
||||
const filteredButtons = Object.keys(allButtons).filter(key =>
|
||||
typeof key !== 'undefined' // filter invalid buttons that may be comming from config.mainToolbarButtons
|
||||
typeof key !== 'undefined' // filter invalid buttons that may be coming from config.mainToolbarButtons
|
||||
// override
|
||||
&& !jwtDisabledButtons.includes(key)
|
||||
&& isButtonEnabled(key, toolbarButtons));
|
||||
@@ -264,7 +264,7 @@ interface ICSSTransitionObject {
|
||||
* @returns {ICSSTransitionObject}
|
||||
*/
|
||||
export function getTransitionParamsForElementsAboveToolbox(isToolbarVisible: boolean): ICSSTransitionObject {
|
||||
// The transistion time and delay is different to account for the time when the toolbar is about to hide/show but
|
||||
// The transition time and delay is different to account for the time when the toolbar is about to hide/show but
|
||||
// the elements don't have to move.
|
||||
return isToolbarVisible ? {
|
||||
duration: 0.15,
|
||||
|
||||
@@ -142,7 +142,7 @@ function _setFullScreen(next: Function, action: AnyAction) {
|
||||
/**
|
||||
* Common logic to gather buttons that have to notify the api when clicked.
|
||||
*
|
||||
* @param {Array} buttonsWithNotifyClick - The array of systme buttons that need to notify the api.
|
||||
* @param {Array} buttonsWithNotifyClick - The array of system buttons that need to notify the api.
|
||||
* @param {Array} customButtons - The custom buttons.
|
||||
* @returns {Array}
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@ import { isWhiteboardParticipant } from '../../../base/participants/functions';
|
||||
import { IParticipant } from '../../../base/participants/types';
|
||||
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
|
||||
import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup';
|
||||
import { stopSharedVideo } from '../../../shared-video/actions.any';
|
||||
import { stopSharedVideo } from '../../../shared-video/actions';
|
||||
import { getParticipantMenuButtonsWithNotifyClick, showOverflowDrawer } from '../../../toolbox/functions.web';
|
||||
import { NOTIFY_CLICK_MODE } from '../../../toolbox/types';
|
||||
import { setWhiteboardOpen } from '../../../whiteboard/actions';
|
||||
|
||||
@@ -29,7 +29,7 @@ export function setMaxReceiverVideoQualityForLargeVideo(maxReceiverVideoQuality:
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the max frame height that should be received for the screen sharing filmstrip particpant.
|
||||
* Sets the max frame height that should be received for the screen sharing filmstrip participant.
|
||||
*
|
||||
* @param {number} maxReceiverVideoQuality - The max video frame height to
|
||||
* receive.
|
||||
|
||||
@@ -309,7 +309,7 @@ StateListenerRegistry.register(
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the source names asociated with the given participants list.
|
||||
* Returns the source names associated with the given participants list.
|
||||
*
|
||||
* @param {Array<string>} participantList - The list of participants.
|
||||
* @param {Object} state - The redux state.
|
||||
|
||||
@@ -41,7 +41,7 @@ module:hook("pre-iq/full", function(event)
|
||||
session.granted_jitsi_meet_context_features,
|
||||
occupant.role == 'moderator');
|
||||
|
||||
if jibri.attr.action == 'start' then
|
||||
if jibri.attr.action == 'start' or jibri.attr.action == 'stop' then
|
||||
if not is_allowed then
|
||||
module:log('info', 'Filtering jibri start recording, stanza:%s', tostring(stanza));
|
||||
session.send(st.error_reply(stanza, 'auth', 'forbidden'));
|
||||
|
||||
@@ -141,7 +141,7 @@ module:hook('muc-occupant-pre-join', function (event)
|
||||
else
|
||||
occupant.role = 'visitor';
|
||||
end
|
||||
elseif room.moderators_list:contains(resource) then
|
||||
elseif room.moderators_list and room.moderators_list:contains(resource) then
|
||||
-- remote participants, host is the main prosody
|
||||
occupant.role = 'moderator';
|
||||
end
|
||||
|
||||
@@ -115,9 +115,10 @@ function handle_kick_participant (event)
|
||||
end
|
||||
|
||||
local number = params["number"];
|
||||
local participantId = params["participantId"];
|
||||
|
||||
if not number then
|
||||
module:log("warn", "Missing number param");
|
||||
if (not number and not participantId) or (number and participantId) then
|
||||
module:log("warn", "Invalid parameters: exactly one of 'number' or 'participantId' must be provided.");
|
||||
return { status_code = 400; };
|
||||
end
|
||||
|
||||
@@ -134,10 +135,8 @@ function handle_kick_participant (event)
|
||||
|
||||
for _, occupant in room:each_occupant() do
|
||||
local pr = occupant:get_presence();
|
||||
local displayName = pr:get_child_text(
|
||||
'nick', 'http://jabber.org/protocol/nick');
|
||||
|
||||
if is_sip_jigasi(pr) and displayName and starts_with(displayName, number) then
|
||||
if is_participant_match(pr, number, participantId) then
|
||||
room:set_role(true, occupant.nick, nil);
|
||||
module:log('info', 'Occupant kicked %s from %s', occupant.nick, room.jid);
|
||||
return { status_code = 200; }
|
||||
@@ -148,6 +147,20 @@ function handle_kick_participant (event)
|
||||
return { status_code = 404; };
|
||||
end
|
||||
|
||||
function is_participant_match(pr, number, participantId)
|
||||
if number then
|
||||
local displayName = pr:get_child_text('nick', 'http://jabber.org/protocol/nick');
|
||||
return is_sip_jigasi(pr) and displayName and starts_with(displayName, number);
|
||||
elseif participantId then
|
||||
local from = pr.attr.from;
|
||||
local _, _, from_resource = jid.split(from);
|
||||
if from_resource then
|
||||
return from_resource == participantId;
|
||||
end
|
||||
end
|
||||
return false;
|
||||
end
|
||||
|
||||
module:log("info","Adding http handler for /kick-participant on %s", module.host);
|
||||
module:depends("http");
|
||||
module:provides("http", {
|
||||
@@ -115,7 +115,8 @@ module:hook("message/bare", function(event)
|
||||
|
||||
if room.polls.by_id[data.pollId] ~= nil then
|
||||
module:log("error", "Poll already exists: %s", data.pollId);
|
||||
return;
|
||||
event.origin.send(st.error_reply(event.stanza, 'cancel', 'not-allowed', 'Poll already exists'));
|
||||
return true;
|
||||
end
|
||||
|
||||
local answers = {}
|
||||
|
||||
@@ -80,7 +80,7 @@ function Util.new(module)
|
||||
These setups relay on configuration 'muc_domain_base' which holds
|
||||
the main domain and we use it to subtract subdomains from the
|
||||
virtual addresses.
|
||||
The following confgurations are for multidomain setups and domain name
|
||||
The following configurations are for multidomain setups and domain name
|
||||
verification:
|
||||
--]]
|
||||
|
||||
@@ -225,7 +225,7 @@ function Util:get_public_key(keyId)
|
||||
self.cache:set(keyId, content);
|
||||
else
|
||||
if code == nil then
|
||||
-- this is timout after nr_retries retries
|
||||
-- this is timeout after nr_retries retries
|
||||
module:log('warn', 'Timeout retrieving %s from %s', keyId, keyurl);
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# The base url that will be used for the test (default will be using "https://alpha.jitsi.net")
|
||||
# If there is a tenant in the URL it must end with a slash (e.g. "https://alpha.jitsi.net/sometenant/")
|
||||
#BASE_URL=
|
||||
|
||||
# To be able to match a domain to a specific address
|
||||
|
||||
2
tests/globals.d.ts
vendored
2
tests/globals.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
import { IContext } from './helpers/types';
|
||||
|
||||
declare global {
|
||||
const context: IContext;
|
||||
const ctx: IContext;
|
||||
}
|
||||
|
||||
@@ -4,14 +4,18 @@ import { multiremotebrowser } from '@wdio/globals';
|
||||
|
||||
import { IConfig } from '../../react/features/base/config/configType';
|
||||
import { urlObjectToString } from '../../react/features/base/util/uri';
|
||||
import BreakoutRooms from '../pageobjects/BreakoutRooms';
|
||||
import ChatPanel from '../pageobjects/ChatPanel';
|
||||
import Filmstrip from '../pageobjects/Filmstrip';
|
||||
import IframeAPI from '../pageobjects/IframeAPI';
|
||||
import Notifications from '../pageobjects/Notifications';
|
||||
import ParticipantsPane from '../pageobjects/ParticipantsPane';
|
||||
import SettingsDialog from '../pageobjects/SettingsDialog';
|
||||
import Toolbar from '../pageobjects/Toolbar';
|
||||
import VideoQualityDialog from '../pageobjects/VideoQualityDialog';
|
||||
|
||||
import { LOG_PREFIX, logInfo } from './browserLogger';
|
||||
import { IContext } from './types';
|
||||
import { IContext, IJoinOptions } from './types';
|
||||
|
||||
/**
|
||||
* Participant.
|
||||
@@ -111,33 +115,40 @@ export class Participant {
|
||||
/**
|
||||
* Joins conference.
|
||||
*
|
||||
* @param {IContext} context - The context.
|
||||
* @param {boolean} skipInMeetingChecks - Whether to skip in meeting checks.
|
||||
* @param {IContext} ctx - The context.
|
||||
* @param {IJoinOptions} options - Options for joining.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async joinConference(context: IContext, skipInMeetingChecks = false): Promise<void> {
|
||||
async joinConference(ctx: IContext, options: IJoinOptions = {}): Promise<void> {
|
||||
const config = {
|
||||
room: context.roomName,
|
||||
configOverwrite: this.config,
|
||||
room: ctx.roomName,
|
||||
configOverwrite: {
|
||||
...this.config,
|
||||
...options.configOverwrite || {}
|
||||
},
|
||||
interfaceConfigOverwrite: {
|
||||
SHOW_CHROME_EXTENSION_BANNER: false
|
||||
},
|
||||
userInfo: {
|
||||
displayName: this._name
|
||||
}
|
||||
};
|
||||
|
||||
if (context.iframeAPI) {
|
||||
if (!options.skipDisplayName) {
|
||||
// @ts-ignore
|
||||
config.userInfo = {
|
||||
displayName: this._name
|
||||
};
|
||||
}
|
||||
|
||||
if (ctx.iframeAPI) {
|
||||
config.room = 'iframeAPITest.html';
|
||||
}
|
||||
|
||||
let url = urlObjectToString(config) || '';
|
||||
|
||||
if (context.iframeAPI) {
|
||||
if (ctx.iframeAPI) {
|
||||
const baseUrl = new URL(this.driver.options.baseUrl || '');
|
||||
|
||||
// @ts-ignore
|
||||
url = `${this.driver.iframePageBase}${url}&domain="${baseUrl.host}"&room="${context.roomName}"`;
|
||||
url = `${this.driver.iframePageBase}${url}&domain="${baseUrl.host}"&room="${ctx.roomName}"`;
|
||||
|
||||
if (baseUrl.pathname.length > 1) {
|
||||
// remove leading slash
|
||||
@@ -150,17 +161,12 @@ export class Participant {
|
||||
|
||||
await this.driver.setTimeout({ 'pageLoad': 30000 });
|
||||
|
||||
// workaround for https://github.com/webdriverio/webdriverio/issues/13956
|
||||
if (url.startsWith('file://')) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
await this.driver.url(url).catch(() => {});
|
||||
} else {
|
||||
await this.driver.url(url.substring(1)); // drop the leading '/' so we can use the tenant if any
|
||||
}
|
||||
// drop the leading '/' so we can use the tenant if any
|
||||
await this.driver.url(url.startsWith('/') ? url.substring(1) : url);
|
||||
|
||||
await this.waitForPageToLoad();
|
||||
|
||||
if (context.iframeAPI) {
|
||||
if (ctx.iframeAPI) {
|
||||
const mainFrame = this.driver.$('iframe');
|
||||
|
||||
await this.driver.switchFrame(mainFrame);
|
||||
@@ -168,7 +174,7 @@ export class Participant {
|
||||
|
||||
await this.waitToJoinMUC();
|
||||
|
||||
await this.postLoadProcess(skipInMeetingChecks);
|
||||
await this.postLoadProcess(options.skipInMeetingChecks);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +184,7 @@ export class Participant {
|
||||
* @returns {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
private async postLoadProcess(skipInMeetingChecks: boolean): Promise<void> {
|
||||
private async postLoadProcess(skipInMeetingChecks = false): Promise<void> {
|
||||
const driver = this.driver;
|
||||
|
||||
const parallel = [];
|
||||
@@ -227,7 +233,7 @@ export class Participant {
|
||||
*/
|
||||
async waitForPageToLoad(): Promise<void> {
|
||||
return this.driver.waitUntil(
|
||||
() => this.driver.execute(() => document.readyState === 'complete'),
|
||||
async () => await this.driver.execute(() => document.readyState === 'complete'),
|
||||
{
|
||||
timeout: 30_000, // 30 seconds
|
||||
timeoutMsg: 'Timeout waiting for Page Load Request to complete.'
|
||||
@@ -238,8 +244,32 @@ export class Participant {
|
||||
/**
|
||||
* Checks if the participant is in the meeting.
|
||||
*/
|
||||
isInMuc() {
|
||||
return this.driver.execute(() => APP.conference.isJoined());
|
||||
async isInMuc() {
|
||||
return await this.driver.execute(() => typeof APP !== 'undefined' && APP.conference?.isJoined());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the participant is a moderator in the meeting.
|
||||
*/
|
||||
async isModerator() {
|
||||
return await this.driver.execute(() => typeof APP !== 'undefined'
|
||||
&& APP.store?.getState()['features/base/participants']?.local?.role === 'moderator');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the meeting supports breakout rooms.
|
||||
*/
|
||||
async isBreakoutRoomsSupported() {
|
||||
return await this.driver.execute(() => typeof APP !== 'undefined'
|
||||
&& APP.store?.getState()['features/base/conference'].conference?.getBreakoutRooms()?.isSupported());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the participant is in breakout room.
|
||||
*/
|
||||
async isInBreakoutRoom() {
|
||||
return await this.driver.execute(() => typeof APP !== 'undefined'
|
||||
&& APP.store?.getState()['features/base/conference'].conference?.getBreakoutRooms()?.isBreakoutRoom());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,7 +296,7 @@ export class Participant {
|
||||
const driver = this.driver;
|
||||
|
||||
return driver.waitUntil(async () =>
|
||||
driver.execute(() => APP.conference.getConnectionState() === 'connected'), {
|
||||
await driver.execute(() => APP.conference.getConnectionState() === 'connected'), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: 'expected ICE to be connected for 15s'
|
||||
});
|
||||
@@ -281,7 +311,7 @@ export class Participant {
|
||||
const driver = this.driver;
|
||||
|
||||
return driver.waitUntil(async () =>
|
||||
driver.execute(() => {
|
||||
await driver.execute(() => {
|
||||
const stats = APP.conference.getStats();
|
||||
const bitrateMap = stats?.bitrate || {};
|
||||
const rtpStats = {
|
||||
@@ -306,12 +336,28 @@ export class Participant {
|
||||
const driver = this.driver;
|
||||
|
||||
return driver.waitUntil(async () =>
|
||||
driver.execute(count => APP.conference.getNumberOfParticipantsWithTracks() >= count, number), {
|
||||
await driver.execute(count => APP.conference.getNumberOfParticipantsWithTracks() >= count, number), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: 'expected remote streams in 15s'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the chat panel for this participant.
|
||||
*/
|
||||
getChatPanel(): ChatPanel {
|
||||
return new ChatPanel(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the BreakoutRooms for this participant.
|
||||
*
|
||||
* @returns {BreakoutRooms}
|
||||
*/
|
||||
getBreakoutRooms(): BreakoutRooms {
|
||||
return new BreakoutRooms(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the toolbar for this participant.
|
||||
*
|
||||
@@ -330,6 +376,13 @@ export class Participant {
|
||||
return new Filmstrip(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the notifications.
|
||||
*/
|
||||
getNotifications(): Notifications {
|
||||
return new Notifications(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the participants pane.
|
||||
*
|
||||
@@ -348,6 +401,15 @@ export class Participant {
|
||||
return new VideoQualityDialog(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the settings Dialog.
|
||||
*
|
||||
* @returns {SettingsDialog}
|
||||
*/
|
||||
getSettingsDialog(): SettingsDialog {
|
||||
return new SettingsDialog(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches to the iframe API context
|
||||
*/
|
||||
@@ -371,6 +433,13 @@ export class Participant {
|
||||
return new IframeAPI(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hangups the participant by leaving the page. base.html is an empty page on all deployments.
|
||||
*/
|
||||
async hangup() {
|
||||
await this.driver.url('/base.html');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local display name.
|
||||
*/
|
||||
@@ -383,4 +452,92 @@ export class Participant {
|
||||
|
||||
return await localDisplayName.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets avatar SRC attribute for the one displayed on local video thumbnail.
|
||||
*/
|
||||
async getLocalVideoAvatar() {
|
||||
const avatar
|
||||
= this.driver.$('//span[@id="localVideoContainer"]//img[contains(@class,"userAvatar")]');
|
||||
|
||||
return await avatar.isExisting() ? await avatar.getAttribute('src') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets avatar SRC attribute for the one displayed on large video.
|
||||
*/
|
||||
async getLargeVideoAvatar() {
|
||||
const avatar = this.driver.$('//img[@id="dominantSpeakerAvatar"]');
|
||||
|
||||
return await avatar.isExisting() ? await avatar.getAttribute('src') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns resource part of the JID of the user who is currently displayed in the large video area.
|
||||
*/
|
||||
async getLargeVideoResource() {
|
||||
return await this.driver.execute(() => APP.UI.getLargeVideoID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the avatar is displayed in the local thumbnail and that the video is not displayed.
|
||||
* There are 3 options for avatar:
|
||||
* - defaultAvatar: true - the default avatar (with grey figure) is used
|
||||
* - image: true - the avatar is an image set in the settings
|
||||
* - defaultAvatar: false, image: false - the avatar is produced from the initials of the display name
|
||||
*/
|
||||
async assertThumbnailShowsAvatar(
|
||||
participant: Participant, reverse = false, defaultAvatar = false, image = false): Promise<void> {
|
||||
const id = participant === this
|
||||
? 'localVideoContainer' : `participant_${await participant.getEndpointId()}`;
|
||||
|
||||
const xpath = defaultAvatar
|
||||
? `//span[@id='${id}']//div[contains(@class,'userAvatar') and contains(@class, 'defaultAvatar')]`
|
||||
: `//span[@id="${id}"]//${image ? 'img' : 'div'}[contains(@class,"userAvatar")]`;
|
||||
|
||||
await this.driver.$(xpath).waitForDisplayed({
|
||||
reverse,
|
||||
timeout: 2000,
|
||||
timeoutMsg: `Avatar is ${reverse ? '' : 'not'} displayed in the local thumbnail for ${participant.name}`
|
||||
});
|
||||
|
||||
await this.driver.$(`//span[@id="${id}"]//video`).waitForDisplayed({
|
||||
reverse: !reverse,
|
||||
timeout: 2000,
|
||||
timeoutMsg: `Video is ${reverse ? 'not' : ''} displayed in the local thumbnail for ${participant.name}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the default avatar is used.
|
||||
*/
|
||||
async assertDefaultAvatarExist(participant: Participant): Promise<void> {
|
||||
const id = participant === this
|
||||
? 'localVideoContainer' : `participant_${await participant.getEndpointId()}`;
|
||||
|
||||
await this.driver.$(
|
||||
`//span[@id='${id}']//div[contains(@class,'userAvatar') and contains(@class, 'defaultAvatar')]`)
|
||||
.waitForExist({
|
||||
timeout: 2000,
|
||||
timeoutMsg: `Default avatar does not exist for ${participant.name}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the local video is displayed in the local thumbnail and that the avatar is not displayed.
|
||||
*/
|
||||
async asserLocalThumbnailShowsVideo(): Promise<void> {
|
||||
await this.assertThumbnailShowsAvatar(this, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure a display name is visible on the stage.
|
||||
* @param value
|
||||
*/
|
||||
async assertDisplayNameVisibleOnStage(value: string) {
|
||||
const displayNameEl = this.driver.$('div[data-testid="stage-display-name"]');
|
||||
|
||||
expect(await displayNameEl.isDisplayed()).toBe(true);
|
||||
expect(await displayNameEl.getText()).toBe(value);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user