mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-27 17:10:20 +00:00
Compare commits
34 Commits
6830
...
track-op-q
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61d483ce1a | ||
|
|
1e2f9160b5 | ||
|
|
4daddd341d | ||
|
|
15cd83387a | ||
|
|
8303e261b2 | ||
|
|
d3c45a5dea | ||
|
|
0d6f00abf3 | ||
|
|
83dfb67f23 | ||
|
|
51bdf67cf2 | ||
|
|
3adbda791c | ||
|
|
924bb0e7ff | ||
|
|
4c9bfe3d4d | ||
|
|
1139311809 | ||
|
|
2ad2e6ff0e | ||
|
|
46cc2e37ae | ||
|
|
0ebac2ac6d | ||
|
|
90e33ee799 | ||
|
|
be982ae996 | ||
|
|
56114fe863 | ||
|
|
a2e8a7f28f | ||
|
|
af072c3070 | ||
|
|
f42772ec5b | ||
|
|
2556a7ab77 | ||
|
|
3cbf160f2b | ||
|
|
744960bb1a | ||
|
|
76471a0ea9 | ||
|
|
0ba033e07d | ||
|
|
cb3fb3ada9 | ||
|
|
48a6472b3b | ||
|
|
691e92b7ec | ||
|
|
6e36340a83 | ||
|
|
ae424c95de | ||
|
|
95b2979eb3 | ||
|
|
a0c130568b |
15
.devcontainer/devcontainer.json
Normal file
15
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "Jitsi Meet Dev Container",
|
||||
"image": "mcr.microsoft.com/devcontainers/universal:2",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"hostRequirements": {
|
||||
"cpus": 4,
|
||||
"memory": "8gb",
|
||||
"storage": "32gb"
|
||||
},
|
||||
"postCreateCommand": "bash -i -c 'nvm use && npm install && cp tsconfig.web.json tsconfig.json'"
|
||||
}
|
||||
988
conference.js
988
conference.js
File diff suppressed because it is too large
Load Diff
@@ -121,13 +121,6 @@ ol.poll-result-list {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.poll-dragged {
|
||||
opacity: 0.5;
|
||||
* {
|
||||
cursor: grabbing !important;
|
||||
}
|
||||
}
|
||||
|
||||
.poll-question {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
|
||||
1
globals.d.ts
vendored
1
globals.d.ts
vendored
@@ -14,6 +14,7 @@ declare global {
|
||||
registerShortcut: Function;
|
||||
unregisterShortcut: Function;
|
||||
openDialog: Function;
|
||||
enable: Function;
|
||||
}
|
||||
};
|
||||
const interfaceConfig: any;
|
||||
|
||||
@@ -46,7 +46,7 @@ public class PiPViewCoordinator {
|
||||
}
|
||||
}
|
||||
|
||||
public let initialPositionInSuperView: Position = .lowerRightCorner
|
||||
public var initialPositionInSuperView: Position = .lowerRightCorner
|
||||
|
||||
// Unused. Remove on the next major release.
|
||||
@available(*, deprecated, message: "The PiP window size is now fixed to 150px.")
|
||||
|
||||
@@ -27,6 +27,25 @@
|
||||
"audioOnly": {
|
||||
"audioOnly": "Alacsony sávszélesség"
|
||||
},
|
||||
"breakoutRooms": {
|
||||
"actions": {
|
||||
"add": "Pihenőszoba hozzáadása",
|
||||
"autoAssign": "Automatikus hozzárendelés a pihenőszobákhoz",
|
||||
"close": "Bezárás",
|
||||
"join": "Csatlakozás",
|
||||
"leaveBreakoutRoom": "Pihenőszoba elhagyása",
|
||||
"more": "Bővebben",
|
||||
"remove": "Eltávolítás",
|
||||
"sendToBreakoutRoom": "Résztvevő áthelyezése ide:"
|
||||
},
|
||||
"defaultName": "Pihenőszoba #{{index}}",
|
||||
"mainRoom": "Fő szoba",
|
||||
"notifications": {
|
||||
"joined": "Csatlakozva a \"{{name}}\" pihenőszobához",
|
||||
"joinedMainRoom": "Csatlakozva a fő szobához",
|
||||
"joinedTitle": "Pihenőszobák"
|
||||
}
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Értekezlet hivatkozásának hozzáadása",
|
||||
"confirmAddLink": "Hozzáadható egy Jitsi hivatkozás az eseményhez?",
|
||||
@@ -46,6 +65,7 @@
|
||||
"today": "Ma"
|
||||
},
|
||||
"chat": {
|
||||
"enter": "Belépés a szobába",
|
||||
"error": "Hiba: az üzenetet nem sikerült elküldeni. Hiba oka: {{error}}",
|
||||
"fieldPlaceHolder": "Írja ide az üzenetét",
|
||||
"messageTo": "Privát üzenet a felhasználónak: {{recipient}}",
|
||||
@@ -58,14 +78,21 @@
|
||||
"noMessagesMessage": "A találkozón még nincsenek üzenetek. Itt kezdhet beszélgetést!",
|
||||
"privateNotice": "Privát üzenet a felhasználónak: {{recipient}}",
|
||||
"sendButton": "Küldés",
|
||||
"tabs": {
|
||||
"chat": "Csevegés",
|
||||
"polls": "Szavazás"
|
||||
},
|
||||
"title": "Csevegés",
|
||||
"titleWithPolls": "Csevegés",
|
||||
"you": "neked"
|
||||
"titleWithPolls": "Csevegés és szavazás",
|
||||
"you": "te"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"buttonText": "Chrome kiterjesztés telepítése",
|
||||
"dontShowAgain": "Ne jelenjen meg újra",
|
||||
"installExtensionText": "Kiterjesztés telepítése a Google Calendar és az Office 365 integrációjához"
|
||||
"installExtensionText": "Kiterjesztés telepítése a Google Calendar és az Office 365 integrációjához",
|
||||
"raiseHandAction": "Kéz felemelése",
|
||||
"reactionSounds": "Hangok kikapcsolása",
|
||||
"reactionSoundsForAll": "Hangok kikapcsolása mindenkinek"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "Kapcsolódás az értekezlethez…"
|
||||
@@ -189,12 +216,18 @@
|
||||
"dismiss": "Elutasítás",
|
||||
"displayNameRequired": "Helló! Mi a neve?",
|
||||
"done": "Kész",
|
||||
"enterDisplayName": "Adja meg itt a nevét",
|
||||
"e2eeDescription": "A végpontok közötti titkosítás jelenleg KÍSÉRLETES. Ne feledje, hogy a végpontok közötti titkosítás bekapcsolása hatékonyan letiltja a szerveroldali szolgáltatásokat, például: telefonos részvételt. Ne feledje azt is, hogy az értekezlet csak olyan felhasználók számára működik, akik olyan böngészőkből csatlakoznak, amelyek támogatják a beilleszthető adatfolyamokat.",
|
||||
"e2eeLabel": "Végpontok közötti titkosítás engedélyezése",
|
||||
"embedMeeting": "Meeting beágyazása",
|
||||
"enterDisplayName": "Adja meg a nevét",
|
||||
"error": "Hiba",
|
||||
"externalInstallationMsg": "Telepíteni kell a munkaasztal megosztására való kiterjesztést.",
|
||||
"externalInstallationTitle": "Kiterjesztésre van szükség",
|
||||
"goToStore": "Ugrás az alkalmazásbolthoz",
|
||||
"gracefulShutdown": "Jelenleg a szolgáltatás karbantartás miatt nem elérhető. Később próbálja meg ismét.",
|
||||
"grantModeratorDialog": "Biztos, hogy moderátori jogokat kíván adni a következőnek: {{participantName}}?",
|
||||
"grantModeratorTitle": "Moderátori jogok megadása",
|
||||
"hideShareAudioHelper": "Ne mutassa ezt az ablakot többé",
|
||||
"incorrectPassword": "Helytelen felhasználói név és jelszó",
|
||||
"incorrectRoomLockPassword": "Helytelen jelszó",
|
||||
"inlineInstallExtension": "Telepítés azonnal",
|
||||
@@ -217,21 +250,34 @@
|
||||
"maxUsersLimitReached": "A lehetséges résztvevők maximális száma elérve. A konferencia tele van. Lépjen kapcsolatba az értekezlet tulajdonosával vagy próbálkozzon később!",
|
||||
"maxUsersLimitReachedTitle": "A lehetséges résztvevők maximális száma elérve",
|
||||
"micConstraintFailedError": "A mikrofon nem felel meg bizonyos kikötéseknek.",
|
||||
"micNotFoundError": "Nem található mikrofon.",
|
||||
"micNotFoundError": "A mikrofon nem található.",
|
||||
"micNotSendingData": "A számítógép beállításai között kell visszahangosítani a mikrofont vagy beállítani a hangfelvétel szintjét",
|
||||
"micNotSendingDataTitle": "A mikrofon le van némítva a rendszerbeállításokban",
|
||||
"micPermissionDeniedError": "Nem adott engedélyt a mikrofon használatához. Csatlakozhat a beszélgetéshez, de a többiek nem fogják Önt hallani. A címsorban lévő kamera ikonnal lehet ezt helyrehozni.",
|
||||
"micUnknownError": "Ismeretlen ok miatt nem lehet a mikrofont használni.",
|
||||
"moderationAudioLabel": "Engedélyezze a résztvevőknek saját némításuk feloldását",
|
||||
"moderationVideoLabel": "Engedélyezze a résztvevőknek saját kamerájuk elindítását",
|
||||
"muteEveryoneDialog": "Valóban mindenki elnémítható? Nem fogja tudni visszahangosítani, de ő önmagát bármikor vissza tudja majd hangosítani.",
|
||||
"muteEveryoneElseDialog": "Némítás után már nem fogja tudni visszahangosítani, de ő önmagát bármikor vissza tudja hangosítani.",
|
||||
"muteEveryoneElseTitle": "Mindenki elnémítása, kivéve: {{whom}}?",
|
||||
"muteEveryoneElsesVideoDialog": "A kamera letiltása után nem tudja újra bekapcsolni, de ők bármikor újra bekapcsolhatják.",
|
||||
"muteEveryoneElsesVideoTitle": "Mindenki kamerájának tilátsa, kivéve {{whom}}?",
|
||||
"muteEveryoneSelf": "önmagamat",
|
||||
"muteEveryoneStartMuted": "Mindenki elnémítva kezd ezután",
|
||||
"muteEveryoneTitle": "Mindenki elnémítása?",
|
||||
"muteEveryonesVideoDialog": "A résztvevők bármikor be tudják kapcsolni a kamerájukat.",
|
||||
"muteEveryonesVideoDialogModerationOn": "A résztvevők bármikor kérhetik videójuk bekapcsolását.",
|
||||
"muteEveryonesVideoTitle": "Minden résztvevő kameráját leállítja?",
|
||||
"muteParticipantBody": "Nem fogja tudni visszahangosítani, de ő önmagát bármikor vissza tudja majd hangosítani.",
|
||||
"muteParticipantButton": "Némítás",
|
||||
"muteParticipantDialog": "Valóban elnémítható ez a résztvevő? Nem fogja tudni visszahangosítani, de ő önmagát bármikor vissza tudja majd hangosítani.",
|
||||
"muteParticipantTitle": "Elnémítható a résztvevő?",
|
||||
"muteParticipantsVideoBody": "Ön nem tudja újra bekapcsolni a kamerát, de ők bármikor újra bekapcsolhatják.",
|
||||
"muteParticipantsVideoButton": "Kamera leállítása",
|
||||
"muteParticipantsVideoDialog": "Biztosan le akarja tiltani ennek a résztvevőnek a kameráját? Ön nem tudja újra bekapcsolni a kamerát, de ők bármikor újra bekapcsolhatják.",
|
||||
"muteParticipantsVideoDialogModerationOn": "Are you sure you want to turn off this participant's camera? You won't be able to turn the camera back on and neither will they.",
|
||||
"muteParticipantsVideoTitle": "Letiltja ennek a résztvevőnek a kameráját?",
|
||||
"password": "Jelszó",
|
||||
"passwordLabel": "Az értekezletet zárolta egy résztvevő. Csatlakozáshoz adja meg a $t(lockRoomPassword).",
|
||||
"passwordNotSupported": "Az értekezlet $t(lockRoomPassword) beállítása nem támogatott.",
|
||||
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) nem támogatott",
|
||||
@@ -249,6 +295,7 @@
|
||||
"remoteControlShareScreenWarning": "Vegye figyelembe, hogy ha megnyomja az „Engedélyezés” lehetőséget, akkor megosztja a képernyőt!",
|
||||
"remoteControlStopMessage": "A távoli munkamenet irányítása befejeződött!",
|
||||
"remoteControlTitle": "Távoli asztal vezérlése",
|
||||
"remoteUserControls": "{{username}} vezérlői",
|
||||
"removePassword": "$t(lockRoomPassword) eltávolítása",
|
||||
"removeSharedVideoMsg": "Valóban eltávolítható a megosztott videó?",
|
||||
"removeSharedVideoTitle": "Megosztott videó eltávolítása",
|
||||
@@ -267,11 +314,14 @@
|
||||
"sendPrivateMessageTitle": "Privátban legyen elküldve?",
|
||||
"serviceUnavailable": "Szolgáltatás nem elérhető",
|
||||
"sessTerminated": "Hívás megszakadt",
|
||||
"shareAudio": "Tovább",
|
||||
"shareAudioTitle": "Hang megosztása",
|
||||
"shareVideoLinkError": "Adjon meg egy helyes linket.",
|
||||
"shareVideoTitle": "Videó megosztása",
|
||||
"shareYourScreen": "Képernyő megosztása",
|
||||
"shareYourScreenDisabled": "Képernyőmegosztás letiltva.",
|
||||
"shareYourScreenDisabledForGuest": "Vendég nem végezhet képernyőmegosztást.",
|
||||
"sharedVideoLinkPlaceholder": "YouTube link vagy közvetlen videó link",
|
||||
"startLiveStreaming": "Élő közvetítés kezdése",
|
||||
"startRecording": "Felvétel indítása",
|
||||
"startRemoteControlErrorMessage": "Hiba történt a távoli vezérlés munkamenetének indítása közben!",
|
||||
@@ -287,6 +337,7 @@
|
||||
"transcribing": "Átirat készítése",
|
||||
"unlockRoom": "Értekezlet $t(lockRoomPassword) eltávolítása",
|
||||
"userPassword": "felhasználói jelszó",
|
||||
"videoLink": "Videó link",
|
||||
"yourEntireScreen": "A teljes képernyő"
|
||||
},
|
||||
"documentSharing": {
|
||||
@@ -367,11 +418,16 @@
|
||||
"showSpeakerStats": "Beszéd statisztikák megjelenítése",
|
||||
"toggleChat": "Csevegés megnyitása vagy bezárása",
|
||||
"toggleFilmstrip": "Videó bélyegképek megjelenítése vagy elrejtése",
|
||||
"toggleParticipantsPane": "A résztvevők panel megjelenítése vagy elrejtése",
|
||||
"toggleScreensharing": "Váltás kamera és képernyőmegosztás között",
|
||||
"toggleShortcuts": "Gyorsbillentyűk megjelenítése vagy elrejtése",
|
||||
"videoMute": "Kamera elindítása vagy leállítása",
|
||||
"videoQuality": "Hívás minőségének kezelése"
|
||||
},
|
||||
"largeVideo": {
|
||||
"screenIsShared": "Ön megosztja a képernyőjét",
|
||||
"showMeWhatImSharing": "Látni szeretném mit osztok meg"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "Dolgozunk a közvetítési erőforrások felszabadításán. Kísérelje meg újra néhány perc múlva.",
|
||||
"busyTitle": "Jelenleg minden közvetítő foglalt",
|
||||
@@ -405,6 +461,44 @@
|
||||
"unavailableTitle": "Élő közvetítés elérhetetlen",
|
||||
"youtubeTerms": "YouTube szolgáltatási feltételek"
|
||||
},
|
||||
"lobby": {
|
||||
"admit": "Engedélyezés",
|
||||
"admitAll": "Mindet engedélyez",
|
||||
"allow": "Engedélyez",
|
||||
"backToKnockModeButton": "Csatlakozási kérelem küldése",
|
||||
"chat": "Chat",
|
||||
"dialogTitle": "Lobby mód",
|
||||
"disableDialogContent": "A lobby mód jelenleg elérhető. Lehetőséged van csak azokat behívni a megbeszélésre, akik erre jogosultak általad. Szeretnéd kikapcsolni?",
|
||||
"disableDialogSubmit": "Elutasítás",
|
||||
"emailField": "Írd be az email címed",
|
||||
"enableDialogPasswordField": "Jelszó megadása (választható)",
|
||||
"enableDialogSubmit": "Elfogadás",
|
||||
"enableDialogText": "A Lobby mód lehetővé teszi a megbeszélés védelmét azáltal, hogy csak a moderátor hivatalos jóváhagyása után engedi be az embereket.",
|
||||
"enterPasswordButton": "Adja meg az értekezlet jelszavát",
|
||||
"enterPasswordTitle": "Adja meg a jelszót az értekezlethez való csatlakozáshoz",
|
||||
"errorMissingPassword": "Kérjük, adja meg az értekezlet jelszavát",
|
||||
"invalidPassword": "Helytelen jelszó",
|
||||
"joinRejectedMessage": "Csatlakozási kérelmét egy moderátor elutasította.",
|
||||
"joinRejectedTitle": "Csatlakozási kérelem elutasítva.",
|
||||
"joinTitle": "Csatlakozás az értekezlethez",
|
||||
"joinWithPasswordMessage": "Csatlakozás jelszóval, kérjük várjon...",
|
||||
"joiningMessage": "Amint valaki elfogadja kérését, csatlakoztatjuk az értekezlethez",
|
||||
"joiningTitle": "Értekezlethez csatlakozás kérése...",
|
||||
"joiningWithPasswordTitle": "Csatlakozás jelszóval...",
|
||||
"knockButton": "Csatlakozási kérelem küldése",
|
||||
"knockTitle": "Valaki szeretne csatlakozni az értekezlethez",
|
||||
"nameField": "Adja meg a nevét",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} elutasításra került a csatlakozásod {{originParticipantName}} által",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} a csatlakozozásod elfogadva lett {{originParticipantName}} által",
|
||||
"notificationLobbyDisabled": "Lobby tiltva lett {{originParticipantName}} által.",
|
||||
"notificationLobbyEnabled": "Lobby engedélyezve lett {{originParticipantName}} által.",
|
||||
"notificationTitle": "Lobby",
|
||||
"passwordField": "Adja meg az értekezlet jelszavát",
|
||||
"passwordJoinButton": "Csatlakozás",
|
||||
"reject": "Elutasít",
|
||||
"rejectAll": "Mindet elutasít",
|
||||
"toggleLabel": "Lobby engedélyezése"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
"off": "Kikapcsolva",
|
||||
@@ -449,11 +543,18 @@
|
||||
"focus": "Konferencia fókusza",
|
||||
"focusFail": "{{component}} nem elérhető – újrapróbálkozás {{ms}} másodperc múlva",
|
||||
"grantedTo": "Moderátori jogok biztosítva {{to}} számára!",
|
||||
"hostAskedUnmute": "Kérlek hangosítsd vissza a mikrofonod.",
|
||||
"invitedOneMember": "{{name}} meg lett hívva",
|
||||
"invitedThreePlusMembers": "{{name}} és {{count}} másik felhasználó meg lett hívva",
|
||||
"invitedTwoMembers": "{{first}} és {{second}} lett meghívva",
|
||||
"kickParticipant": "{{kicked}} résztvevőt kirúgta {{kicker}}",
|
||||
"leftOneMember": "{{name}} elhagyta az értekezletet",
|
||||
"leftThreePlusMembers": "{{name}} és mások elhagyták az értekezletet",
|
||||
"leftTwoMembers": "{{first}} és {{second}} elhagyták az értekezletet",
|
||||
"localRecordingStarted": "{{name}} elkezdte rögzíteni az értekezletet.",
|
||||
"localRecordingStopped": "{{name}} leállította a rögzítést.",
|
||||
"me": "Én",
|
||||
"moderationInEffectTitle": "A moderátor elnémította a mikrofonját",
|
||||
"moderator": "Moderátori jogok biztosítva!",
|
||||
"muted": "A beszélgetést elnémítva kezdte meg.",
|
||||
"mutedRemotelyDescription": "Bármikor visszahangosíthatja magát, ha készen áll a beszédre. Némítsa le magát ismét, ha a felesleges zajoktól meg kívánja védeni az értekezletet.",
|
||||
@@ -462,6 +563,10 @@
|
||||
"newDeviceAction": "Alkalmaz",
|
||||
"newDeviceAudioTitle": "Új hangeszköz észlelve",
|
||||
"newDeviceCameraTitle": "Új kamera észlelve",
|
||||
"noiseSuppressionFailedTitle": "Nem sikerült elindítani a zajcsökkentést",
|
||||
"noiseSuppressionNoTrackDescription": "Kérjük, először kapcsolja ki a mikrofon némítását.",
|
||||
"noiseSuppressionStereoDescription": "A sztereó zajcsökkentés jelenleg nem támogatott.",
|
||||
"participantWantsToJoin": "Csatlakozni szeretne az értekezlethez",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) egy másik résztvevő által eltávolítva",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) egy másik résztvevő által beállítva",
|
||||
"raisedHand": "{{name}} beszélni szeretne.",
|
||||
@@ -472,9 +577,115 @@
|
||||
"suboptimalExperienceTitle": "Böngészőhiba",
|
||||
"unmute": "Visszahangosítás"
|
||||
},
|
||||
"participantsPane": {
|
||||
"actions": {
|
||||
"allow": "Engedélyezés a résztvevőknek, hogy:",
|
||||
"allowVideo": "Videó engedélyezése",
|
||||
"askUnmute": "Kérje a némítás feloldását",
|
||||
"audioModeration": "A némítást feloldhassák",
|
||||
"blockEveryoneMicCamera": "Block everyone's mic and camera",
|
||||
"invite": "Meghívás",
|
||||
"moreModerationActions": "További moderálási opciók",
|
||||
"moreModerationControls": "További moderálási vezérlők",
|
||||
"moreParticipantOptions": "Résztvevő további beállításai",
|
||||
"mute": "Némítás",
|
||||
"muteAll": "Mindenkit elnémít",
|
||||
"muteEveryoneElse": "Mute everyone else",
|
||||
"stopEveryonesVideo": "Mindenki videójának leállítása",
|
||||
"stopVideo": "Videó leállítása",
|
||||
"unblockEveryoneMicCamera": "Unblock everyone's mic and camera",
|
||||
"videoModeration": "Elindíthassák a videójukat"
|
||||
},
|
||||
"close": "Bezár",
|
||||
"header": "Résztvevők",
|
||||
"headings": {
|
||||
"lobby": "Lobby ({{count}})",
|
||||
"participantsList": "Résztvevők ({{count}})",
|
||||
"waitingLobby": "Lobby-ban várakozók ({{count}})"
|
||||
},
|
||||
"search": "Résztvevők keresése"
|
||||
},
|
||||
"passwordDigitsOnly": "Legfeljebb {{number}} szám",
|
||||
"passwordSetRemotely": "egy másik résztvevő által beállítva",
|
||||
"polls": {
|
||||
"answer": {
|
||||
"skip": "Kihagyás",
|
||||
"submit": "Küldés"
|
||||
},
|
||||
"by": "Létrehozta: {{ name }}",
|
||||
"create": {
|
||||
"addOption": "Opció hozzáadása",
|
||||
"answerPlaceholder": "Opció {{index}}",
|
||||
"cancel": "Mégsem",
|
||||
"create": "Szavazás létrehozása",
|
||||
"pollOption": "Opció {{index}}",
|
||||
"pollQuestion": "Szavazás kérdése",
|
||||
"questionPlaceholder": "Írja le a kérdést",
|
||||
"removeOption": "Opció eltávolítása",
|
||||
"send": "Küldés"
|
||||
},
|
||||
"notification": {
|
||||
"description": "Szavazás megnyitása",
|
||||
"title": "Új szavazás létrehozva"
|
||||
},
|
||||
"results": {
|
||||
"changeVote": "Módosítás",
|
||||
"empty": "Még nincsenek szavazások. Indítson szavazást itt!",
|
||||
"hideDetailedResults": "Részletek elrejtése",
|
||||
"showDetailedResults": "Részletek",
|
||||
"vote": "Szavazás"
|
||||
}
|
||||
},
|
||||
"poweredby": "Működteti a",
|
||||
"prejoin": {
|
||||
"audioAndVideoError": "Hang és videó hiba:",
|
||||
"audioDeviceProblem": "Hiba lépett fel az hangeszközzel",
|
||||
"audioOnlyError": "Hang hiba:",
|
||||
"audioTrackError": "Nem lehet a hangot rögzíteni.",
|
||||
"callMe": "Hívj fel",
|
||||
"callMeAtNumber": "Hívj fel ezen a számon:",
|
||||
"calling": "Hívás",
|
||||
"configuringDevices": "Eszköz beállítás...",
|
||||
"connectedWithAudioQ": "Csak hanggal szeretne csatlakozni?",
|
||||
"connection": {
|
||||
"good": "Az internet kapcsolat jónak tűnik!",
|
||||
"nonOptimal": "Az internet kapcsolat nem optimális",
|
||||
"poor": "Az internet kapcsolat nagyon gyenge!"
|
||||
},
|
||||
"connectionDetails": {
|
||||
"goodQuality": "Fantasztikus! A média minősége kiváló lesz."
|
||||
},
|
||||
"copyAndShare": "Másolom és megosztom az értekezlet linkjét",
|
||||
"dialInMeeting": "Behívás az értekezletbe",
|
||||
"dialInPin": "Behívás az értkezeletbe és megadom a PIN kódot:",
|
||||
"dialing": "Tárcsázás",
|
||||
"doNotShow": "Ne mutassa mégegyszer ezt a képernyőt",
|
||||
"errorDialOut": "Nem sikerült a behívás",
|
||||
"errorDialOutDisconnected": "Nem sikerült a behívás. Kapcsolat bontása",
|
||||
"errorDialOutFailed": "Nem sikerült a behívás. Hiba a hívásban",
|
||||
"errorDialOutStatus": "Hiba a hívás státusz megadásában",
|
||||
"errorMissingName": "Kérlet add meg a neved, hogy csatlakozhass a megbeszéléshez",
|
||||
"errorNoPermissions": "Engedélyeznie kell a mikrofonhoz és a kamerához való hozzáférést",
|
||||
"errorStatusCode": "Hiba a hiváskor, hiba kód: {{status}}",
|
||||
"errorValidation": "Hívószám validációs hiba",
|
||||
"iWantToDialIn": "Hívni szeretném",
|
||||
"initiated": "Hívás felépítés",
|
||||
"joinAudioByPhone": "Csatlakozás telefon beszélgetéssel",
|
||||
"joinMeeting": "Csatlakozás",
|
||||
"joinMeetingInLowBandwidthMode": "Csatlakozás alacsony sávszélességi módban",
|
||||
"joinWithoutAudio": "Csatlakozás hang nélkül",
|
||||
"keyboardShortcuts": "Gyorsbillentyűk engedélyezése",
|
||||
"linkCopied": "A link a vágólapra másolva",
|
||||
"lookGood": "A mikrofon megfelelően működik",
|
||||
"or": "vagy",
|
||||
"premeeting": "Csatlakozás előtt",
|
||||
"screenSharingError": "Képernyő megosztás hiba:",
|
||||
"showScreen": "Csatlakozás előtti kamerakép",
|
||||
"startWithPhone": "Kezdés telefonhanggal",
|
||||
"videoOnlyError": "Videó hiba:",
|
||||
"videoTrackError": "Nem sikerült a videó megjelenítés.",
|
||||
"viewAllNumbers": "Összes szám megjelenítése"
|
||||
},
|
||||
"presenceStatus": {
|
||||
"busy": "Foglalt",
|
||||
"calling": "Hívás…",
|
||||
@@ -509,13 +720,19 @@
|
||||
"failedToStart": "A felvétel indítása meghiúsult",
|
||||
"fileSharingdescription": "Felvétel megosztása az értekezlet résztvevőivel",
|
||||
"live": "ÉLŐ",
|
||||
"localRecordingNoNotificationWarning": "A felvételt nem közöljük más résztvevőkkel. Értesítenie kell velük, hogy a találkozót rögzítették.",
|
||||
"localRecordingStartWarning": "A megbeszélésből való kilépés előtt feltétlenül állítsa le a felvételt, hogy elmentse azt.",
|
||||
"localRecordingStartWarningTitle": "Állítsa le a felvételt a mentéshez",
|
||||
"localRecordingWarning": "Győződjön meg arról, hogy az aktuális lapot választotta a megfelelő videó és hang használatához. A felvétel jelenleg 1 GB-ra van korlátozva, ami körülbelül 100 perc.",
|
||||
"loggedIn": "Belépve mint {{userName}}",
|
||||
"off": "Felvétel leállítva",
|
||||
"offBy": "{{name}} leállította a felvételt",
|
||||
"on": "Felvétel",
|
||||
"onBy": "{{name}} elindította a felvételt",
|
||||
"onlyRecordSelf": "Csak az én hang- és videófolyamomat rögzítse",
|
||||
"pending": "Értekezlet rögzítésének előkészítése…",
|
||||
"rec": "REC",
|
||||
"saveLocalRecording": "Felvétel mentése helyileg (béta)",
|
||||
"serviceDescription": "A felvételt a rögzítési szolgáltatás veszi fel",
|
||||
"serviceName": "Felvétel szolgáltatás",
|
||||
"signIn": "Belépés",
|
||||
@@ -527,6 +744,12 @@
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Húzás a frissítéshez"
|
||||
},
|
||||
"security": {
|
||||
"about": "Hozzáadhat jelszót az értekezlethez. A résztvevőknek meg kell adniuk a jelszót, mielőtt csatlakozhatnak az értekezlethez.",
|
||||
"aboutReadOnly": "A moderátor résztvevői hozzáadhatnak egy jelszót az értekezlethez. A résztvevőknek meg kell adniuk a jelszót, mielőtt csatlakozhatnak az értekezlethez.",
|
||||
"header": "Biztonsági beállítások",
|
||||
"insecureRoomNameWarning": "The room name is unsafe. Unwanted participants may join your conference. Consider securing your meeting using the security button."
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "A {{appName}} naptárintegráció a naptár biztonságos elérésére szolgál, így olvasni tudja a soron következő eseményeket.",
|
||||
@@ -535,8 +758,13 @@
|
||||
"signedIn": "Jelenleg ehhez az címhez tartozó naptár eseményei érhetőek el: {{email}}. Alább a „szétkapcsolás” gombra kattintva lehet leállítani a naptár eseményeinek elérését.",
|
||||
"title": "Naptár"
|
||||
},
|
||||
"desktopShareFramerate": "Képernyőmegosztás sebessége (FPS)",
|
||||
"desktopShareHighFpsWarning": "A nagyobb képkockasebesség az asztali megosztásnál hatással lehet a sávszélességre. Az új beállítások érvénybe léptetéséhez újra kell indítania a képernyőmegosztást.",
|
||||
"desktopShareWarning": "Az új beállítások érvénybe léptetéséhez újra kell indítania a képernyőmegosztást.",
|
||||
"devices": "Eszközök",
|
||||
"followMe": "Mindenki engem kövessen",
|
||||
"framesPerSecond": "képkocka / másodperc",
|
||||
"incomingMessage": "Bejövő üzenet",
|
||||
"language": "Nyelv",
|
||||
"loggedIn": "Belépve mint {{name}}",
|
||||
"microphones": "Mikrofonok",
|
||||
@@ -544,12 +772,22 @@
|
||||
"more": "Továbbiak",
|
||||
"name": "Név",
|
||||
"noDevice": "Nincs",
|
||||
"participantJoined": "Résztvevő csatlakozott",
|
||||
"participantKnocking": "Résztvevő belépett a lobby-ba",
|
||||
"participantLeft": "Résztvevő kilépett",
|
||||
"playSounds": "Hangok lejátszása a következőkhöz:",
|
||||
"reactions": "Meeting reakciók",
|
||||
"sameAsSystem": "Rendszerhang ({{label}})",
|
||||
"selectAudioOutput": "Hangkimenet",
|
||||
"selectCamera": "Kamera",
|
||||
"selectMic": "Mikrofon",
|
||||
"selfView": "Saját kép",
|
||||
"sounds": "Hangok",
|
||||
"speakers": "Hangszórók",
|
||||
"startAudioMuted": "Mindenki elnémítva kezd",
|
||||
"startReactionsMuted": "Reakció hangok némítása mindenki számára",
|
||||
"startVideoMuted": "Mindenki videó nélkül kezd",
|
||||
"talkWhileMuted": "Lenémított beszéd",
|
||||
"title": "Beállítások"
|
||||
},
|
||||
"settingsView": {
|
||||
@@ -577,14 +815,23 @@
|
||||
},
|
||||
"speaker": "Hangszóró",
|
||||
"speakerStats": {
|
||||
"angry": "Mérges",
|
||||
"disgusted": "Felháborodott",
|
||||
"fearful": "Félelmetes",
|
||||
"happy": "Boldog",
|
||||
"hours": "{{count}} h",
|
||||
"minutes": "{{count}} perc",
|
||||
"name": "Név",
|
||||
"neutral": "Semleges",
|
||||
"sad": "Szomorú",
|
||||
"search": "Keresés",
|
||||
"seconds": "{{count}} mp",
|
||||
"speakerStats": "Beszélő statisztika",
|
||||
"speakerTime": "Beszélő ideje"
|
||||
"speakerTime": "Beszélő ideje",
|
||||
"surprised": "Meglepett"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"genericTitle": "Az értekezlethez engedélyezni kell a mikrofont és kamerát.",
|
||||
"policyText": " ",
|
||||
"title": "A {{app}} használni szeretné a mikrofont és a kamerát."
|
||||
},
|
||||
@@ -599,36 +846,49 @@
|
||||
"Settings": "Beállítások átváltása",
|
||||
"audioOnly": "Csak a hang átváltása",
|
||||
"audioRoute": "Hangeszköz kijelölése",
|
||||
"boo": "Szomorú",
|
||||
"callQuality": "Videóminőség kezelése",
|
||||
"cc": "Feliratok átváltása",
|
||||
"chat": "Csevegés ablak átváltása",
|
||||
"clap": "Taps",
|
||||
"document": "Megosztott dokumentum átváltása",
|
||||
"download": "Alkalmazás letöltése",
|
||||
"embedMeeting": "Meeting beágyazása",
|
||||
"feedback": "Visszajelzés küldése",
|
||||
"fullScreen": "Teljes képernyő átváltása",
|
||||
"grantModerator": "Moderátori jogok megadása",
|
||||
"hangup": "Beszélgetés elhagyása",
|
||||
"help": "Súgó",
|
||||
"invite": "Személyek meghívása",
|
||||
"kick": "Résztvevő kirúgása",
|
||||
"laugh": "Nevetés",
|
||||
"like": "Hüvelykujj fel",
|
||||
"localRecording": "Helyi felvétel vezérlőelemeinek átváltása",
|
||||
"lockRoom": "Értekezlet jelszavának átváltása",
|
||||
"moreActions": "További műveltek menü átváltása",
|
||||
"moreActionsMenu": "További műveltek menü",
|
||||
"moreActions": "További műveletek menü átváltása",
|
||||
"moreActionsMenu": "További műveletek menü",
|
||||
"moreOptions": "További beállítások megjelenítése",
|
||||
"mute": "Hang némításának átváltása",
|
||||
"muteEveryone": "Mindenki elnémítása",
|
||||
"muteEveryonesVideoStream": "Mindenki videójának leállítása",
|
||||
"noiseSuppression": "Zajcsökkentés",
|
||||
"participants": "Résztvevők",
|
||||
"pip": "Kép és képben mód átváltása",
|
||||
"privateMessage": "Privát üzenet küldése",
|
||||
"profile": "Adja meg a profilját",
|
||||
"raiseHand": "Kéz felemelésének átváltása",
|
||||
"recording": "Felvétel átváltása",
|
||||
"remoteMute": "Résztvevők némítása",
|
||||
"security": "Biztonsági Beállítások",
|
||||
"shareRoom": "Valaki meghívása",
|
||||
"shareYourScreen": "Képernyőmegosztás átváltása",
|
||||
"shareaudio": "Hang megosztása",
|
||||
"sharedvideo": "Videó megosztásának átváltása",
|
||||
"shortcuts": "Gyorsbillentyűk átváltása",
|
||||
"show": "Megjelenítés a színpadon",
|
||||
"silence": "Néma",
|
||||
"speakerStats": "Beszélő statisztika átváltása",
|
||||
"surprised": "Meglepett",
|
||||
"tileView": "Mozaikos nézet átváltása",
|
||||
"toggleCamera": "Kamera átváltása",
|
||||
"toggleFilmstrip": "Filmszalag átváltása",
|
||||
@@ -639,13 +899,18 @@
|
||||
"audioOnlyOff": "Alacsony sávszélességű mód letiltása",
|
||||
"audioOnlyOn": "Alacsony sávszélességű mód engedélyezése",
|
||||
"audioRoute": "Hangeszköz kijelölése",
|
||||
"audioSettings": "Hangbeállítások",
|
||||
"authenticate": "Hitelesítés",
|
||||
"boo": "Szomorú",
|
||||
"callQuality": "Videominőség kezelése",
|
||||
"chat": "Csevegés megnyitása / bezárása",
|
||||
"clap": "Taps",
|
||||
"closeChat": "Csevegés bezárása",
|
||||
"disableReactionSounds": "Kikapcsolhatja a reakcióhangokat a Meeting-en",
|
||||
"documentClose": "Megosztott dokumentum bezárása",
|
||||
"documentOpen": "Megosztott dokumentum megnyitása",
|
||||
"download": "Alkalmazás letöltése",
|
||||
"embedMeeting": "Meeting beágyazása",
|
||||
"enterFullScreen": "Teljes képernyős megtekintés",
|
||||
"enterTileView": "Mozaikos nézet indítása",
|
||||
"exitFullScreen": "Kilépés a teljes képernyőből",
|
||||
@@ -654,6 +919,10 @@
|
||||
"hangup": "Kilépés",
|
||||
"help": "Súgó",
|
||||
"invite": "Személyek meghívása",
|
||||
"joinBreakoutRoom": "Csatlakozás a pihenőszobához",
|
||||
"laugh": "Nevetés",
|
||||
"leaveBreakoutRoom": "Pihenőszoba elhagyása",
|
||||
"like": "Hüvelykujj fel",
|
||||
"login": "Bejelentkezés",
|
||||
"logout": "Kijelentkezés",
|
||||
"lowerYourHand": "Kéz leengedése",
|
||||
@@ -666,28 +935,42 @@
|
||||
"noAudioSignalDialInDesc": "Be is tárcsázhat:",
|
||||
"noAudioSignalDialInLinkDesc": "Betárcsázási számok",
|
||||
"noAudioSignalTitle": "Nincs bemenet a mikrofonjáról!",
|
||||
"noiseSuppression": "Zajcsökkentés",
|
||||
"noisyAudioInputDesc": "Úgy tűnik, hogy ez a mikrofon zajos. Le kellene némítani vagy cserélni az eszközt.",
|
||||
"noisyAudioInputTitle": "Zajosnak tűnik a mikrofonja!",
|
||||
"openChat": "Csevegés megnyitása",
|
||||
"participants": "Résztvevők",
|
||||
"pip": "Belépés kép a képben módba",
|
||||
"privateMessage": "Privát üzenet küldése",
|
||||
"profile": "Adja meg a profilját",
|
||||
"raiseHand": "Kéz felemelése / leengedése",
|
||||
"raiseYourHand": "Kéz felemelése",
|
||||
"reactionBoo": "Boo reakció küldése",
|
||||
"reactionClap": "Tapsolás reakció küldése",
|
||||
"reactionLaugh": "Nevetés reakció küldése",
|
||||
"reactionLike": "Hüvelykujj fel reakció küldése",
|
||||
"reactionSilence": "Néma arc reakció küldése",
|
||||
"reactionSurprised": "Meglepett reakció küldése",
|
||||
"security": "Biztonsági Beállítások",
|
||||
"selectBackground": "Háttér beállítása",
|
||||
"shareRoom": "Valaki meghívása",
|
||||
"shareaudio": "Hang megosztása",
|
||||
"sharedvideo": "Videó megosztása",
|
||||
"shortcuts": "Gyorsbillentyűk megtekintése",
|
||||
"silence": "Néma",
|
||||
"speakerStats": "Beszélő statisztika",
|
||||
"startScreenSharing": "Képernyőmegosztás kezdése",
|
||||
"startSubtitles": "Feliratok kezdése",
|
||||
"startvideoblur": "Háttér elhomályosítása",
|
||||
"stopScreenSharing": "Képernyőmegosztás leállítása",
|
||||
"stopSharedVideo": "Videó leállítása",
|
||||
"stopSharedVideo": "Kamera leállítása",
|
||||
"stopSubtitles": "Felirat leállítása",
|
||||
"stopvideoblur": "Háttér elhomályosításának letiltása",
|
||||
"surprised": "Meglepett",
|
||||
"talkWhileMutedPopup": "Úgy tűnik beszélni szeretne, de le van némítva.",
|
||||
"tileViewToggle": "Mozaikos nézet átváltása",
|
||||
"toggleCamera": "Kamera átváltása",
|
||||
"videoSettings": "Videóbeállítások",
|
||||
"videomute": "Kamera indítása / leállítása"
|
||||
},
|
||||
"transcribing": {
|
||||
@@ -725,12 +1008,15 @@
|
||||
"pending": "{{displayName}} -t meghívta"
|
||||
},
|
||||
"videoStatus": {
|
||||
"adjustFor": "Igazítsa a legjobb:",
|
||||
"audioOnly": "CsH",
|
||||
"audioOnlyExpanded": "Jelenleg az alacsony sávszélességű mód az aktív, vagyis csak hangot lehet fogadni és képernyőmegosztást.",
|
||||
"bestPerformance": "Teljesítményhez",
|
||||
"callQuality": "Videominőség",
|
||||
"hd": "MF",
|
||||
"hdTooltip": "Magas felbontású videó megtekintése",
|
||||
"highDefinition": "Magas felbontású",
|
||||
"highestQuality": "Minőséghez",
|
||||
"labelTooiltipNoVideo": "Nincs videó",
|
||||
"labelTooltipAudioOnly": "Alacsony sávszélességű mód aktiválva",
|
||||
"ld": "AF",
|
||||
@@ -738,6 +1024,7 @@
|
||||
"lowDefinition": "Alacsony felbontású",
|
||||
"onlyAudioAvailable": "„Csak hang” mód elérhető",
|
||||
"onlyAudioSupported": "Csak a hang támogatott ebben a böngészőben.",
|
||||
"performanceSettings": "Teljesítménybeállítások",
|
||||
"sd": "SF",
|
||||
"sdTooltip": "Szabványos felbontású videó megtekintése",
|
||||
"standardDefinition": "Szabványos felbontású"
|
||||
@@ -745,7 +1032,10 @@
|
||||
"videothumbnail": {
|
||||
"domute": "Némítás",
|
||||
"domuteOthers": "Mindenki más elnémítása",
|
||||
"domuteVideoOfOthers": "Mindenki más kamerájának letiltása",
|
||||
"flip": "Tükrözés",
|
||||
"grantModerator": "Moderátori jogok megadása",
|
||||
"hideSelfView": "Saját kép elrejtése",
|
||||
"kick": "Kirúgás",
|
||||
"moderator": "Moderátor",
|
||||
"mute": "A résztvevő le van némítva",
|
||||
@@ -754,6 +1044,29 @@
|
||||
"show": "Megjelenítés a színpadon",
|
||||
"videomute": "A résztvevő leállította a kameráját"
|
||||
},
|
||||
"virtualBackground": {
|
||||
"addBackground": "Háttér hozzáadása",
|
||||
"apply": "Alkalmaz",
|
||||
"backgroundEffectError": "Hiba a háttér effekt hozzáadásnál.",
|
||||
"blur": "Elmosódott",
|
||||
"deleteImage": "Delete image",
|
||||
"desktopShare": "Asztal megosztása",
|
||||
"desktopShareError": "Nem lehet asztalt megosztani",
|
||||
"image1": "Tengerpart",
|
||||
"image2": "Fehér semleges fal",
|
||||
"image3": "Fehér üres szoba",
|
||||
"image4": "Fekete állólámpa",
|
||||
"image5": "Hegy",
|
||||
"image6": "Erdő",
|
||||
"image7": "Napfelkelte",
|
||||
"none": "Nincs",
|
||||
"pleaseWait": "Kérjük várjon...",
|
||||
"removeBackground": "Háttér eltávolítása",
|
||||
"slightBlur": "Enyhén elmosódott",
|
||||
"title": "Virtuális háttérképek",
|
||||
"uploadedImage": "Feltöltött kép {{index}}"
|
||||
},
|
||||
"volumeSlider": "Hangerő szabályzó",
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
"join": "Koppintson a csatlakozáshoz",
|
||||
@@ -771,17 +1084,20 @@
|
||||
"getHelp": "Segítség kérése",
|
||||
"go": "Indítás",
|
||||
"goSmall": "Indítás",
|
||||
"headerSubtitle": "Biztonságos és magas színvonalú konferenciák",
|
||||
"info": "Információ",
|
||||
"jitsiOnMobile": "Jitsi Mobil – töltse le az app-ot, és indítson megbeszélést bárhonnan",
|
||||
"join": "LÉTREHOZ /HOZZÁAD",
|
||||
"privacy": "Adatvédelem",
|
||||
"recentList": "Legutóbbi",
|
||||
"recentListDelete": "Törlés",
|
||||
"recentListDelete": "Lista törlés",
|
||||
"recentListEmpty": "A legutóbbi lista jelenleg üres. Csevegjen a csapattal és minden előző értekezlet itt lesz megtalálható.",
|
||||
"reducedUIText": "Üdvözlet a {{app}} programban!",
|
||||
"roomNameAllowedChars": "Az értekezlet neve nem tartalmazhatja a következő karaktereket: ?, &, :, ', \", %, #.",
|
||||
"roomname": "Adja meg a szoba nevét",
|
||||
"roomnameHint": "Adja meg a kívánt nevet vagy URL-t, amelyhez csatlakozni szeretne. Bármiképp elnevezheti, csak ossza meg az értekezlet résztvevőivel, hogy ők ugyanezt a nevet tudják majd megadni.",
|
||||
"sendFeedback": "Visszajelzés küldése",
|
||||
"startMeeting": "Csatlakozás",
|
||||
"terms": "Feltételek",
|
||||
"title": "Biztonságos, maradéktalanul felszerelt és teljesen ingyenes videokonferencia"
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"bridgeCount": "Server count: ",
|
||||
"codecs": "Codecs (A/V): ",
|
||||
"connectedTo": "Connected to:",
|
||||
"e2eeVerified": "E2EE verified:",
|
||||
"framerate": "Frame rate:",
|
||||
"less": "Show less",
|
||||
"localaddress": "Local address:",
|
||||
@@ -408,6 +409,10 @@
|
||||
"user": "User",
|
||||
"userIdentifier": "User identifier",
|
||||
"userPassword": "User password",
|
||||
"verifyParticipantConfirm": "They match",
|
||||
"verifyParticipantDismiss": "They do not match",
|
||||
"verifyParticipantQuestion": "EXPERIMENTAL: Ask participant {{participantName}} if they see the same content, in the same order.",
|
||||
"verifyParticipantTitle": "User verification",
|
||||
"videoLink": "Video link",
|
||||
"viewUpgradeOptions": "View upgrade options",
|
||||
"viewUpgradeOptionsContent": "To get unlimited access to premium features like recording, transcriptions, RTMP Streaming & more, you'll need to upgrade your plan.",
|
||||
@@ -563,7 +568,6 @@
|
||||
"lobby": {
|
||||
"admit": "Admit",
|
||||
"admitAll": "Admit all",
|
||||
"allow": "Allow",
|
||||
"backToKnockModeButton": "Ask to join",
|
||||
"chat": "Chat",
|
||||
"dialogTitle": "Lobby mode",
|
||||
@@ -1298,6 +1302,7 @@
|
||||
"show": "Show on stage",
|
||||
"showSelfView": "Show self view",
|
||||
"unpinFromStage": "Unpin",
|
||||
"verify": "Verify participant",
|
||||
"videoMuted": "Camera disabled",
|
||||
"videomute": "Participant has stopped the camera"
|
||||
},
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
getVideoTrackByParticipant,
|
||||
trackStreamingStatusChanged
|
||||
} from '../../../react/features/base/tracks';
|
||||
import { createDeferred } from '../../../react/features/base/util/helpers';
|
||||
import { CHAT_SIZE } from '../../../react/features/chat';
|
||||
import {
|
||||
isTrackStreamingStatusActive,
|
||||
@@ -38,7 +39,6 @@ import { getParticipantsPaneOpen } from '../../../react/features/participants-pa
|
||||
import { PresenceLabel } from '../../../react/features/presence-status';
|
||||
import { shouldDisplayTileView } from '../../../react/features/video-layout';
|
||||
/* eslint-enable no-unused-vars */
|
||||
import { createDeferred } from '../../util/helpers';
|
||||
import AudioLevels from '../audio_levels/AudioLevels';
|
||||
|
||||
import { VIDEO_CONTAINER_TYPE, VideoContainer } from './VideoContainer';
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
const logger = require('@jitsi/logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Manages a queue of functions where the current function in progress will
|
||||
* automatically execute the next queued function.
|
||||
*/
|
||||
export class TaskQueue {
|
||||
/**
|
||||
* Creates a new instance of {@link TaskQueue} and sets initial instance
|
||||
* variable values.
|
||||
*/
|
||||
constructor() {
|
||||
this._queue = [];
|
||||
this._currentTask = null;
|
||||
|
||||
this._onTaskComplete = this._onTaskComplete.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new function to the queue. It will be immediately invoked if no
|
||||
* other functions are queued.
|
||||
*
|
||||
* @param {Function} taskFunction - The function to be queued for execution.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
enqueue(taskFunction) {
|
||||
this._queue.push(taskFunction);
|
||||
this._executeNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* If no queued task is currently executing, invokes the first task in the
|
||||
* queue if any.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_executeNext() {
|
||||
if (this._currentTask) {
|
||||
logger.warn('Task queued while a task is in progress.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentTask = this._queue.shift() || null;
|
||||
|
||||
if (this._currentTask) {
|
||||
logger.debug('Executing a task.');
|
||||
|
||||
try {
|
||||
this._currentTask(this._onTaskComplete);
|
||||
} catch (error) {
|
||||
logger.error(`Task execution failed: ${error}`);
|
||||
this._onTaskComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares to invoke the next function in the queue.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onTaskComplete() {
|
||||
this._currentTask = null;
|
||||
logger.debug('Task completed.');
|
||||
this._executeNext();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { TaskQueue } from './TaskQueue';
|
||||
|
||||
/**
|
||||
* Create deferred object.
|
||||
*
|
||||
* @returns {{promise, resolve, reject}}
|
||||
*/
|
||||
export function createDeferred() {
|
||||
const deferred = {};
|
||||
|
||||
deferred.promise = new Promise((resolve, reject) => {
|
||||
deferred.resolve = resolve;
|
||||
deferred.reject = reject;
|
||||
});
|
||||
|
||||
return deferred;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of {@link TaskQueue}.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function createTaskQueue() {
|
||||
return new TaskQueue();
|
||||
}
|
||||
22
package-lock.json
generated
22
package-lock.json
generated
@@ -27,7 +27,7 @@
|
||||
"@giphy/react-components": "5.6.0",
|
||||
"@giphy/react-native-sdk": "1.7.0",
|
||||
"@hapi/bourne": "2.0.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.11/jitsi-excalidraw-0.0.11.tgz",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.12/jitsi-excalidraw-0.0.12.tgz",
|
||||
"@jitsi/js-utils": "2.0.4",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
@@ -74,7 +74,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1539.0.0+eb4873d2/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1549.0.0+877c4546/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -3746,9 +3746,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jitsi/excalidraw": {
|
||||
"version": "0.0.11",
|
||||
"resolved": "https://github.com/jitsi/excalidraw/releases/download/v0.0.11/jitsi-excalidraw-0.0.11.tgz",
|
||||
"integrity": "sha512-R0om5mYmjjozmJ6i5PXPSQy8/kiMTCqk/QVxOVryEUlHps4UeLNx+Gb/tjzNBN/C6db6ea+Vxt/l27nh9frphg==",
|
||||
"version": "0.0.12",
|
||||
"resolved": "https://github.com/jitsi/excalidraw/releases/download/v0.0.12/jitsi-excalidraw-0.0.12.tgz",
|
||||
"integrity": "sha512-WFzaH5GCZLA5DTSZ6ReqAz9g53mSgi+211zTC7AFZUYZme5tzKpPg55AKkJZA3ZIRikkbKaEfP/dC4QOH5NMmA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.2",
|
||||
@@ -13497,8 +13497,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1539.0.0+eb4873d2/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-hEu5nmljbOVKPHIcCpW6GTFzZpWDOAfplKTkNdvrIyNgMiIjHhUgmMzF+IGicd7KPud3z8aQ+0HFnpcxO/T6ZA==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1549.0.0+877c4546/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-cxzr8vnJ6RyqWYzJ4LO09PqblJ6nIrJFFmzW8kPQgC1Nhq7sDAD896827q/shd+FE6bSoK0xVjDP+gW+JInS9A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
@@ -23178,8 +23178,8 @@
|
||||
"dev": true
|
||||
},
|
||||
"@jitsi/excalidraw": {
|
||||
"version": "https://github.com/jitsi/excalidraw/releases/download/v0.0.11/jitsi-excalidraw-0.0.11.tgz",
|
||||
"integrity": "sha512-R0om5mYmjjozmJ6i5PXPSQy8/kiMTCqk/QVxOVryEUlHps4UeLNx+Gb/tjzNBN/C6db6ea+Vxt/l27nh9frphg=="
|
||||
"version": "https://github.com/jitsi/excalidraw/releases/download/v0.0.12/jitsi-excalidraw-0.0.12.tgz",
|
||||
"integrity": "sha512-WFzaH5GCZLA5DTSZ6ReqAz9g53mSgi+211zTC7AFZUYZme5tzKpPg55AKkJZA3ZIRikkbKaEfP/dC4QOH5NMmA=="
|
||||
},
|
||||
"@jitsi/js-utils": {
|
||||
"version": "2.0.4",
|
||||
@@ -30510,8 +30510,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1539.0.0+eb4873d2/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-hEu5nmljbOVKPHIcCpW6GTFzZpWDOAfplKTkNdvrIyNgMiIjHhUgmMzF+IGicd7KPud3z8aQ+0HFnpcxO/T6ZA==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1549.0.0+877c4546/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-cxzr8vnJ6RyqWYzJ4LO09PqblJ6nIrJFFmzW8kPQgC1Nhq7sDAD896827q/shd+FE6bSoK0xVjDP+gW+JInS9A==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@giphy/react-components": "5.6.0",
|
||||
"@giphy/react-native-sdk": "1.7.0",
|
||||
"@hapi/bourne": "2.0.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.11/jitsi-excalidraw-0.0.11.tgz",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.12/jitsi-excalidraw-0.0.12.tgz",
|
||||
"@jitsi/js-utils": "2.0.4",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
@@ -79,7 +79,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1539.0.0+eb4873d2/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1549.0.0+877c4546/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import logger from '../logger';
|
||||
|
||||
import AbstractHandler, { IEvent } from './AbstractHandler';
|
||||
@@ -103,8 +101,10 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
|
||||
return {
|
||||
sessionId: amplitude.getInstance().getSessionId(),
|
||||
|
||||
// @ts-ignore
|
||||
deviceId: amplitude.getInstance().options.deviceId,
|
||||
|
||||
// @ts-ignore
|
||||
userId: amplitude.getInstance().options.userId
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ import { IResponsiveUIState } from '../base/responsive-ui/reducer';
|
||||
import { ISettingsState } from '../base/settings/reducer';
|
||||
import { ISoundsState } from '../base/sounds/reducer';
|
||||
import { ITestingState } from '../base/testing/reducer';
|
||||
import { INoSrcDataState, ITracksState } from '../base/tracks/reducer';
|
||||
import { INoSrcDataState, ITrackOperations, ITracksState } from '../base/tracks/reducer';
|
||||
import { IUserInteractionState } from '../base/user-interaction/reducer';
|
||||
import { IBreakoutRoomsState } from '../breakout-rooms/reducer';
|
||||
import { ICalendarSyncState } from '../calendar-sync/reducer';
|
||||
@@ -107,6 +107,7 @@ export interface IReduxState {
|
||||
'features/base/responsive-ui': IResponsiveUIState;
|
||||
'features/base/settings': ISettingsState;
|
||||
'features/base/sounds': ISoundsState;
|
||||
'features/base/track-operations': ITrackOperations;
|
||||
'features/base/tracks': ITracksState;
|
||||
'features/base/user-interaction': IUserInteractionState;
|
||||
'features/breakout-rooms': IBreakoutRoomsState;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { appNavigate } from '../../app/actions';
|
||||
@@ -22,11 +21,13 @@ import { getNormalizedDisplayName } from '../participants/functions';
|
||||
import { toState } from '../redux/functions';
|
||||
import {
|
||||
destroyLocalTracks,
|
||||
replaceLocalTrack,
|
||||
executeTrackOperation,
|
||||
replaceStoredTracks,
|
||||
trackAdded,
|
||||
trackRemoved
|
||||
} from '../tracks/actions.any';
|
||||
} from '../tracks/actions';
|
||||
import { getLocalTracks } from '../tracks/functions';
|
||||
import { TrackOperationType } from '../tracks/types';
|
||||
import { getBackendSafeRoomName } from '../util/uri';
|
||||
|
||||
import {
|
||||
@@ -111,6 +112,7 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
|
||||
JitsiConferenceEvents.CONFERENCE_LEFT,
|
||||
(...args: any[]) => {
|
||||
dispatch(conferenceTimestampChanged(0));
|
||||
|
||||
// @ts-ignore
|
||||
dispatch(conferenceLeft(conference, ...args));
|
||||
});
|
||||
@@ -137,34 +139,43 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
|
||||
conference.on(
|
||||
JitsiConferenceEvents.STARTED_MUTED,
|
||||
() => {
|
||||
const audioMuted = Boolean(conference.isStartAudioMuted());
|
||||
const videoMuted = Boolean(conference.isStartVideoMuted());
|
||||
const localTracks = getLocalTracks(state['features/base/tracks']);
|
||||
dispatch(executeTrackOperation(TrackOperationType.AudioVideo, () => {
|
||||
const promises = [];
|
||||
const audioMuted = Boolean(conference.isStartAudioMuted());
|
||||
const videoMuted = Boolean(conference.isStartVideoMuted());
|
||||
const localTracks = getLocalTracks(state['features/base/tracks']);
|
||||
|
||||
sendAnalytics(createStartMutedConfigurationEvent('remote', audioMuted, videoMuted));
|
||||
logger.log(`Start muted: ${audioMuted ? 'audio, ' : ''}${videoMuted ? 'video' : ''}`);
|
||||
sendAnalytics(createStartMutedConfigurationEvent('remote', audioMuted, videoMuted));
|
||||
logger.log(`Start muted: ${audioMuted ? 'audio, ' : ''}${videoMuted ? 'video' : ''}`);
|
||||
|
||||
// XXX Jicofo tells lib-jitsi-meet to start with audio and/or video
|
||||
// muted i.e. Jicofo expresses an intent. Lib-jitsi-meet has turned
|
||||
// Jicofo's intent into reality by actually muting the respective
|
||||
// tracks. The reality is expressed in base/tracks already so what
|
||||
// is left is to express Jicofo's intent in base/media.
|
||||
// TODO Maybe the app needs to learn about Jicofo's intent and
|
||||
// transfer that intent to lib-jitsi-meet instead of lib-jitsi-meet
|
||||
// acting on Jicofo's intent without the app's knowledge.
|
||||
dispatch(setAudioMuted(audioMuted));
|
||||
dispatch(setVideoMuted(videoMuted));
|
||||
// XXX Jicofo tells lib-jitsi-meet to start with audio and/or video
|
||||
// muted i.e. Jicofo expresses an intent. Lib-jitsi-meet has turned
|
||||
// Jicofo's intent into reality by actually muting the respective
|
||||
// tracks. The reality is expressed in base/tracks already so what
|
||||
// is left is to express Jicofo's intent in base/media.
|
||||
// TODO Maybe the app needs to learn about Jicofo's intent and
|
||||
// transfer that intent to lib-jitsi-meet instead of lib-jitsi-meet
|
||||
// acting on Jicofo's intent without the app's knowledge.
|
||||
promises.push(
|
||||
dispatch(setAudioMuted(audioMuted)).catch(e => logger.error(`Set audio muted failed: ${e}`)));
|
||||
promises.push(
|
||||
dispatch(setVideoMuted(videoMuted)).catch(e => logger.error(`Set video muted failed: ${e}`)));
|
||||
|
||||
// Remove the tracks from peerconnection as well.
|
||||
for (const track of localTracks) {
|
||||
const trackType = track.jitsiTrack.getType();
|
||||
// Remove the tracks from peerconnection as well.
|
||||
for (const track of localTracks) {
|
||||
const trackType = track.jitsiTrack.getType();
|
||||
|
||||
// Do not remove the audio track on RN. Starting with iOS 15 it will fail to unmute otherwise.
|
||||
if ((audioMuted && trackType === MEDIA_TYPE.AUDIO && navigator.product !== 'ReactNative')
|
||||
|| (videoMuted && trackType === MEDIA_TYPE.VIDEO)) {
|
||||
dispatch(replaceLocalTrack(track.jitsiTrack, null, conference));
|
||||
// Do not remove the audio track on RN. Starting with iOS 15 it will fail to unmute otherwise.
|
||||
if ((audioMuted && trackType === MEDIA_TYPE.AUDIO && navigator.product !== 'ReactNative')
|
||||
|| (videoMuted && trackType === MEDIA_TYPE.VIDEO)) {
|
||||
promises.push(
|
||||
dispatch(replaceStoredTracks(track.jitsiTrack, null))
|
||||
.catch(e => logger.error(`replaceLocalTrack failed: ${e}`)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}));
|
||||
});
|
||||
|
||||
conference.on(
|
||||
|
||||
@@ -59,6 +59,7 @@ export interface IJitsiConference {
|
||||
grantOwner: Function;
|
||||
isAVModerationSupported: Function;
|
||||
isCallstatsEnabled: Function;
|
||||
isE2EEEnabled: Function;
|
||||
isEndConferenceSupported: Function;
|
||||
isLobbySupported: Function;
|
||||
isSIPCallingSupported: Function;
|
||||
@@ -71,6 +72,7 @@ export interface IJitsiConference {
|
||||
muteParticipant: Function;
|
||||
myLobbyUserId: Function;
|
||||
myUserId: Function;
|
||||
off: Function;
|
||||
on: Function;
|
||||
removeTrack: Function;
|
||||
replaceTrack: Function;
|
||||
@@ -88,6 +90,7 @@ export interface IJitsiConference {
|
||||
setReceiverConstraints: Function;
|
||||
setSenderVideoConstraint: Function;
|
||||
setSubject: Function;
|
||||
startVerification: Function;
|
||||
}
|
||||
|
||||
export interface IConferenceState {
|
||||
|
||||
@@ -316,6 +316,7 @@ export interface IConfig {
|
||||
sdkKey?: string;
|
||||
tileTime?: number;
|
||||
};
|
||||
googleApiApplicationClientID?: string;
|
||||
gravatar?: {
|
||||
baseUrl?: string;
|
||||
disabled?: boolean;
|
||||
@@ -366,6 +367,7 @@ export interface IConfig {
|
||||
localSubject?: string;
|
||||
locationURL?: URL;
|
||||
maxFullResolutionParticipants?: number;
|
||||
microsoftApiApplicationClientID?: string;
|
||||
moderatedRoomServiceUrl?: string;
|
||||
mouseMoveCallbackInterval?: number;
|
||||
noticeMessage?: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
// @ts-ignore
|
||||
import Bourne from '@hapi/bourne';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import _ from 'lodash';
|
||||
|
||||
@@ -77,6 +77,7 @@ export interface IConfigState extends IConfig {
|
||||
analysis?: {
|
||||
obfuscateRoomName?: boolean;
|
||||
};
|
||||
disableRemoteControl?: boolean;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ConnectionFailedError } from './actions.any';
|
||||
export interface IConnectionState {
|
||||
connecting?: any;
|
||||
connection?: {
|
||||
addFeature: Function;
|
||||
disconnect: Function;
|
||||
getJid: () => string;
|
||||
getLogs: () => Object;
|
||||
|
||||
@@ -4,6 +4,7 @@ import WaitForOwnerDialog from '../../authentication/components/web/WaitForOwner
|
||||
import ChatPrivacyDialog from '../../chat/components/web/ChatPrivacyDialog';
|
||||
import DesktopPicker from '../../desktop-picker/components/DesktopPicker';
|
||||
import DisplayNamePrompt from '../../display-name/components/web/DisplayNamePrompt';
|
||||
import ParticipantVerificationDialog from '../../e2ee/components/ParticipantVerificationDialog';
|
||||
import EmbedMeetingDialog from '../../embed-meeting/components/EmbedMeetingDialog';
|
||||
// @ts-ignore
|
||||
import FeedbackDialog from '../../feedback/components/FeedbackDialog.web';
|
||||
@@ -49,7 +50,7 @@ const NEW_DIALOG_LIST = [ KeyboardShortcutsDialog, ChatPrivacyDialog, DisplayNam
|
||||
SharedVideoDialog, SpeakerStats, LanguageSelectorDialog, MuteEveryoneDialog, MuteEveryonesVideoDialog,
|
||||
GrantModeratorDialog, KickRemoteParticipantDialog, MuteRemoteParticipantsVideoDialog, VideoQualityDialog,
|
||||
VirtualBackgroundDialog, LoginDialog, WaitForOwnerDialog, DesktopPicker, RemoteControlAuthorizationDialog,
|
||||
LogoutDialog, SalesforceLinkDialog ];
|
||||
LogoutDialog, SalesforceLinkDialog, ParticipantVerificationDialog ];
|
||||
|
||||
// This function is necessary while the transition from @atlaskit dialog to our component is ongoing.
|
||||
const isNewDialog = (component: any) => NEW_DIALOG_LIST.some(comp => comp === component);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
// @ts-ignore
|
||||
import { Container } from '../../react/base';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { styleTypeToObject } from '../../styles';
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@ import { IStore } from '../../app/types';
|
||||
import { showModeratedNotification } from '../../av-moderation/actions';
|
||||
import { shouldShowModeratedNotification } from '../../av-moderation/functions';
|
||||
import { isModerationNotificationDisplayed } from '../../notifications/functions';
|
||||
import { isForceMuted } from '../../participants-pane/functions';
|
||||
import { maybeStopMuteBecauseOfLocalRecording } from '../../recording/functions';
|
||||
import { getLocalParticipant } from '../participants/functions';
|
||||
import { setMuted } from '../tracks/actions.any';
|
||||
import { isUserInteractionRequiredForUnmute } from '../tracks/functions';
|
||||
|
||||
import {
|
||||
SET_AUDIO_AVAILABLE,
|
||||
@@ -53,10 +58,32 @@ export function setAudioAvailable(available: boolean) {
|
||||
* }}
|
||||
*/
|
||||
export function setAudioMuted(muted: boolean, ensureTrack = false) {
|
||||
return {
|
||||
type: SET_AUDIO_MUTED,
|
||||
ensureTrack,
|
||||
muted
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const participant = getLocalParticipant(state);
|
||||
|
||||
if (!muted && isForceMuted(participant, MEDIA_TYPE.AUDIO, state)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!muted
|
||||
&& isUserInteractionRequiredForUnmute(state)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const mutePromise = dispatch(setMuted({
|
||||
muted,
|
||||
ensureTrack,
|
||||
mediaType: MEDIA_TYPE.AUDIO
|
||||
}));
|
||||
|
||||
dispatch({
|
||||
type: SET_AUDIO_MUTED,
|
||||
ensureTrack,
|
||||
muted
|
||||
});
|
||||
|
||||
return mutePromise;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,7 +141,7 @@ export function setScreenshareMuted(
|
||||
ensureTrack && dispatch(showModeratedNotification(MEDIA_TYPE.SCREENSHARE));
|
||||
}
|
||||
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const oldValue = state['features/base/media'].screenshare.muted;
|
||||
@@ -122,13 +149,28 @@ export function setScreenshareMuted(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
const newValue = muted ? oldValue | authority : oldValue & ~authority;
|
||||
|
||||
return dispatch({
|
||||
const participant = getLocalParticipant(state);
|
||||
|
||||
if (!newValue && isForceMuted(participant, MEDIA_TYPE.SCREENSHARE, state)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const mutePromise = dispatch(setMuted({
|
||||
authority,
|
||||
mediaType,
|
||||
ensureTrack,
|
||||
muted: Boolean(newValue)
|
||||
}));
|
||||
|
||||
dispatch({
|
||||
type: SET_SCREENSHARE_MUTED,
|
||||
authority,
|
||||
mediaType,
|
||||
ensureTrack,
|
||||
muted: newValue
|
||||
});
|
||||
|
||||
return mutePromise;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -163,7 +205,7 @@ export function setVideoAvailable(available: boolean) {
|
||||
*/
|
||||
export function setVideoMuted(
|
||||
muted: boolean,
|
||||
mediaType: string = MEDIA_TYPE.VIDEO,
|
||||
mediaType: MediaType = MEDIA_TYPE.VIDEO,
|
||||
authority: number = VIDEO_MUTISM_AUTHORITY.USER,
|
||||
ensureTrack = false) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
@@ -175,21 +217,43 @@ export function setVideoMuted(
|
||||
ensureTrack && dispatch(showModeratedNotification(MEDIA_TYPE.VIDEO));
|
||||
}
|
||||
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const oldValue = state['features/base/media'].video.muted;
|
||||
|
||||
// eslint-disable-next-line no-bitwise
|
||||
const newValue = muted ? oldValue | authority : oldValue & ~authority;
|
||||
const participant = getLocalParticipant(state);
|
||||
|
||||
return dispatch({
|
||||
if (!newValue && isForceMuted(participant, MEDIA_TYPE.VIDEO, state)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (maybeStopMuteBecauseOfLocalRecording(Boolean(newValue), dispatch)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!newValue && isUserInteractionRequiredForUnmute(state)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const mutePromise = dispatch(setMuted({
|
||||
ensureTrack,
|
||||
authority,
|
||||
muted: Boolean(newValue),
|
||||
mediaType
|
||||
}));
|
||||
|
||||
dispatch({
|
||||
type: SET_VIDEO_MUTED,
|
||||
authority,
|
||||
mediaType,
|
||||
ensureTrack,
|
||||
muted: newValue
|
||||
});
|
||||
|
||||
return mutePromise;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,10 @@ import {
|
||||
NOTIFICATION_TIMEOUT_TYPE,
|
||||
showWarningNotification
|
||||
} from '../../notifications';
|
||||
import { isForceMuted } from '../../participants-pane/functions';
|
||||
import { isScreenMediaShared } from '../../screen-share/functions';
|
||||
import { SET_AUDIO_ONLY, setAudioOnly } from '../audio-only';
|
||||
import { SET_ROOM, isRoomValid } from '../conference';
|
||||
import { getMultipleVideoSendingSupportFeatureFlag } from '../config';
|
||||
import { getLocalParticipant } from '../participants';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { getPropertyValue } from '../settings';
|
||||
import {
|
||||
@@ -27,12 +25,11 @@ import {
|
||||
isLocalVideoTrackDesktop,
|
||||
setTrackMuted
|
||||
} from '../tracks';
|
||||
import { executeTrackOperation } from '../tracks/actions';
|
||||
import { TrackOperationType } from '../tracks/types';
|
||||
|
||||
import {
|
||||
SET_AUDIO_MUTED,
|
||||
SET_AUDIO_UNMUTE_PERMISSIONS,
|
||||
SET_SCREENSHARE_MUTED,
|
||||
SET_VIDEO_MUTED,
|
||||
SET_VIDEO_UNMUTE_PERMISSIONS
|
||||
} from './actionTypes';
|
||||
import {
|
||||
@@ -54,6 +51,9 @@ import {
|
||||
_VIDEO_INITIAL_MEDIA_STATE
|
||||
} from './reducer';
|
||||
|
||||
|
||||
import './subscriber';
|
||||
|
||||
/**
|
||||
* Implements the entry point of the middleware of the feature base/media.
|
||||
*
|
||||
@@ -83,16 +83,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
return result;
|
||||
}
|
||||
|
||||
case SET_AUDIO_MUTED: {
|
||||
const state = store.getState();
|
||||
const participant = getLocalParticipant(state);
|
||||
|
||||
if (!action.muted && isForceMuted(participant, MEDIA_TYPE.AUDIO, state)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_AUDIO_UNMUTE_PERMISSIONS: {
|
||||
const { blocked, skipNotification } = action;
|
||||
const state = store.getState();
|
||||
@@ -108,25 +98,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_SCREENSHARE_MUTED: {
|
||||
const state = store.getState();
|
||||
const participant = getLocalParticipant(state);
|
||||
|
||||
if (!action.muted && isForceMuted(participant, MEDIA_TYPE.SCREENSHARE, state)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SET_VIDEO_MUTED: {
|
||||
const state = store.getState();
|
||||
const participant = getLocalParticipant(state);
|
||||
|
||||
if (!action.muted && isForceMuted(participant, MEDIA_TYPE.VIDEO, state)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_VIDEO_UNMUTE_PERMISSIONS: {
|
||||
const { blocked, skipNotification } = action;
|
||||
const state = store.getState();
|
||||
@@ -191,9 +162,12 @@ function _setAudioOnly({ dispatch, getState }, next, action) {
|
||||
sendAnalytics(createTrackMutedEvent('video', 'audio-only mode', audioOnly));
|
||||
|
||||
// Make sure we mute both the desktop and video tracks.
|
||||
dispatch(setVideoMuted(audioOnly, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY));
|
||||
dispatch(executeTrackOperation(TrackOperationType.Video,
|
||||
() => dispatch(setVideoMuted(audioOnly, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY))));
|
||||
if (getMultipleVideoSendingSupportFeatureFlag(state)) {
|
||||
dispatch(setScreenshareMuted(audioOnly, MEDIA_TYPE.SCREENSHARE, SCREENSHARE_MUTISM_AUTHORITY.AUDIO_ONLY));
|
||||
dispatch(executeTrackOperation(TrackOperationType.Video,
|
||||
() => dispatch(setScreenshareMuted(
|
||||
audioOnly, MEDIA_TYPE.SCREENSHARE, SCREENSHARE_MUTISM_AUTHORITY.AUDIO_ONLY))));
|
||||
}
|
||||
|
||||
return next(action);
|
||||
@@ -234,9 +208,9 @@ function _setRoom({ dispatch, getState }, next, action) {
|
||||
// Unconditionally express the desires/expectations/intents of the app and
|
||||
// the user i.e. the state of base/media. Eventually, practice/reality i.e.
|
||||
// the state of base/tracks will or will not agree with the desires.
|
||||
dispatch(setAudioMuted(audioMuted));
|
||||
dispatch(executeTrackOperation(TrackOperationType.Audio, () => dispatch(setAudioMuted(audioMuted))));
|
||||
dispatch(setCameraFacingMode(CAMERA_FACING_MODE.USER));
|
||||
dispatch(setVideoMuted(videoMuted));
|
||||
dispatch(executeTrackOperation(TrackOperationType.Video, () => dispatch(setVideoMuted(videoMuted))));
|
||||
|
||||
// startAudioOnly
|
||||
//
|
||||
@@ -296,21 +270,29 @@ function _setRoom({ dispatch, getState }, next, action) {
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _syncTrackMutedState({ getState }, track) {
|
||||
const state = getState()['features/base/media'];
|
||||
function _syncTrackMutedState({ dispatch, getState }, track) {
|
||||
const mediaType = track.mediaType;
|
||||
const muted = Boolean(state[mediaType].muted);
|
||||
const trackOpType = mediaType === MEDIA_TYPE.AUDIO ? TrackOperationType.Audio : TrackOperationType.Video;
|
||||
|
||||
// XXX If muted state of track when it was added is different from our media
|
||||
// muted state, we need to mute track and explicitly modify 'muted' property
|
||||
// on track. This is because though TRACK_ADDED action was dispatched it's
|
||||
// not yet in redux state and JitsiTrackEvents.TRACK_MUTE_CHANGED may be
|
||||
// fired before track gets to state.
|
||||
if (track.muted !== muted) {
|
||||
sendAnalytics(createSyncTrackStateEvent(mediaType, muted));
|
||||
logger.log(`Sync ${mediaType} track muted state to ${muted ? 'muted' : 'unmuted'}`);
|
||||
dispatch(executeTrackOperation(trackOpType, () => {
|
||||
const state = getState()['features/base/media'];
|
||||
|
||||
track.muted = muted;
|
||||
setTrackMuted(track.jitsiTrack, muted, state);
|
||||
}
|
||||
const muted = Boolean(state[mediaType].muted);
|
||||
|
||||
// XXX If muted state of track when it was added is different from our media
|
||||
// muted state, we need to mute track and explicitly modify 'muted' property
|
||||
// on track. This is because though TRACK_ADDED action was dispatched it's
|
||||
// not yet in redux state and JitsiTrackEvents.TRACK_MUTE_CHANGED may be
|
||||
// fired before track gets to state.
|
||||
if (track.muted !== muted) {
|
||||
sendAnalytics(createSyncTrackStateEvent(track.mediaType, muted));
|
||||
logger.log(`Sync ${track.mediaType} track muted state to ${muted ? 'muted' : 'unmuted'}`);
|
||||
|
||||
track.muted = muted;
|
||||
|
||||
return setTrackMuted(track.jitsiTrack, muted, state);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}));
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
import './middleware.any.js';
|
||||
@@ -1,45 +0,0 @@
|
||||
import './middleware.any.js';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager.web';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import StopRecordingDialog from '../../recording/components/Recording/web/StopRecordingDialog';
|
||||
import { openDialog } from '../dialog/actions';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
import { SET_VIDEO_MUTED } from './actionTypes';
|
||||
|
||||
import './subscriber';
|
||||
|
||||
/**
|
||||
* Implements the entry point of the middleware of the feature base/media.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: any) => {
|
||||
const { dispatch } = store;
|
||||
|
||||
switch (action.type) {
|
||||
case SET_VIDEO_MUTED: {
|
||||
if (LocalRecordingManager.isRecordingLocally() && LocalRecordingManager.selfRecording.on) {
|
||||
if (action.muted && LocalRecordingManager.selfRecording.withVideo) {
|
||||
dispatch(openDialog(StopRecordingDialog, { localRecordingVideoStop: true }));
|
||||
|
||||
return;
|
||||
} else if (!action.muted && !LocalRecordingManager.selfRecording.withVideo) {
|
||||
dispatch(showNotification({
|
||||
titleKey: 'recording.localRecordingNoVideo',
|
||||
descriptionKey: 'recording.localRecordingVideoWarning',
|
||||
uid: 'recording.localRecordingNoVideo'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
@@ -477,18 +477,19 @@ export function participantMutedUs(participant: any, track: any) {
|
||||
/**
|
||||
* Action to create a virtual screenshare participant.
|
||||
*
|
||||
* @param {(string)} sourceName - JitsiTrack instance.
|
||||
* @param {(boolean)} local - JitsiTrack instance.
|
||||
* @param {(string)} sourceName - The source name of the JitsiTrack instance.
|
||||
* @param {(boolean)} local - Whether it's a local or remote participant.
|
||||
* @param {JitsiConference} conference - The conference instance for which the participant is to be created.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function createVirtualScreenshareParticipant(sourceName: string, local: boolean) {
|
||||
export function createVirtualScreenshareParticipant(sourceName: string, local: boolean, conference: any) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(sourceName);
|
||||
const ownerName = getParticipantDisplayName(state, ownerId);
|
||||
|
||||
dispatch(participantJoined({
|
||||
conference: state['features/base/conference'].conference,
|
||||
conference,
|
||||
fakeParticipant: local ? FakeParticipant.LocalScreenShare : FakeParticipant.RemoteScreenShare,
|
||||
id: sourceName,
|
||||
name: ownerName
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
// @ts-ignore
|
||||
import { getGravatarURL } from '@jitsi/js-utils/avatar';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
// @ts-ignore
|
||||
import { isStageFilmstripAvailable } from '../../filmstrip/functions';
|
||||
import { IStateful } from '../app/types';
|
||||
import { GRAVATAR_BASE_URL } from '../avatar/constants';
|
||||
@@ -20,6 +18,7 @@ import {
|
||||
PARTICIPANT_ROLE,
|
||||
WHITEBOARD_PARTICIPANT_ICON
|
||||
} from './constants';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { preloadImage } from './preloadImage';
|
||||
import { FakeParticipant, IParticipant } from './types';
|
||||
|
||||
@@ -46,7 +46,7 @@ function _updateScreenshareParticipants({ getState, dispatch }: IStore) {
|
||||
|
||||
if (getMultipleVideoSendingSupportFeatureFlag(state)) {
|
||||
if (!localScreenShare && newLocalSceenshareSourceName) {
|
||||
dispatch(createVirtualScreenshareParticipant(newLocalSceenshareSourceName, true));
|
||||
dispatch(createVirtualScreenshareParticipant(newLocalSceenshareSourceName, true, conference));
|
||||
}
|
||||
|
||||
if (localScreenShare && !newLocalSceenshareSourceName) {
|
||||
@@ -68,7 +68,7 @@ function _updateScreenshareParticipants({ getState, dispatch }: IStore) {
|
||||
}
|
||||
|
||||
if (addedScreenshareSourceNames.length) {
|
||||
addedScreenshareSourceNames.forEach(id => dispatch(createVirtualScreenshareParticipant(id, false)));
|
||||
|
||||
addedScreenshareSourceNames.forEach(id => dispatch(
|
||||
createVirtualScreenshareParticipant(id, false, conference)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ export interface IParticipant {
|
||||
dominantSpeaker?: boolean;
|
||||
e2eeEnabled?: boolean;
|
||||
e2eeSupported?: boolean;
|
||||
e2eeVerificationAvailable?: boolean;
|
||||
e2eeVerified?: boolean;
|
||||
email?: string;
|
||||
fakeParticipant?: FakeParticipant;
|
||||
features?: {
|
||||
|
||||
@@ -10,8 +10,8 @@ import { navigate }
|
||||
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
// @ts-ignore
|
||||
import { screen } from '../../../../mobile/navigation/routes';
|
||||
// @ts-ignore
|
||||
import { SETTINGS_ENABLED, getFeatureFlag } from '../../../flags';
|
||||
import { SETTINGS_ENABLED } from '../../../flags/constants';
|
||||
import { getFeatureFlag } from '../../../flags/functions';
|
||||
import { connect } from '../../../redux/functions';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import _ from 'lodash';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
|
||||
@@ -9,6 +9,17 @@
|
||||
*/
|
||||
export const SET_NO_SRC_DATA_NOTIFICATION_UID = 'SET_NO_SRC_DATA_NOTIFICATION_UID';
|
||||
|
||||
/**
|
||||
* Sets the track operation promise.
|
||||
*
|
||||
* {
|
||||
* type: SET_TRACK_OPERATIONS_PROMISE,
|
||||
* audioTrackOperationsPromise: Promise<void>,
|
||||
* videoTrackOperationsPromise: Promise<void>
|
||||
* }
|
||||
*/
|
||||
export const SET_TRACK_OPERATIONS_PROMISE = 'SET_TRACK_OPERATIONS_PROMISE';
|
||||
|
||||
/**
|
||||
* The type of redux action dispatched when a track has been (locally or
|
||||
* remotely) added to the conference.
|
||||
|
||||
@@ -3,22 +3,22 @@ import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IStore } from '../../app/types';
|
||||
import { showErrorNotification, showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT, NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { IJitsiConference } from '../conference/reducer';
|
||||
import { getMultipleVideoSendingSupportFeatureFlag } from '../config/functions.any';
|
||||
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||
import { createLocalTrack } from '../lib-jitsi-meet/functions.any';
|
||||
import { setAudioMuted, setScreenshareMuted, setVideoMuted } from '../media/actions';
|
||||
import {
|
||||
CAMERA_FACING_MODE,
|
||||
MEDIA_TYPE,
|
||||
MediaType,
|
||||
SCREENSHARE_MUTISM_AUTHORITY,
|
||||
VIDEO_MUTISM_AUTHORITY,
|
||||
VIDEO_TYPE,
|
||||
VideoType
|
||||
} from '../media/constants';
|
||||
import { getLocalParticipant } from '../participants/functions';
|
||||
import { updateSettings } from '../settings/actions';
|
||||
|
||||
import {
|
||||
SET_NO_SRC_DATA_NOTIFICATION_UID,
|
||||
@@ -38,7 +38,8 @@ import {
|
||||
getLocalTrack,
|
||||
getLocalTracks,
|
||||
getLocalVideoTrack,
|
||||
getTrackByJitsiTrack
|
||||
getTrackByJitsiTrack,
|
||||
setTrackMuted
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { ITrackOptions } from './types';
|
||||
@@ -57,7 +58,7 @@ export function addLocalTrack(newTrack: any) {
|
||||
await conference.addTrack(newTrack);
|
||||
}
|
||||
|
||||
const setMuted = newTrack.isVideoTrack()
|
||||
const setMutedA = newTrack.isVideoTrack()
|
||||
? getMultipleVideoSendingSupportFeatureFlag(getState())
|
||||
&& newTrack.getVideoType() === VIDEO_TYPE.DESKTOP
|
||||
? setScreenshareMuted
|
||||
@@ -66,7 +67,7 @@ export function addLocalTrack(newTrack: any) {
|
||||
const isMuted = newTrack.isMuted();
|
||||
|
||||
logger.log(`Adding ${newTrack.getType()} track - ${isMuted ? 'muted' : 'unmuted'}`);
|
||||
await dispatch(setMuted(isMuted));
|
||||
await dispatch(setMutedA(isMuted));
|
||||
|
||||
return dispatch(_addTracks([ newTrack ]));
|
||||
};
|
||||
@@ -139,6 +140,7 @@ export function createLocalTracksA(options: ITrackOptions = {}) {
|
||||
dispatch,
|
||||
getState
|
||||
};
|
||||
const promises: Promise<any>[] = [];
|
||||
|
||||
// The following executes on React Native only at the time of this
|
||||
// writing. The effort to port Web's createInitialLocalTracksAndConnect
|
||||
@@ -216,7 +218,14 @@ export function createLocalTracksA(options: ITrackOptions = {}) {
|
||||
mediaType: device
|
||||
}
|
||||
});
|
||||
|
||||
promises.push(gumProcess.catch(() => {
|
||||
// ignore the error in the result promises so that the Promise.all resolves after all promises are
|
||||
// settled.
|
||||
}));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -329,7 +338,7 @@ export function replaceLocalTrack(oldTrack: any, newTrack: any, conference?: IJi
|
||||
* @param {JitsiLocalTrack|null} newTrack - The track to use instead.
|
||||
* @returns {Function}
|
||||
*/
|
||||
function replaceStoredTracks(oldTrack: any, newTrack: any) {
|
||||
export function replaceStoredTracks(oldTrack: any, newTrack: any) {
|
||||
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
// We call dispose after doing the replace because dispose will
|
||||
// try and do a new o/a after the track removes itself. Doing it
|
||||
@@ -345,7 +354,7 @@ function replaceStoredTracks(oldTrack: any, newTrack: any) {
|
||||
// should be falsey. As such, emit a mute event here to set up the app to reflect the track's mute
|
||||
// state. If this is not done, the current mute state of the app will be reflected on the track,
|
||||
// not vice-versa.
|
||||
const setMuted = newTrack.isVideoTrack()
|
||||
const setMutedA = newTrack.isVideoTrack()
|
||||
? getMultipleVideoSendingSupportFeatureFlag(getState())
|
||||
&& newTrack.getVideoType() === VIDEO_TYPE.DESKTOP
|
||||
? setScreenshareMuted
|
||||
@@ -356,7 +365,7 @@ function replaceStoredTracks(oldTrack: any, newTrack: any) {
|
||||
sendAnalytics(createTrackMutedEvent(newTrack.getType(), 'track.replaced', isMuted));
|
||||
logger.log(`Replace ${newTrack.getType()} track - ${isMuted ? 'muted' : 'unmuted'}`);
|
||||
|
||||
await dispatch(setMuted(isMuted));
|
||||
await dispatch(setMutedA(isMuted));
|
||||
await dispatch(_addTracks([ newTrack ]));
|
||||
}
|
||||
};
|
||||
@@ -817,37 +826,48 @@ export function updateLastTrackVideoMediaEvent(track: any, name: string): {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggles the facingMode constraint on the video stream.
|
||||
* Mutes or unmutes a local track with a specific media type.
|
||||
*
|
||||
* @returns {Function}
|
||||
* @param {Object} options - Parameters of the function.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function toggleCamera() {
|
||||
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
export function setMuted({ ensureTrack, authority, mediaType, muted }: {
|
||||
authority?: number; ensureTrack: boolean; mediaType: MediaType; muted: boolean; }) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise<any> => {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localVideoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||
const currentFacingMode = localVideoTrack.getCameraFacingMode();
|
||||
const localTrack = getLocalTrack(state['features/base/tracks'], mediaType, true);
|
||||
|
||||
/**
|
||||
* FIXME: Ideally, we should be dispatching {@code replaceLocalTrack} here,
|
||||
* but it seems to not trigger the re-rendering of the local video on Chrome;
|
||||
* could be due to a plan B vs unified plan issue. Therefore, we use the legacy
|
||||
* method defined in conference.js that manually takes care of updating the local
|
||||
* video as well.
|
||||
*/
|
||||
await APP.conference.useVideoStream(null);
|
||||
if (mediaType === MEDIA_TYPE.SCREENSHARE
|
||||
&& getMultipleVideoSendingSupportFeatureFlag(state)
|
||||
&& !muted) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const targetFacingMode = currentFacingMode === CAMERA_FACING_MODE.USER
|
||||
? CAMERA_FACING_MODE.ENVIRONMENT
|
||||
: CAMERA_FACING_MODE.USER;
|
||||
if (localTrack) {
|
||||
// The `jitsiTrack` property will have a value only for a localTrack for which `getUserMedia` has
|
||||
// already completed. If there's no `jitsiTrack`, then the `muted` state will be applied once the
|
||||
// `jitsiTrack` is created.
|
||||
const { jitsiTrack } = localTrack;
|
||||
const isAudioOnly = (mediaType === MEDIA_TYPE.VIDEO && authority === VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY)
|
||||
|| (mediaType === MEDIA_TYPE.SCREENSHARE && authority === SCREENSHARE_MUTISM_AUTHORITY.AUDIO_ONLY);
|
||||
|
||||
// Update the flipX value so the environment facing camera is not flipped, before the new track is created.
|
||||
dispatch(updateSettings({ localFlipX: targetFacingMode === CAMERA_FACING_MODE.USER }));
|
||||
// Screenshare cannot be unmuted using the video mute button unless it is muted by audioOnly in
|
||||
// the legacy screensharing mode.
|
||||
if (jitsiTrack && (
|
||||
jitsiTrack.videoType !== 'desktop' || isAudioOnly || getMultipleVideoSendingSupportFeatureFlag(state))
|
||||
) {
|
||||
return setTrackMuted(jitsiTrack, muted, state).catch(
|
||||
() => dispatch(trackMuteUnmuteFailed(localTrack, muted)));
|
||||
}
|
||||
} else if (!muted && ensureTrack && (typeof APP === 'undefined' || isPrejoinPageVisible(state))) {
|
||||
// FIXME: This only runs on mobile now because web has its own way of
|
||||
// creating local tracks. Adjust the check once they are unified.
|
||||
return dispatch(createLocalTracksA({ devices: [ mediaType ] }));
|
||||
}
|
||||
|
||||
const newVideoTrack = await createLocalTrack('video', null, null, { facingMode: targetFacingMode });
|
||||
|
||||
// FIXME: See above.
|
||||
await APP.conference.useVideoStream(newVideoTrack);
|
||||
return Promise.resolve();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { setPictureInPictureEnabled } from '../../mobile/picture-in-picture/functions';
|
||||
import { setAudioOnly } from '../audio-only/actions';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
|
||||
import { destroyLocalDesktopTrackIfExists, replaceLocalTrack } from './actions.any';
|
||||
import { getLocalVideoTrack, isLocalVideoTrackDesktop } from './functions';
|
||||
import { getLocalVideoTrack, isLocalVideoTrackDesktop } from './functions.native';
|
||||
import { TrackOperationType } from './types';
|
||||
/* eslint-enable lines-around-comment */
|
||||
|
||||
export * from './actions.any';
|
||||
@@ -72,3 +72,22 @@ function _startScreenSharing(dispatch: Function, state: IReduxState) {
|
||||
setPictureInPictureEnabled(true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes a track operation.
|
||||
*
|
||||
* NOTE: This is dummy implementation for mobile. Currently we are not sure if we need to chain the track operations.
|
||||
* For now we are just executing the passed operation without chaining it.
|
||||
*
|
||||
* @param {TrackOperationType} type - The type of the operation ('audio', 'video' or 'audio-video').
|
||||
* @param {Function} operation - The operation.
|
||||
* @returns {{
|
||||
* type: SET_TRACK_OPERATIONS_PROMISE,
|
||||
* audioTrackOperationsPromise: Promise<void>,
|
||||
* videoTrackOperationsPromise: Promise<void>
|
||||
* }}
|
||||
*/
|
||||
export function executeTrackOperation(type: TrackOperationType, operation: () => Promise<any>) {
|
||||
return () => operation();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
// @ts-ignore
|
||||
import { stopReceiver } from '../../remote-control/actions';
|
||||
// @ts-ignore
|
||||
import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen-share/actions';
|
||||
import { isAudioOnlySharing, isScreenVideoShared } from '../../screen-share/functions';
|
||||
// @ts-ignore
|
||||
@@ -18,10 +17,13 @@ import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEff
|
||||
import { setAudioOnly } from '../audio-only/actions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||
import { createLocalTrack } from '../lib-jitsi-meet/functions.any';
|
||||
import { setScreenshareMuted } from '../media/actions';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
import { CAMERA_FACING_MODE, MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
import { updateSettings } from '../settings/actions';
|
||||
/* eslint-enable lines-around-comment */
|
||||
|
||||
import { SET_TRACK_OPERATIONS_PROMISE } from './actionTypes';
|
||||
import {
|
||||
addLocalTrack,
|
||||
replaceLocalTrack
|
||||
@@ -29,9 +31,10 @@ import {
|
||||
import {
|
||||
createLocalTracksF,
|
||||
getLocalDesktopTrack,
|
||||
getLocalJitsiAudioTrack
|
||||
getLocalJitsiAudioTrack,
|
||||
getLocalVideoTrack
|
||||
} from './functions';
|
||||
import { IShareOptions, IToggleScreenSharingOptions } from './types';
|
||||
import { IShareOptions, IToggleScreenSharingOptions, TrackOperationType } from './types';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
@@ -149,8 +152,6 @@ async function _toggleScreenSharing(
|
||||
const audioOnlySharing = isAudioOnlySharing(state);
|
||||
const screenSharing = isScreenVideoShared(state);
|
||||
const conference = getCurrentConference(state);
|
||||
const localAudio = getLocalJitsiAudioTrack(state);
|
||||
const localScreenshare = getLocalDesktopTrack(state['features/base/tracks']);
|
||||
|
||||
// Toggle screenshare or audio-only share if the new state is not passed. Happens in the following two cases.
|
||||
// 1. ShareAudioDialog passes undefined when the user hits continue in the share audio demo modal.
|
||||
@@ -200,11 +201,16 @@ async function _toggleScreenSharing(
|
||||
throw new Error(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK);
|
||||
}
|
||||
} else if (desktopVideoTrack) {
|
||||
if (localScreenshare) {
|
||||
await dispatch(replaceLocalTrack(localScreenshare.jitsiTrack, desktopVideoTrack, conference));
|
||||
} else {
|
||||
await dispatch(addLocalTrack(desktopVideoTrack));
|
||||
}
|
||||
await dispatch(executeTrackOperation(TrackOperationType.Video, async () => {
|
||||
const localScreenshare = getLocalDesktopTrack(getState()['features/base/tracks']);
|
||||
|
||||
if (localScreenshare) {
|
||||
await dispatch(replaceLocalTrack(localScreenshare.jitsiTrack, desktopVideoTrack, conference));
|
||||
} else {
|
||||
await dispatch(addLocalTrack(desktopVideoTrack));
|
||||
}
|
||||
}));
|
||||
|
||||
if (isScreenshotCaptureEnabled(state, false, true)) {
|
||||
dispatch(toggleScreenshotCaptureSummary(true));
|
||||
}
|
||||
@@ -217,15 +223,23 @@ async function _toggleScreenSharing(
|
||||
// Noise suppression doesn't work with desktop audio because we can't chain track effects yet, disable it
|
||||
// first. We need to to wait for the effect to clear first or it might interfere with the audio mixer.
|
||||
await dispatch(setNoiseSuppressionEnabled(false));
|
||||
_maybeApplyAudioMixerEffect(desktopAudioTrack, state);
|
||||
dispatch(setScreenshareAudioTrack(desktopAudioTrack));
|
||||
|
||||
// Handle the case where screen share was stopped from the browsers 'screen share in progress' window.
|
||||
if (audioOnly) {
|
||||
desktopAudioTrack?.on(
|
||||
JitsiTrackEvents.LOCAL_TRACK_STOPPED,
|
||||
() => dispatch(toggleScreensharing(undefined, true)));
|
||||
}
|
||||
dispatch(executeTrackOperation(TrackOperationType.Audio,
|
||||
() => {
|
||||
const result = _maybeApplyAudioMixerEffect(desktopAudioTrack, state);
|
||||
|
||||
dispatch(setScreenshareAudioTrack(desktopAudioTrack));
|
||||
|
||||
// Handle the case where screen share was stopped from the browsers 'screen share in progress'
|
||||
// window.
|
||||
if (audioOnly) {
|
||||
desktopAudioTrack?.on(
|
||||
JitsiTrackEvents.LOCAL_TRACK_STOPPED,
|
||||
() => dispatch(toggleScreensharing(undefined, true)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}));
|
||||
}
|
||||
|
||||
// Disable audio-only or best performance mode if the user starts screensharing. This doesn't apply to
|
||||
@@ -242,16 +256,25 @@ async function _toggleScreenSharing(
|
||||
|
||||
dispatch(toggleScreenshotCaptureSummary(false));
|
||||
|
||||
// Mute the desktop track instead of removing it from the conference since we don't want the client to signal
|
||||
// a source-remove to the remote peer for the screenshare track. Later when screenshare is enabled again, the
|
||||
// same sender will be re-used without the need for signaling a new ssrc through source-add.
|
||||
dispatch(setScreenshareMuted(true));
|
||||
await dispatch(executeTrackOperation(TrackOperationType.Video, () => {
|
||||
// Mute the desktop track instead of removing it from the conference since we don't want the client to
|
||||
// signal a source-remove to the remote peer for the screenshare track. Later when screenshare is enabled
|
||||
// again, the same sender will be re-used without the need for signaling a new ssrc through source-add.
|
||||
dispatch(setScreenshareMuted(true));
|
||||
|
||||
return Promise.resolve();
|
||||
}));
|
||||
|
||||
if (desktopAudioTrack) {
|
||||
if (localAudio) {
|
||||
localAudio.setEffect(undefined);
|
||||
} else {
|
||||
await conference.replaceTrack(desktopAudioTrack, null);
|
||||
}
|
||||
await dispatch(executeTrackOperation(TrackOperationType.Audio, async () => {
|
||||
const localAudio = getLocalJitsiAudioTrack(state);
|
||||
|
||||
if (localAudio) {
|
||||
await localAudio.setEffect(undefined);
|
||||
} else {
|
||||
await conference.replaceTrack(desktopAudioTrack, null);
|
||||
}
|
||||
}));
|
||||
desktopAudioTrack.dispose();
|
||||
dispatch(setScreenshareAudioTrack(null));
|
||||
}
|
||||
@@ -264,3 +287,101 @@ async function _toggleScreenSharing(
|
||||
APP.API.notifyScreenSharingStatusChanged(enable, screensharingDetails);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes a track operation.
|
||||
*
|
||||
* @param {TrackOperationType} type - The type of the operation ('audio', 'video' or 'audio-video').
|
||||
* @param {Function} operation - The operation.
|
||||
* @returns {{
|
||||
* type: SET_TRACK_OPERATIONS_PROMISE,
|
||||
* audioTrackOperationsPromise: Promise<void>,
|
||||
* videoTrackOperationsPromise: Promise<void>
|
||||
* }}
|
||||
*/
|
||||
export function executeTrackOperation(type: TrackOperationType, operation: () => Promise<any>) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const {
|
||||
audioTrackOperationsPromise,
|
||||
videoTrackOperationsPromise
|
||||
} = getState()['features/base/track-operations'];
|
||||
|
||||
switch (type) {
|
||||
case TrackOperationType.Audio: {
|
||||
const promise = audioTrackOperationsPromise.then(operation, operation);
|
||||
|
||||
dispatch({
|
||||
type: SET_TRACK_OPERATIONS_PROMISE,
|
||||
audioTrackOperationsPromise: promise
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
case TrackOperationType.Video: {
|
||||
const promise = videoTrackOperationsPromise.then(operation, operation);
|
||||
|
||||
dispatch({
|
||||
type: SET_TRACK_OPERATIONS_PROMISE,
|
||||
videoTrackOperationsPromise: promise
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
case TrackOperationType.AudioVideo: {
|
||||
const promise = Promise.allSettled([
|
||||
audioTrackOperationsPromise,
|
||||
videoTrackOperationsPromise
|
||||
]).then(operation);
|
||||
|
||||
dispatch({
|
||||
type: SET_TRACK_OPERATIONS_PROMISE,
|
||||
audioTrackOperationsPromise: promise,
|
||||
videoTrackOperationsPromise: promise
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
default: {
|
||||
const unexpectedType: never = type;
|
||||
|
||||
return Promise.reject(new Error(`Unexpected track operation type: ${unexpectedType}`));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the facingMode constraint on the video stream.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function toggleCamera() {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) =>
|
||||
dispatch(executeTrackOperation(TrackOperationType.Video, () =>
|
||||
|
||||
/**
|
||||
* FIXME: Ideally, we should be dispatching {@code replaceLocalTrack} here,
|
||||
* but it seems to not trigger the re-rendering of the local video on Chrome;
|
||||
* could be due to a plan B vs unified plan issue. Therefore, we use the legacy
|
||||
* method defined in conference.js that manually takes care of updating the local
|
||||
* video as well.
|
||||
*/
|
||||
APP.conference.useVideoStream(null).then(() => {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localVideoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||
const currentFacingMode = localVideoTrack.getCameraFacingMode();
|
||||
|
||||
const targetFacingMode = currentFacingMode === CAMERA_FACING_MODE.USER
|
||||
? CAMERA_FACING_MODE.ENVIRONMENT
|
||||
: CAMERA_FACING_MODE.USER;
|
||||
|
||||
// Update the flipX value so the environment facing camera is not flipped, before the new track is
|
||||
// created.
|
||||
dispatch(updateSettings({ localFlipX: targetFacingMode === CAMERA_FACING_MODE.USER }));
|
||||
|
||||
return createLocalTrack('video', null, null, { facingMode: targetFacingMode });
|
||||
})
|
||||
.then((newVideoTrack: any) => APP.conference.useVideoStream(newVideoTrack))));
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ import {
|
||||
getUserSelectedMicDeviceId
|
||||
} from '../settings/functions.web';
|
||||
|
||||
import { executeTrackOperation } from './actions.web';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import loadEffects from './loadEffects';
|
||||
import logger from './logger';
|
||||
import { ITrackOptions } from './types';
|
||||
import { ITrackOptions, TrackOperationType } from './types';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
@@ -107,14 +109,15 @@ export function createPrejoinTracks() {
|
||||
const initialDevices = [ 'audio' ];
|
||||
const requestedAudio = true;
|
||||
let requestedVideo = false;
|
||||
const { startAudioOnly, startWithAudioMuted, startWithVideoMuted } = APP.store.getState()['features/base/settings'];
|
||||
const { dispatch, getState } = APP.store;
|
||||
const { startAudioOnly, startWithAudioMuted, startWithVideoMuted } = getState()['features/base/settings'];
|
||||
|
||||
// Always get a handle on the audio input device so that we have statistics even if the user joins the
|
||||
// conference muted. Previous implementation would only acquire the handle when the user first unmuted,
|
||||
// which would results in statistics ( such as "No audio input" or "Are you trying to speak?") being available
|
||||
// only after that point.
|
||||
if (startWithAudioMuted) {
|
||||
APP.store.dispatch(setAudioMuted(true));
|
||||
dispatch(executeTrackOperation(TrackOperationType.Audio, () => dispatch(setAudioMuted(true))));
|
||||
}
|
||||
|
||||
if (!startWithVideoMuted && !startAudioOnly) {
|
||||
@@ -128,10 +131,11 @@ export function createPrejoinTracks() {
|
||||
// Resolve with no tracks
|
||||
tryCreateLocalTracks = Promise.resolve([]);
|
||||
} else {
|
||||
tryCreateLocalTracks = createLocalTracksF({
|
||||
devices: initialDevices,
|
||||
firePermissionPromptIsShownEvent: true
|
||||
}, APP.store)
|
||||
tryCreateLocalTracks = dispatch(executeTrackOperation(TrackOperationType.AudioVideo, () =>
|
||||
createLocalTracksF({
|
||||
devices: initialDevices,
|
||||
firePermissionPromptIsShownEvent: true
|
||||
}, APP.store)
|
||||
.catch((err: Error) => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
|
||||
@@ -177,7 +181,7 @@ export function createPrejoinTracks() {
|
||||
errors.videoOnlyError = err;
|
||||
|
||||
return [];
|
||||
});
|
||||
})));
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,42 +1,17 @@
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { _RESET_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { getMultipleVideoSendingSupportFeatureFlag } from '../config/functions.any';
|
||||
import {
|
||||
SET_AUDIO_MUTED,
|
||||
SET_CAMERA_FACING_MODE,
|
||||
SET_SCREENSHARE_MUTED,
|
||||
SET_VIDEO_MUTED,
|
||||
TOGGLE_CAMERA_FACING_MODE
|
||||
} from '../media/actionTypes';
|
||||
import { toggleCameraFacingMode } from '../media/actions';
|
||||
import {
|
||||
CAMERA_FACING_MODE,
|
||||
MEDIA_TYPE,
|
||||
MediaType,
|
||||
SCREENSHARE_MUTISM_AUTHORITY,
|
||||
VIDEO_MUTISM_AUTHORITY
|
||||
MediaType
|
||||
} from '../media/constants';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||
|
||||
import {
|
||||
TRACK_UPDATED
|
||||
} from './actionTypes';
|
||||
import {
|
||||
createLocalTracksA,
|
||||
destroyLocalTracks,
|
||||
trackMuteUnmuteFailed,
|
||||
trackRemoved
|
||||
} from './actions';
|
||||
import {
|
||||
getLocalTrack,
|
||||
isUserInteractionRequiredForUnmute,
|
||||
setTrackMuted
|
||||
} from './functions';
|
||||
import { TRACK_UPDATED } from './actionTypes';
|
||||
import { getLocalTrack } from './functions';
|
||||
import './subscriber';
|
||||
|
||||
/**
|
||||
@@ -49,15 +24,6 @@ import './subscriber';
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case SET_AUDIO_MUTED:
|
||||
if (!action.muted
|
||||
&& isUserInteractionRequiredForUnmute(store.getState())) {
|
||||
return;
|
||||
}
|
||||
|
||||
_setMuted(store, action, MEDIA_TYPE.AUDIO);
|
||||
break;
|
||||
|
||||
case SET_CAMERA_FACING_MODE: {
|
||||
// XXX The camera facing mode of a MediaStreamTrack can be specified
|
||||
// only at initialization time and then it can only be toggled. So in
|
||||
@@ -78,19 +44,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_SCREENSHARE_MUTED:
|
||||
_setMuted(store, action, action.mediaType);
|
||||
break;
|
||||
|
||||
case SET_VIDEO_MUTED:
|
||||
if (!action.muted
|
||||
&& isUserInteractionRequiredForUnmute(store.getState())) {
|
||||
return;
|
||||
}
|
||||
|
||||
_setMuted(store, action, action.mediaType);
|
||||
break;
|
||||
|
||||
case TOGGLE_CAMERA_FACING_MODE: {
|
||||
const localTrack = _getLocalTrack(store, MEDIA_TYPE.VIDEO);
|
||||
let jitsiTrack;
|
||||
@@ -121,31 +74,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Set up state change listener to perform maintenance tasks when the conference
|
||||
* is left or failed, remove all tracks from the store.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
state => getCurrentConference(state),
|
||||
(conference, { dispatch, getState }, prevConference) => {
|
||||
const { authRequired, error } = getState()['features/base/conference'];
|
||||
|
||||
// conference keep flipping while we are authenticating, skip clearing while we are in that process
|
||||
if (prevConference && !conference && !authRequired && !error) {
|
||||
|
||||
// Clear all tracks.
|
||||
const remoteTracks = getState()['features/base/tracks'].filter(t => !t.local);
|
||||
|
||||
batch(() => {
|
||||
dispatch(destroyLocalTracks());
|
||||
for (const track of remoteTracks) {
|
||||
dispatch(trackRemoved(track.jitsiTrack));
|
||||
}
|
||||
dispatch({ type: _RESET_BREAKOUT_ROOMS });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets the local track associated with a specific {@code MEDIA_TYPE} in a
|
||||
* specific redux store.
|
||||
@@ -173,48 +101,3 @@ function _getLocalTrack(
|
||||
mediaType,
|
||||
includePending));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes or unmutes a local track with a specific media type.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified action is
|
||||
* dispatched.
|
||||
* @param {Action} action - The redux action dispatched in the specified store.
|
||||
* @param {MEDIA_TYPE} mediaType - The {@link MEDIA_TYPE} of the local track
|
||||
* which is being muted or unmuted.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
async function _setMuted(store: IStore, { ensureTrack, authority, muted }: {
|
||||
authority: number; ensureTrack: boolean; muted: boolean; }, mediaType: MediaType) {
|
||||
const { dispatch, getState } = store;
|
||||
const localTrack = _getLocalTrack(store, mediaType, /* includePending */ true);
|
||||
const state = getState();
|
||||
|
||||
if (mediaType === MEDIA_TYPE.SCREENSHARE
|
||||
&& getMultipleVideoSendingSupportFeatureFlag(state)
|
||||
&& !muted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (localTrack) {
|
||||
// The `jitsiTrack` property will have a value only for a localTrack for which `getUserMedia` has already
|
||||
// completed. If there's no `jitsiTrack`, then the `muted` state will be applied once the `jitsiTrack` is
|
||||
// created.
|
||||
const { jitsiTrack } = localTrack;
|
||||
const isAudioOnly = (mediaType === MEDIA_TYPE.VIDEO && authority === VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY)
|
||||
|| (mediaType === MEDIA_TYPE.SCREENSHARE && authority === SCREENSHARE_MUTISM_AUTHORITY.AUDIO_ONLY);
|
||||
|
||||
// Screenshare cannot be unmuted using the video mute button unless it is muted by audioOnly in the legacy
|
||||
// screensharing mode.
|
||||
if (jitsiTrack && (
|
||||
jitsiTrack.videoType !== 'desktop' || isAudioOnly || getMultipleVideoSendingSupportFeatureFlag(state))
|
||||
) {
|
||||
setTrackMuted(jitsiTrack, muted, state).catch(() => dispatch(trackMuteUnmuteFailed(localTrack, muted)));
|
||||
}
|
||||
} else if (!muted && ensureTrack && (typeof APP === 'undefined' || isPrejoinPageVisible(state))) {
|
||||
// FIXME: This only runs on mobile now because web has its own way of
|
||||
// creating local tracks. Adjust the check once they are unified.
|
||||
dispatch(createLocalTracksA({ devices: [ mediaType ] }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
TRACK_UPDATED
|
||||
} from './actionTypes';
|
||||
import {
|
||||
executeTrackOperation,
|
||||
showNoDataFromSourceVideoError,
|
||||
toggleScreensharing,
|
||||
trackNoDataFromSourceNotificationInfoChanged
|
||||
@@ -25,7 +26,7 @@ import {
|
||||
import {
|
||||
getTrackByJitsiTrack
|
||||
} from './functions.web';
|
||||
import { ITrack } from './types';
|
||||
import { ITrack, TrackOperationType } from './types';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
@@ -67,9 +68,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const { jitsiTrack } = action.track;
|
||||
const muted = action.wasMuted;
|
||||
const isVideoTrack = jitsiTrack.getType() !== MEDIA_TYPE.AUDIO;
|
||||
const { dispatch } = store;
|
||||
|
||||
if (isVideoTrack && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
||||
store.dispatch(setScreenshareMuted(!muted));
|
||||
dispatch(executeTrackOperation(TrackOperationType.Video, () => dispatch(setScreenshareMuted(!muted))));
|
||||
} else if (isVideoTrack) {
|
||||
APP.conference.setVideoMuteStatus();
|
||||
} else {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { set } from '../redux/functions';
|
||||
|
||||
import {
|
||||
SET_NO_SRC_DATA_NOTIFICATION_UID,
|
||||
SET_TRACK_OPERATIONS_PROMISE,
|
||||
TRACK_ADDED,
|
||||
TRACK_CREATE_CANCELED,
|
||||
TRACK_CREATE_ERROR,
|
||||
@@ -152,3 +153,31 @@ ReducerRegistry.register<INoSrcDataState>('features/base/no-src-data', (state =
|
||||
}
|
||||
});
|
||||
|
||||
export interface ITrackOperations {
|
||||
audioTrackOperationsPromise: Promise<void>;
|
||||
videoTrackOperationsPromise: Promise<void>;
|
||||
}
|
||||
|
||||
const DEFAULT_TRACK_OPERATIONS_STATE = {
|
||||
audioTrackOperationsPromise: Promise.resolve(),
|
||||
videoTrackOperationsPromise: Promise.resolve()
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for actions that mutate the no-src-data state, like the current notification id.
|
||||
*/
|
||||
ReducerRegistry.register<ITrackOperations>(
|
||||
'features/base/track-operations',
|
||||
(state = DEFAULT_TRACK_OPERATIONS_STATE, action): ITrackOperations => {
|
||||
switch (action.type) {
|
||||
case SET_TRACK_OPERATIONS_PROMISE:
|
||||
return {
|
||||
...state,
|
||||
audioTrackOperationsPromise: action.audioTrackOperationsPromise || state.audioTrackOperationsPromise,
|
||||
videoTrackOperationsPromise: action.videoTrackOperationsPromise || state.videoTrackOperationsPromise
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import _ from 'lodash';
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { _RESET_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { MEDIA_TYPE } from '../media/constants';
|
||||
import { getScreenshareParticipantIds } from '../participants/functions';
|
||||
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||
|
||||
import { destroyLocalTracks, trackRemoved } from './actions.any';
|
||||
import { isLocalTrackMuted } from './functions';
|
||||
|
||||
/**
|
||||
@@ -38,3 +42,28 @@ StateListenerRegistry.register(
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Set up state change listener to perform maintenance tasks when the conference
|
||||
* is left or failed, remove all tracks from the store.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
state => getCurrentConference(state),
|
||||
(conference, { dispatch, getState }, prevConference) => {
|
||||
const { authRequired, error } = getState()['features/base/conference'];
|
||||
|
||||
// conference keep flipping while we are authenticating, skip clearing while we are in that process
|
||||
if (prevConference && !conference && !authRequired && !error) {
|
||||
|
||||
// Clear all tracks.
|
||||
const remoteTracks = getState()['features/base/tracks'].filter(t => !t.local);
|
||||
|
||||
batch(() => {
|
||||
dispatch(destroyLocalTracks());
|
||||
for (const track of remoteTracks) {
|
||||
dispatch(trackRemoved(track.jitsiTrack));
|
||||
}
|
||||
dispatch({ type: _RESET_BREAKOUT_ROOMS });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -72,3 +72,9 @@ export interface IShareOptions {
|
||||
desktopSharingSources?: string[];
|
||||
desktopStream?: any;
|
||||
}
|
||||
|
||||
export enum TrackOperationType {
|
||||
Audio = 'audio',
|
||||
AudioVideo = 'audio-video',
|
||||
Video = 'video'
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// @ts-ignore
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React from 'react';
|
||||
import { TouchableRipple } from 'react-native-paper';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import styles from '../../../react/components/native/styles';
|
||||
import { IIconButtonProps } from '../../../react/types';
|
||||
|
||||
@@ -27,6 +27,7 @@ const useStyles = makeStyles()(theme => {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
zIndex: 301,
|
||||
animation: `${keyframes`
|
||||
0% {
|
||||
opacity: 0.4;
|
||||
|
||||
@@ -3,6 +3,10 @@ import React, { Component, ComponentType } from 'react';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { IReactionEmojiProps } from '../../../../reactions/constants';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { JitsiPortal } from '../../../../toolbox/components/web';
|
||||
import { showOverflowDrawer } from '../../../../toolbox/functions.web';
|
||||
import { connect } from '../../../redux/functions';
|
||||
|
||||
import DialogTransition from './DialogTransition';
|
||||
@@ -24,6 +28,11 @@ interface IProps {
|
||||
*/
|
||||
_isNewDialog: boolean;
|
||||
|
||||
/**
|
||||
* Whether the overflow drawer should be used.
|
||||
*/
|
||||
_overflowDrawer: boolean;
|
||||
|
||||
/**
|
||||
* Array of reactions to be displayed.
|
||||
*/
|
||||
@@ -69,7 +78,9 @@ class DialogContainer extends Component<IProps> {
|
||||
render() {
|
||||
return this.props._isNewDialog ? (
|
||||
<DialogTransition>
|
||||
{this._renderDialogContent()}
|
||||
{this.props._overflowDrawer
|
||||
? <JitsiPortal>{this._renderDialogContent()}</JitsiPortal>
|
||||
: this._renderDialogContent() }
|
||||
</DialogTransition>
|
||||
) : (
|
||||
<ModalTransition>
|
||||
@@ -90,11 +101,13 @@ class DialogContainer extends Component<IProps> {
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
const stateFeaturesBaseDialog = state['features/base/dialog'];
|
||||
const { reducedUI } = state['features/base/responsive-ui'];
|
||||
const overflowDrawer = showOverflowDrawer(state);
|
||||
|
||||
return {
|
||||
_component: stateFeaturesBaseDialog.component,
|
||||
_componentProps: stateFeaturesBaseDialog.componentProps,
|
||||
_isNewDialog: stateFeaturesBaseDialog.isNewDialog,
|
||||
_overflowDrawer: overflowDrawer,
|
||||
_reducedUI: reducedUI
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
REFRESH_CALENDAR,
|
||||
SET_CALENDAR_AUTHORIZATION,
|
||||
@@ -19,8 +17,7 @@ import {
|
||||
* isInteractive: boolean
|
||||
* }}
|
||||
*/
|
||||
export function refreshCalendar(
|
||||
forcePermission: boolean = false, isInteractive: boolean = true) {
|
||||
export function refreshCalendar(forcePermission = false, isInteractive = true) {
|
||||
return {
|
||||
type: REFRESH_CALENDAR,
|
||||
forcePermission,
|
||||
@@ -39,7 +36,7 @@ export function refreshCalendar(
|
||||
* authorization: ?string
|
||||
* }}
|
||||
*/
|
||||
export function setCalendarAuthorization(authorization: ?string) {
|
||||
export function setCalendarAuthorization(authorization?: string) {
|
||||
return {
|
||||
type: SET_CALENDAR_AUTHORIZATION,
|
||||
authorization
|
||||
@@ -1,13 +1,15 @@
|
||||
// @flow
|
||||
// @ts-expect-error
|
||||
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { getDefaultURL } from '../app/functions';
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { IStore } from '../app/types';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
|
||||
import { refreshCalendar } from './actions';
|
||||
import {
|
||||
UpdateCalendarEventDialog
|
||||
|
||||
// @ts-ignore
|
||||
} from './components';
|
||||
import { addLinkToCalendarEntry } from './functions.native';
|
||||
|
||||
@@ -35,11 +37,13 @@ export function openUpdateCalendarEventDialog(eventId: string) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function updateCalendarEvent(eventId: string) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const defaultUrl = getDefaultURL(getState);
|
||||
const roomName = generateRoomWithoutSeparator();
|
||||
|
||||
addLinkToCalendarEntry(getState(), eventId, `${defaultUrl}/${roomName}`)
|
||||
|
||||
// @ts-ignore
|
||||
.finally(() => {
|
||||
dispatch(refreshCalendar(false, false));
|
||||
});
|
||||
@@ -1,9 +1,11 @@
|
||||
// @flow
|
||||
|
||||
// @ts-expect-error
|
||||
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { createCalendarConnectedEvent, sendAnalytics } from '../analytics';
|
||||
import { createCalendarConnectedEvent } from '../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../analytics/functions';
|
||||
import { IStore } from '../app/types';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { loadGoogleAPI } from '../google-api';
|
||||
|
||||
import {
|
||||
@@ -14,8 +16,8 @@ import {
|
||||
SET_CALENDAR_PROFILE_EMAIL,
|
||||
SET_LOADING_CALENDAR_EVENTS
|
||||
} from './actionTypes';
|
||||
import { refreshCalendar, setCalendarEvents } from './actions';
|
||||
import { _getCalendarIntegration, isCalendarEnabled } from './functions';
|
||||
import { refreshCalendar, setCalendarEvents } from './actions.web';
|
||||
import { _getCalendarIntegration, isCalendarEnabled } from './functions.web';
|
||||
import logger from './logger';
|
||||
|
||||
export * from './actions.any';
|
||||
@@ -26,8 +28,8 @@ export * from './actions.any';
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function bootstrapCalendarIntegration(): Function {
|
||||
return (dispatch, getState) => {
|
||||
export function bootstrapCalendarIntegration() {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
|
||||
if (!isCalendarEnabled(state)) {
|
||||
@@ -63,7 +65,7 @@ export function bootstrapCalendarIntegration(): Function {
|
||||
}
|
||||
|
||||
return dispatch(integrationToLoad._isSignedIn())
|
||||
.then(signedIn => {
|
||||
.then((signedIn: boolean) => {
|
||||
if (signedIn) {
|
||||
dispatch(setIntegrationReady(integrationType));
|
||||
dispatch(updateProfile(integrationType));
|
||||
@@ -114,7 +116,7 @@ export function openUpdateCalendarEventDialog(
|
||||
* msAuthState: Object
|
||||
* }}
|
||||
*/
|
||||
export function setCalendarAPIAuthState(newState: ?Object) {
|
||||
export function setCalendarAPIAuthState(newState?: Object) {
|
||||
return {
|
||||
type: SET_CALENDAR_AUTH_STATE,
|
||||
msAuthState: newState
|
||||
@@ -130,7 +132,7 @@ export function setCalendarAPIAuthState(newState: ?Object) {
|
||||
* error: Object
|
||||
* }}
|
||||
*/
|
||||
export function setCalendarError(error: ?Object) {
|
||||
export function setCalendarError(error?: Object) {
|
||||
return {
|
||||
type: SET_CALENDAR_ERROR,
|
||||
error
|
||||
@@ -146,7 +148,7 @@ export function setCalendarError(error: ?Object) {
|
||||
* email: string
|
||||
* }}
|
||||
*/
|
||||
export function setCalendarProfileEmail(newEmail: ?string) {
|
||||
export function setCalendarProfileEmail(newEmail?: string) {
|
||||
return {
|
||||
type: SET_CALENDAR_PROFILE_EMAIL,
|
||||
email: newEmail
|
||||
@@ -196,8 +198,8 @@ export function setIntegrationReady(integrationType: string) {
|
||||
* signed into.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function signIn(calendarType: string): Function {
|
||||
return (dispatch: Dispatch<any>) => {
|
||||
export function signIn(calendarType: string) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
const integration = _getCalendarIntegration(calendarType);
|
||||
|
||||
if (!integration) {
|
||||
@@ -210,7 +212,7 @@ export function signIn(calendarType: string): Function {
|
||||
.then(() => dispatch(updateProfile(calendarType)))
|
||||
.then(() => dispatch(refreshCalendar()))
|
||||
.then(() => sendAnalytics(createCalendarConnectedEvent()))
|
||||
.catch(error => {
|
||||
.catch((error: any) => {
|
||||
logger.error(
|
||||
'Error occurred while signing into calendar integration',
|
||||
error);
|
||||
@@ -228,10 +230,10 @@ export function signIn(calendarType: string): Function {
|
||||
* @param {string} calendarId - The id of the calendar to use.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function updateCalendarEvent(id: string, calendarId: string): Function {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
export function updateCalendarEvent(id: string, calendarId: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
|
||||
const { integrationType } = getState()['features/calendar-sync'];
|
||||
const { integrationType = '' } = getState()['features/calendar-sync'];
|
||||
const integration = _getCalendarIntegration(integrationType);
|
||||
|
||||
if (!integration) {
|
||||
@@ -240,7 +242,7 @@ export function updateCalendarEvent(id: string, calendarId: string): Function {
|
||||
|
||||
const { locationURL } = getState()['features/base/connection'];
|
||||
const newRoomName = generateRoomWithoutSeparator();
|
||||
let href = locationURL.href;
|
||||
let href = locationURL?.href ?? '';
|
||||
|
||||
href.endsWith('/') || (href += '/');
|
||||
|
||||
@@ -275,8 +277,8 @@ export function updateCalendarEvent(id: string, calendarId: string): Function {
|
||||
* should be updated.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function updateProfile(calendarType: string): Function {
|
||||
return (dispatch: Dispatch<any>) => {
|
||||
export function updateProfile(calendarType: string) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
const integration = _getCalendarIntegration(calendarType);
|
||||
|
||||
if (!integration) {
|
||||
@@ -284,7 +286,7 @@ export function updateProfile(calendarType: string): Function {
|
||||
}
|
||||
|
||||
return dispatch(integration.getCurrentEmail())
|
||||
.then(email => {
|
||||
.then((email: string) => {
|
||||
dispatch(setCalendarProfileEmail(email));
|
||||
});
|
||||
};
|
||||
@@ -1,8 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import md5 from 'js-md5';
|
||||
|
||||
import { APP_LINK_SCHEME, parseURIString } from '../base/util';
|
||||
import { APP_LINK_SCHEME, parseURIString } from '../base/util/uri';
|
||||
|
||||
import { setCalendarEvents } from './actions';
|
||||
import { MAX_LIST_LENGTH } from './constants';
|
||||
@@ -16,7 +14,8 @@ const ALLDAY_EVENT_LENGTH = 23 * 60 * 60 * 1000;
|
||||
* @param {Object} entry - The calendar entry.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function _isDisplayableCalendarEntry(entry) {
|
||||
function _isDisplayableCalendarEntry(entry: { allDay: boolean; attendees: Object[];
|
||||
endDate: number; startDate: number; }) {
|
||||
// Entries are displayable if:
|
||||
// - Ends in the future (future or ongoing events)
|
||||
// - Is not an all day event and there is only one attendee (these events
|
||||
@@ -45,7 +44,8 @@ export function _updateCalendarEntries(events: Array<Object>) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-invalid-this
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-invalid-this
|
||||
const { dispatch, getState } = this;
|
||||
const knownDomains = getState()['features/base/known-domains'];
|
||||
const entryMap = new Map();
|
||||
@@ -106,7 +106,7 @@ export function _updateCalendarEntries(events: Array<Object>) {
|
||||
* @param {string} negativePattern - The negative pattern.
|
||||
* @returns {string}
|
||||
*/
|
||||
function _checkPattern(str, positivePattern, negativePattern) {
|
||||
function _checkPattern(str: string, positivePattern: string, negativePattern: string) {
|
||||
const positiveRegExp = new RegExp(positivePattern, 'gi');
|
||||
let positiveMatch = positiveRegExp.exec(str);
|
||||
|
||||
@@ -129,7 +129,7 @@ function _checkPattern(str, positivePattern, negativePattern) {
|
||||
* @private
|
||||
* @returns {CalendarEntry}
|
||||
*/
|
||||
function _parseCalendarEntry(event, knownDomains) {
|
||||
function _parseCalendarEntry(event: any, knownDomains: string[]) {
|
||||
if (event) {
|
||||
const url = _getURLFromEvent(event, knownDomains);
|
||||
const startDate = Date.parse(event.startDate);
|
||||
@@ -170,7 +170,8 @@ function _parseCalendarEntry(event, knownDomains) {
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
function _getURLFromEvent(event, knownDomains) {
|
||||
function _getURLFromEvent(event: { description: string; location: string; notes: string; title: string;
|
||||
url: string; }, knownDomains: string[]) {
|
||||
const linkTerminatorPattern = '[^\\s<>$]';
|
||||
const urlRegExp
|
||||
= `http(s)?://(${knownDomains.join('|')})/${linkTerminatorPattern}+`;
|
||||
@@ -1,15 +1,17 @@
|
||||
// @flow
|
||||
|
||||
import { NativeModules, Platform } from 'react-native';
|
||||
import RNCalendarEvents from 'react-native-calendar-events';
|
||||
import type { Store } from 'redux';
|
||||
|
||||
import { CALENDAR_ENABLED, getFeatureFlag } from '../base/flags';
|
||||
import { IReduxState, IStore } from '../app/types';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { CALENDAR_ENABLED } from '../base/flags/constants';
|
||||
import { getFeatureFlag } from '../base/flags/functions';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { getShareInfoText } from '../invite';
|
||||
|
||||
import { setCalendarAuthorization } from './actions';
|
||||
import { setCalendarAuthorization } from './actions.native';
|
||||
import { FETCH_END_DAYS, FETCH_START_DAYS } from './constants';
|
||||
import { _updateCalendarEntries } from './functions';
|
||||
import { _updateCalendarEntries } from './functions.native';
|
||||
import logger from './logger';
|
||||
|
||||
export * from './functions.any';
|
||||
@@ -23,10 +25,10 @@ export * from './functions.any';
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export function addLinkToCalendarEntry(
|
||||
state: Object, id: string, link: string): Promise<any> {
|
||||
state: IReduxState, id: string, link: string): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
getShareInfoText(state, link, true).then(shareInfoText => {
|
||||
RNCalendarEvents.findEventById(id).then(event => {
|
||||
getShareInfoText(state, link, true).then((shareInfoText: string) => {
|
||||
RNCalendarEvents.findEventById(id).then((event: any) => {
|
||||
const updateText
|
||||
= event.description
|
||||
? `${event.description}\n\n${shareInfoText}`
|
||||
@@ -43,6 +45,7 @@ export function addLinkToCalendarEntry(
|
||||
})
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
RNCalendarEvents.saveEvent(event.title, updateObject)
|
||||
.then(resolve, reject);
|
||||
}, reject);
|
||||
@@ -61,7 +64,7 @@ export function addLinkToCalendarEntry(
|
||||
* @returns {boolean} If the app has enabled the calendar feature, {@code true};
|
||||
* otherwise, {@code false}.
|
||||
*/
|
||||
export function isCalendarEnabled(stateful: Function | Object) {
|
||||
export function isCalendarEnabled(stateful: IStateful) {
|
||||
const flag = getFeatureFlag(stateful, CALENDAR_ENABLED);
|
||||
|
||||
if (typeof flag !== 'undefined') {
|
||||
@@ -85,9 +88,9 @@ export function isCalendarEnabled(stateful: Function | Object) {
|
||||
* @returns {void}
|
||||
*/
|
||||
export function _fetchCalendarEntries(
|
||||
store: Store<*, *>,
|
||||
store: IStore,
|
||||
maybePromptForPermission: boolean,
|
||||
forcePermission: ?boolean) {
|
||||
forcePermission?: boolean) {
|
||||
const { dispatch, getState } = store;
|
||||
const promptForPermission
|
||||
= (maybePromptForPermission
|
||||
@@ -104,6 +107,8 @@ export function _fetchCalendarEntries(
|
||||
endDate.setDate(endDate.getDate() + FETCH_END_DAYS);
|
||||
|
||||
RNCalendarEvents.fetchAllEvents(
|
||||
|
||||
// @ts-ignore
|
||||
startDate.getTime(),
|
||||
endDate.getTime(),
|
||||
[])
|
||||
@@ -126,7 +131,7 @@ export function _fetchCalendarEntries(
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function _ensureCalendarAccess(promptForPermission, dispatch) {
|
||||
function _ensureCalendarAccess(promptForPermission: boolean | undefined, dispatch: IStore['dispatch']) {
|
||||
return new Promise((resolve, reject) => {
|
||||
RNCalendarEvents.checkPermissions()
|
||||
.then(status => {
|
||||
@@ -1,12 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import { toState } from '../base/redux';
|
||||
/* eslint-disable lines-around-comment */
|
||||
import { IStore } from '../app/types';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { toState } from '../base/redux/functions';
|
||||
|
||||
import {
|
||||
clearCalendarIntegration,
|
||||
setCalendarError,
|
||||
setLoadingCalendarEvents
|
||||
} from './actions';
|
||||
} from './actions.web';
|
||||
export * from './functions.any';
|
||||
import {
|
||||
CALENDAR_TYPE,
|
||||
@@ -14,10 +15,13 @@ import {
|
||||
FETCH_END_DAYS,
|
||||
FETCH_START_DAYS
|
||||
} from './constants';
|
||||
import { _updateCalendarEntries } from './functions';
|
||||
import { _updateCalendarEntries } from './functions.web';
|
||||
import logger from './logger';
|
||||
// @ts-ignore
|
||||
import { googleCalendarApi } from './web/googleCalendar';
|
||||
// @ts-ignore
|
||||
import { microsoftCalendarApi } from './web/microsoftCalendar';
|
||||
/* eslint-enable lines-around-comment */
|
||||
|
||||
/**
|
||||
* Determines whether the calendar feature is enabled by the web.
|
||||
@@ -27,7 +31,7 @@ import { microsoftCalendarApi } from './web/microsoftCalendar';
|
||||
* @returns {boolean} If the app has enabled the calendar feature, {@code true};
|
||||
* otherwise, {@code false}.
|
||||
*/
|
||||
export function isCalendarEnabled(stateful: Function | Object) {
|
||||
export function isCalendarEnabled(stateful: IStateful) {
|
||||
const {
|
||||
enableCalendarIntegration,
|
||||
googleApiApplicationClientID,
|
||||
@@ -37,26 +41,24 @@ export function isCalendarEnabled(stateful: Function | Object) {
|
||||
return Boolean(enableCalendarIntegration && (googleApiApplicationClientID || microsoftApiApplicationClientID));
|
||||
}
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
/**
|
||||
* Reads the user's calendar and updates the stored entries if need be.
|
||||
*
|
||||
* @param {Object} store - The redux store.
|
||||
* @param {boolean} maybePromptForPermission - Flag to tell the app if it should
|
||||
* @param {boolean} _maybePromptForPermission - Flag to tell the app if it should
|
||||
* prompt for a calendar permission if it wasn't granted yet.
|
||||
* @param {boolean|undefined} forcePermission - Whether to force to re-ask for
|
||||
* @param {boolean|undefined} _forcePermission - Whether to force to re-ask for
|
||||
* the permission or not.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
export function _fetchCalendarEntries(
|
||||
store: Object,
|
||||
maybePromptForPermission: boolean,
|
||||
forcePermission: ?boolean) {
|
||||
/* eslint-enable no-unused-vars */
|
||||
store: IStore,
|
||||
_maybePromptForPermission: boolean,
|
||||
_forcePermission?: boolean) {
|
||||
const { dispatch, getState } = store;
|
||||
|
||||
const { integrationType } = getState()['features/calendar-sync'];
|
||||
const { integrationType = '' } = getState()['features/calendar-sync'];
|
||||
const integration = _getCalendarIntegration(integrationType);
|
||||
|
||||
if (!integration) {
|
||||
@@ -69,7 +71,7 @@ export function _fetchCalendarEntries(
|
||||
|
||||
dispatch(integration.load())
|
||||
.then(() => dispatch(integration._isSignedIn()))
|
||||
.then(signedIn => {
|
||||
.then((signedIn: boolean) => {
|
||||
if (signedIn) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -80,13 +82,13 @@ export function _fetchCalendarEntries(
|
||||
})
|
||||
.then(() => dispatch(integration.getCalendarEntries(
|
||||
FETCH_START_DAYS, FETCH_END_DAYS)))
|
||||
.then(events => _updateCalendarEntries.call({
|
||||
.then((events: Object[]) => _updateCalendarEntries.call({
|
||||
dispatch,
|
||||
getState
|
||||
}, events))
|
||||
.then(() => {
|
||||
dispatch(setCalendarError());
|
||||
}, error => {
|
||||
}, (error: any) => {
|
||||
logger.error('Error fetching calendar.', error);
|
||||
|
||||
if (error.error === ERRORS.AUTH_FAILED) {
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/calendar-sync');
|
||||
@@ -1,8 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import { SET_CONFIG } from '../base/config';
|
||||
import { ADD_KNOWN_DOMAINS } from '../base/known-domains';
|
||||
import { MiddlewareRegistry, equals } from '../base/redux';
|
||||
import { IStore } from '../app/types';
|
||||
import { SET_CONFIG } from '../base/config/actionTypes';
|
||||
import { ADD_KNOWN_DOMAINS } from '../base/known-domains/actionTypes';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { equals } from '../base/redux/functions';
|
||||
import { APP_STATE_CHANGED } from '../mobile/background/actionTypes';
|
||||
|
||||
import { REFRESH_CALENDAR } from './actionTypes';
|
||||
@@ -70,7 +70,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _maybeClearAccessStatus(store, { appState }) {
|
||||
function _maybeClearAccessStatus(store: IStore, { appState }: { appState: string; }) {
|
||||
appState === 'background'
|
||||
&& store.dispatch(setCalendarAuthorization(undefined));
|
||||
}
|
||||
@@ -29,7 +29,11 @@ const DEFAULT_STATE = {
|
||||
export interface ICalendarSyncState {
|
||||
authorization?: string;
|
||||
error?: Object;
|
||||
events: Array<Object>;
|
||||
events: Array<{
|
||||
calendarId: string;
|
||||
id: string;
|
||||
url: string;
|
||||
}>;
|
||||
integrationReady: boolean;
|
||||
integrationType?: string;
|
||||
isLoadingEvents?: boolean;
|
||||
|
||||
@@ -6,8 +6,7 @@ import type { Dispatch } from 'redux';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { findWindows } from 'windows-iana';
|
||||
|
||||
import { createDeferred } from '../../../../modules/util/helpers';
|
||||
import { parseStandardURIString, parseURLParams } from '../../base/util';
|
||||
import { createDeferred, parseStandardURIString, parseURLParams } from '../../base/util';
|
||||
import { getShareInfoText } from '../../invite';
|
||||
import { setCalendarAPIAuthState } from '../actions';
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
} from './constants';
|
||||
import { getUnreadCount } from './functions';
|
||||
import { INCOMING_MSG_SOUND_FILE } from './sounds';
|
||||
/* eslint-enable lines-around-comment */
|
||||
|
||||
/**
|
||||
* Timeout for when to show the privacy notice after a private message was received.
|
||||
|
||||
@@ -462,7 +462,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
{/* eslint-disable-next-line react/jsx-no-bind */}
|
||||
<AlwaysOnLabels createOnPress = { this._createOnPress } />
|
||||
</View>
|
||||
{this._renderNotificationsContainer()}
|
||||
{ this._renderNotificationsContainer() }
|
||||
<KnockingParticipantList />
|
||||
</SafeAreaView>
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
@@ -9,6 +8,7 @@ import Button from '../../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../../base/ui/constants.native';
|
||||
|
||||
import EndMeetingIcon from './EndMeetingIcon';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@@ -17,7 +16,6 @@ import { IconMic, IconMicSlash } from '../../../../base/icons/svg';
|
||||
import { MEDIA_TYPE } from '../../../../base/media/constants';
|
||||
import { isLocalTrackMuted } from '../../../../base/tracks/functions';
|
||||
import { isAudioMuteButtonDisabled } from '../../../../toolbox/functions.any';
|
||||
// @ts-ignore
|
||||
import { muteLocal } from '../../../../video-menu/actions';
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
@@ -131,16 +131,16 @@ export default {
|
||||
roomTimer: {
|
||||
...BaseTheme.typography.bodyShortBold,
|
||||
color: BaseTheme.palette.text01,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 6,
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
roomTimerView: {
|
||||
backgroundColor: BaseTheme.palette.ui03,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
height: 32,
|
||||
justifyContent: 'center',
|
||||
minHeight: 32,
|
||||
paddingHorizontal: BaseTheme.spacing[2],
|
||||
paddingVertical: BaseTheme.spacing[1],
|
||||
minWidth: 50
|
||||
},
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@@ -6,9 +5,9 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { IconArrowDown } from '../../../base/icons/svg/index';
|
||||
import Label from '../../../base/label/components/web/Label';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { Tooltip } from '../../../base/tooltip';
|
||||
// @ts-ignore
|
||||
import { setTopPanelVisible } from '../../../filmstrip/actions.web';
|
||||
|
||||
const ToggleTopPanelLabel = () => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { INDICATOR_DISPLAY_THRESHOLD } from '../AbstractConnectionIndicator';
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import { Theme } from '@mui/material';
|
||||
import { withStyles } from '@mui/styles';
|
||||
import clsx from 'clsx';
|
||||
@@ -29,11 +27,13 @@ import AbstractConnectionIndicator, {
|
||||
type State as AbstractState,
|
||||
INDICATOR_DISPLAY_THRESHOLD,
|
||||
mapStateToProps as _abstractMapStateToProps
|
||||
|
||||
// @ts-ignore
|
||||
} from '../AbstractConnectionIndicator';
|
||||
|
||||
// @ts-ignore
|
||||
import ConnectionIndicatorContent from './ConnectionIndicatorContent';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { ConnectionIndicatorIcon } from './ConnectionIndicatorIcon';
|
||||
|
||||
@@ -228,6 +228,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, IState> {
|
||||
<Popover
|
||||
className = { clsx(classes.container, visibilityClass) }
|
||||
content = { <ConnectionIndicatorContent
|
||||
|
||||
// @ts-ignore
|
||||
inheritedStats = { this.state.stats }
|
||||
participantId = { participantId } /> }
|
||||
@@ -236,6 +237,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, IState> {
|
||||
onPopoverClose = { this._onHidePopover }
|
||||
onPopoverOpen = { this._onShowPopover }
|
||||
position = { statsPopoverPosition }
|
||||
|
||||
// @ts-ignore
|
||||
visible = { this.state.popoverVisible }>
|
||||
{ this._renderIndicator() }
|
||||
@@ -259,6 +261,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, IState> {
|
||||
_isConnectionStatusInactive,
|
||||
_isConnectionStatusInterrupted,
|
||||
_connectionIndicatorInactiveDisabled
|
||||
|
||||
// @ts-ignore
|
||||
} = this.props;
|
||||
|
||||
@@ -305,6 +308,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, IState> {
|
||||
|
||||
// @ts-ignore
|
||||
return this.state.showIndicator
|
||||
|
||||
// @ts-ignore
|
||||
|| this.props.alwaysVisible
|
||||
|| _isConnectionStatusInterrupted
|
||||
@@ -348,6 +352,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, IState> {
|
||||
_videoTrack,
|
||||
classes,
|
||||
iconSize
|
||||
|
||||
// @ts-ignore
|
||||
} = this.props;
|
||||
|
||||
@@ -399,5 +404,6 @@ export function _mapStateToProps(state: IReduxState, ownProps: Props) {
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(
|
||||
|
||||
// @ts-ignore
|
||||
withStyles(styles)(ConnectionIndicator)));
|
||||
|
||||
@@ -189,6 +189,7 @@ class ConnectionIndicatorContent extends AbstractConnectionIndicator<Props, Stat
|
||||
codec = { codec }
|
||||
connectionSummary = { this._getConnectionStatusTip() }
|
||||
disableShowMoreStats = { this.props._disableShowMoreStats }
|
||||
e2eeVerified = { this.props._isE2EEVerified }
|
||||
enableSaveLogs = { this.props._enableSaveLogs }
|
||||
framerate = { framerate }
|
||||
isLocalVideo = { this.props._isLocalVideo }
|
||||
@@ -328,6 +329,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
_disableShowMoreStats: state['features/base/config'].disableShowMoreStats,
|
||||
_isConnectionStatusInactive,
|
||||
_isConnectionStatusInterrupted,
|
||||
_isE2EEVerified: participant?.e2eeVerified,
|
||||
_isVirtualScreenshareParticipant: isScreenShareParticipant(participant),
|
||||
_isLocalVideo: participant?.local,
|
||||
_region: participant?.region,
|
||||
|
||||
@@ -73,6 +73,11 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
disableShowMoreStats: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the participant was verified.
|
||||
*/
|
||||
e2eeVerified: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not should display the "Save Logs" link.
|
||||
*/
|
||||
@@ -486,6 +491,31 @@ class ConnectionStatsTable extends Component<IProps> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a a table row as a ReactElement for displaying e2ee verication status, if present.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderE2EEVerified() {
|
||||
const { e2eeVerified, t } = this.props;
|
||||
|
||||
if (e2eeVerified === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const status = e2eeVerified ? '\u{2705}' : '\u{274C}';
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
<span>{ t('connectionindicator.e2eeVerified') }</span>
|
||||
</td>
|
||||
<td>{ status }</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a table row as a ReactElement for displaying a summary message
|
||||
@@ -726,6 +756,7 @@ class ConnectionStatsTable extends Component<IProps> {
|
||||
{ this._renderResolution() }
|
||||
{ this._renderFrameRate() }
|
||||
{ this._renderCodecs() }
|
||||
{ this._renderE2EEVerified() }
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import Tabs from '@atlaskit/tabs';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
@@ -8,6 +7,7 @@ import { hideDialog } from '../../base/dialog/actions';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import { connect } from '../../base/redux/functions';
|
||||
import Dialog from '../../base/ui/components/web/Dialog';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { obtainDesktopSources } from '../functions';
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@@ -11,9 +10,9 @@ import {
|
||||
} from '../../../base/participants/functions';
|
||||
import { updateSettings } from '../../../base/settings/actions';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { Tooltip } from '../../../base/tooltip';
|
||||
// @ts-ignore
|
||||
import { getIndicatorsTooltipPosition } from '../../../filmstrip/functions.web';
|
||||
import { appendSuffix } from '../../functions';
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
@@ -12,11 +10,9 @@ import {
|
||||
isWhiteboardParticipant
|
||||
} from '../../../base/participants/functions';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
// @ts-ignore
|
||||
import { getLargeVideoParticipant } from '../../../large-video/functions';
|
||||
import { isToolboxVisible } from '../../../toolbox/functions.web';
|
||||
// @ts-ignore
|
||||
import { isLayoutTileView } from '../../../video-layout';
|
||||
import { isLayoutTileView } from '../../../video-layout/functions.web';
|
||||
|
||||
import DisplayNameBadge from './DisplayNameBadge';
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export function authorizeDropbox() {
|
||||
* expireDate: number
|
||||
* }}
|
||||
*/
|
||||
export function updateDropboxToken(token: string, rToken: string, expireDate: number) {
|
||||
export function updateDropboxToken(token?: string, rToken?: string, expireDate?: number) {
|
||||
return {
|
||||
type: UPDATE_DROPBOX_TOKEN,
|
||||
token,
|
||||
|
||||
@@ -43,3 +43,7 @@ export const SET_MAX_MODE = 'SET_MAX_MODE';
|
||||
* }
|
||||
*/
|
||||
export const SET_MEDIA_ENCRYPTION_KEY = 'SET_MEDIA_ENCRYPTION_KEY';
|
||||
|
||||
export const START_VERIFICATION = 'START_VERIFICATION';
|
||||
|
||||
export const PARTICIPANT_VERIFIED = 'PARTICIPANT_VERIFIED';
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import {
|
||||
PARTICIPANT_VERIFIED,
|
||||
SET_EVERYONE_ENABLED_E2EE,
|
||||
SET_EVERYONE_SUPPORT_E2EE,
|
||||
SET_MAX_MODE,
|
||||
SET_MEDIA_ENCRYPTION_KEY,
|
||||
START_VERIFICATION,
|
||||
TOGGLE_E2EE } from './actionTypes';
|
||||
|
||||
/**
|
||||
@@ -80,3 +82,38 @@ export function setMediaEncryptionKey(keyInfo: Object) {
|
||||
keyInfo
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to start participant e2ee verficiation process.
|
||||
*
|
||||
* @param {string} pId - The participant id.
|
||||
* @returns {{
|
||||
* type: START_VERIFICATION,
|
||||
* pId: string
|
||||
* }}
|
||||
*/
|
||||
export function startVerification(pId: string) {
|
||||
return {
|
||||
type: START_VERIFICATION,
|
||||
pId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to set participant e2ee verification status.
|
||||
*
|
||||
* @param {string} pId - The participant id.
|
||||
* @param {boolean} isVerified - The verifcation status.
|
||||
* @returns {{
|
||||
* type: PARTICIPANT_VERIFIED,
|
||||
* pId: string,
|
||||
* isVerified: boolean
|
||||
* }}
|
||||
*/
|
||||
export function participantVerified(pId: string, isVerified: boolean) {
|
||||
return {
|
||||
type: PARTICIPANT_VERIFIED,
|
||||
pId,
|
||||
isVerified
|
||||
};
|
||||
}
|
||||
|
||||
166
react/features/e2ee/components/ParticipantVerificationDialog.tsx
Normal file
166
react/features/e2ee/components/ParticipantVerificationDialog.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import { withStyles } from '@mui/styles';
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import { getParticipantById } from '../../base/participants/functions';
|
||||
import { connect } from '../../base/redux/functions';
|
||||
import Dialog from '../../base/ui/components/web/Dialog';
|
||||
import { participantVerified } from '../actions';
|
||||
import { ISas } from '../reducer';
|
||||
|
||||
interface IProps extends WithTranslation {
|
||||
classes: any;
|
||||
decimal: string;
|
||||
dispatch: IStore['dispatch'];
|
||||
emoji: string;
|
||||
pId: string;
|
||||
participantName: string;
|
||||
sas: ISas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the styles for the component.
|
||||
*
|
||||
* @param {Object} theme - The current UI theme.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
const styles = () => {
|
||||
return {
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
margin: '16px'
|
||||
},
|
||||
row: {
|
||||
alignSelf: 'center',
|
||||
display: 'flex'
|
||||
},
|
||||
item: {
|
||||
textAlign: 'center',
|
||||
margin: '16px'
|
||||
},
|
||||
emoji: {
|
||||
fontSize: '40px',
|
||||
margin: '12px'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class for the dialog displayed for E2EE sas verification.
|
||||
*/
|
||||
export class ParticipantVerificationDialog extends Component<IProps> {
|
||||
/**
|
||||
* Instantiates a new instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onConfirmed = this._onConfirmed.bind(this);
|
||||
this._onDismissed = this._onDismissed.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { emoji } = this.props.sas;
|
||||
const { participantName } = this.props;
|
||||
|
||||
const { classes, t } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
cancel = {{ translationKey: 'dialog.verifyParticipantDismiss' }}
|
||||
ok = {{ translationKey: 'dialog.verifyParticipantConfirm' }}
|
||||
onCancel = { this._onDismissed }
|
||||
onSubmit = { this._onConfirmed }
|
||||
titleKey = 'dialog.verifyParticipantTitle'>
|
||||
<div>
|
||||
{ t('dialog.verifyParticipantQuestion', { participantName }) }
|
||||
</div>
|
||||
|
||||
<div className = { classes.container }>
|
||||
<div className = { classes.row }>
|
||||
{/* @ts-ignore */}
|
||||
{emoji.slice(0, 4).map((e: Array<string>) =>
|
||||
(<div
|
||||
className = { classes.item }
|
||||
key = { e.toString() }>
|
||||
<div className = { classes.emoji }>{ e[0] }</div>
|
||||
<div>{ e[1].charAt(0).toUpperCase() + e[1].slice(1) }</div>
|
||||
</div>))}
|
||||
</div>
|
||||
|
||||
<div className = { classes.row }>
|
||||
{/* @ts-ignore */}
|
||||
{emoji.slice(4, 7).map((e: Array<string>) =>
|
||||
(<div
|
||||
className = { classes.item }
|
||||
key = { e.toString() }>
|
||||
<div className = { classes.emoji }>{ e[0] } </div>
|
||||
<div>{ e[1].charAt(0).toUpperCase() + e[1].slice(1) }</div>
|
||||
</div>))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this ParticipantVerificationDialog that it has been dismissed by cancel.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDismissed() {
|
||||
this.props.dispatch(participantVerified(this.props.pId, false));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this ParticipantVerificationDialog that it has been dismissed with confirmation.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onConfirmed() {
|
||||
this.props.dispatch(participantVerified(this.props.pId, true));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux store to the props of this component.
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @param {IProps} ownProps - The own props of the component.
|
||||
* @returns {IProps}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
const participant = getParticipantById(state, ownProps.pId);
|
||||
|
||||
return {
|
||||
sas: ownProps.sas,
|
||||
pId: ownProps.pId,
|
||||
participantName: participant?.name
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(
|
||||
|
||||
// @ts-ignore
|
||||
withStyles(styles)(ParticipantVerificationDialog)));
|
||||
@@ -1,7 +1,9 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { getParticipantCount } from '../base/participants/functions';
|
||||
import { getParticipantById, getParticipantCount } from '../base/participants/functions';
|
||||
import { toState } from '../base/redux/functions';
|
||||
|
||||
|
||||
import { MAX_MODE_LIMIT, MAX_MODE_THRESHOLD } from './constants';
|
||||
|
||||
/**
|
||||
@@ -55,3 +57,19 @@ export function isMaxModeThresholdReached(stateful: IStateful) {
|
||||
|
||||
return participantCount >= MAX_MODE_LIMIT + MAX_MODE_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether e2ee is enabled by the backend.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {string} pId - The participant id.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function displayVerification(state: IReduxState, pId: string) {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const participant = getParticipantById(state, pId);
|
||||
|
||||
return Boolean(conference?.isE2EEEnabled()
|
||||
&& participant?.e2eeVerificationAvailable
|
||||
&& participant?.e2eeVerified === undefined);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { IStore } from '../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
|
||||
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||
import { getCurrentConference } from '../base/conference/functions';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT, PARTICIPANT_UPDATED } from '../base/participants/actionTypes';
|
||||
import { participantUpdated } from '../base/participants/actions';
|
||||
import {
|
||||
@@ -17,13 +19,15 @@ import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
|
||||
|
||||
import { SET_MEDIA_ENCRYPTION_KEY, TOGGLE_E2EE } from './actionTypes';
|
||||
import { PARTICIPANT_VERIFIED, SET_MEDIA_ENCRYPTION_KEY, START_VERIFICATION, TOGGLE_E2EE } from './actionTypes';
|
||||
import { setE2EEMaxMode, setEveryoneEnabledE2EE, setEveryoneSupportE2EE, toggleE2EE } from './actions';
|
||||
import ParticipantVerificationDialog from './components/ParticipantVerificationDialog';
|
||||
import { E2EE_OFF_SOUND_ID, E2EE_ON_SOUND_ID, MAX_MODE } from './constants';
|
||||
import { isMaxModeReached, isMaxModeThresholdReached } from './functions';
|
||||
import logger from './logger';
|
||||
import { E2EE_OFF_SOUND_FILE, E2EE_ON_SOUND_FILE } from './sounds';
|
||||
|
||||
|
||||
/**
|
||||
* Middleware that captures actions related to E2EE.
|
||||
*
|
||||
@@ -239,6 +243,18 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PARTICIPANT_VERIFIED: {
|
||||
const { isVerified, pId } = action;
|
||||
|
||||
conference?.markParticipantVerified(pId, isVerified);
|
||||
break;
|
||||
}
|
||||
|
||||
case START_VERIFICATION: {
|
||||
conference?.startVerification(action.pId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
@@ -254,6 +270,29 @@ StateListenerRegistry.register(
|
||||
if (previousConference) {
|
||||
dispatch(toggleE2EE(false));
|
||||
}
|
||||
|
||||
conference.on(JitsiConferenceEvents.E2EE_VERIFICATION_AVAILABLE, (pId: string) => {
|
||||
dispatch(participantUpdated({
|
||||
e2eeVerificationAvailable: true,
|
||||
id: pId
|
||||
}));
|
||||
});
|
||||
|
||||
conference.on(JitsiConferenceEvents.E2EE_VERIFICATION_READY, (pId: string, sas: object) => {
|
||||
dispatch(openDialog(ParticipantVerificationDialog, { pId,
|
||||
sas }));
|
||||
});
|
||||
|
||||
conference.on(JitsiConferenceEvents.E2EE_VERIFICATION_COMPLETED,
|
||||
(pId: string, success: boolean, message: string) => {
|
||||
if (message) {
|
||||
logger.warn('E2EE_VERIFICATION_COMPLETED warning', message);
|
||||
}
|
||||
dispatch(participantUpdated({
|
||||
e2eeVerified: success,
|
||||
id: pId
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,10 @@ export interface IE2EEState {
|
||||
maxMode: string;
|
||||
}
|
||||
|
||||
export interface ISas {
|
||||
emoji: Array<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the Redux actions of the feature features/e2ee.
|
||||
*/
|
||||
|
||||
@@ -17,12 +17,10 @@ import { IconArrowDown, IconArrowUp } from '../../../base/icons/svg';
|
||||
import { IParticipant } from '../../../base/participants/types';
|
||||
import { connect } from '../../../base/redux/functions';
|
||||
import { shouldHideSelfView } from '../../../base/settings/functions.web';
|
||||
// @ts-ignore
|
||||
import { showToolbox } from '../../../toolbox/actions.web';
|
||||
import { isButtonEnabled, isToolboxVisible } from '../../../toolbox/functions.web';
|
||||
// @ts-ignore
|
||||
import { getCurrentLayout } from '../../../video-layout';
|
||||
import { LAYOUTS } from '../../../video-layout/constants';
|
||||
import { getCurrentLayout } from '../../../video-layout/functions.web';
|
||||
import {
|
||||
setFilmstripVisible,
|
||||
setTopPanelVisible,
|
||||
@@ -30,7 +28,6 @@ import {
|
||||
setUserFilmstripWidth,
|
||||
setUserIsResizing,
|
||||
setVisibleRemoteParticipants
|
||||
// @ts-ignore
|
||||
} from '../../actions';
|
||||
import {
|
||||
ASPECT_RATIO_BREAKPOINT,
|
||||
@@ -46,7 +43,6 @@ import {
|
||||
getVerticalViewMaxWidth,
|
||||
isStageFilmstripTopPanel,
|
||||
shouldRemoteVideosBeVisible
|
||||
// @ts-ignore
|
||||
} from '../../functions';
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
@@ -6,8 +6,6 @@ import { IReduxState } from '../../../app/types';
|
||||
import { IconPin } from '../../../base/icons/svg';
|
||||
import { getParticipantById } from '../../../base/participants/functions';
|
||||
import BaseIndicator from '../../../base/react/components/web/BaseIndicator';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { getPinnedActiveParticipants, isStageFilmstripAvailable } from '../../functions.web';
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,10 +39,8 @@ import { hideGif, showGif } from '../../../gifs/actions';
|
||||
import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions';
|
||||
// @ts-ignore
|
||||
import { PresenceLabel } from '../../../presence-status';
|
||||
// @ts-ignore
|
||||
import { getCurrentLayout } from '../../../video-layout';
|
||||
import { LAYOUTS } from '../../../video-layout/constants';
|
||||
// @ts-ignore
|
||||
import { getCurrentLayout } from '../../../video-layout/functions.web';
|
||||
import { togglePinStageParticipant } from '../../actions';
|
||||
import {
|
||||
DISPLAY_MODE_TO_CLASS_NAME,
|
||||
@@ -60,7 +58,6 @@ import {
|
||||
isStageFilmstripAvailable,
|
||||
isVideoPlayable,
|
||||
showGridInVerticalView
|
||||
// @ts-ignore
|
||||
} from '../../functions';
|
||||
|
||||
// @ts-ignore
|
||||
@@ -1188,7 +1185,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any): Object {
|
||||
const _audioTrack = isLocal
|
||||
? getLocalAudioTrack(tracks)
|
||||
: getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
|
||||
const _currentLayout = getCurrentLayout(state);
|
||||
const _currentLayout = getCurrentLayout(state) ?? '';
|
||||
let size: any = {};
|
||||
let _isMobilePortrait = false;
|
||||
const {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
@@ -9,11 +7,11 @@ import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { isScreenShareParticipantById } from '../../../base/participants/functions';
|
||||
import ConnectionIndicator from '../../../connection-indicator/components/web/ConnectionIndicator';
|
||||
import { STATS_POPOVER_POSITION, THUMBNAIL_TYPE } from '../../constants';
|
||||
// @ts-ignore
|
||||
import { getIndicatorsTooltipPosition } from '../../functions.web';
|
||||
|
||||
import PinnedIndicator from './PinnedIndicator';
|
||||
import RaisedHandIndicator from './RaisedHandIndicator';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import StatusIndicators from './StatusIndicators';
|
||||
import VideoMenuTriggerButton from './VideoMenuTriggerButton';
|
||||
|
||||
@@ -4,8 +4,6 @@ import { getParticipantCountWithFake } from '../base/participants/functions';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { clientResized } from '../base/responsive-ui/actions';
|
||||
import { shouldHideSelfView } from '../base/settings/functions.web';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { selectParticipantInLargeVideo } from '../large-video/actions.any';
|
||||
import { getParticipantsPaneOpen } from '../participants-pane/functions';
|
||||
import { setOverflowDrawer } from '../toolbox/actions.web';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import { GiphyFetch, TrendingOptions } from '@giphy/js-fetch-api';
|
||||
import { Grid } from '@giphy/react-components';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
@@ -13,8 +12,8 @@ import Input from '../../../base/ui/components/web/Input';
|
||||
import { sendMessage } from '../../../chat/actions.any';
|
||||
import { SCROLL_SIZE } from '../../../filmstrip/constants';
|
||||
import { toggleReactionsMenuVisibility } from '../../../reactions/actions.web';
|
||||
// @ts-ignore
|
||||
import { setOverflowMenuVisible } from '../../../toolbox/actions.web';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { Drawer, JitsiPortal } from '../../../toolbox/components/web';
|
||||
import { showOverflowDrawer } from '../../../toolbox/functions.web';
|
||||
|
||||
@@ -12,8 +12,7 @@ import { connect } from '../../../../base/redux/functions';
|
||||
import Dialog from '../../../../base/ui/components/web/Dialog';
|
||||
import { isDynamicBrandingDataLoaded } from '../../../../dynamic-branding/functions.any';
|
||||
import { isVpaasMeeting } from '../../../../jaas/functions';
|
||||
// @ts-ignore
|
||||
import { getActiveSession } from '../../../../recording';
|
||||
import { getActiveSession } from '../../../../recording/functions';
|
||||
// @ts-ignore
|
||||
import { updateDialInNumbers } from '../../../actions';
|
||||
import {
|
||||
|
||||
@@ -4,8 +4,6 @@ import { useStore } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { setSeeWhatIsBeingShared } from '../actions.web';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants/functions';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { handleLobbyChatInitialized } from '../../../chat/actions.any';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { handleLobbyChatInitialized } from '../../../chat/actions.native';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { setKnockingParticipantApproval } from '../../actions';
|
||||
import { HIDDEN_EMAILS } from '../../constants';
|
||||
import ParticipantItem
|
||||
from '../../../participants-pane/components/native/ParticipantItem';
|
||||
import { setKnockingParticipantApproval } from '../../actions.native';
|
||||
import { getKnockingParticipants, getLobbyEnabled, showLobbyChatButton } from '../../functions';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* Props type of the component.
|
||||
*/
|
||||
@@ -44,12 +45,7 @@ export type Props = {
|
||||
/**
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
dispatch: Function
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -74,68 +70,47 @@ class KnockingParticipantList extends PureComponent<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _participants, _visible, _showChatButton, t } = this.props;
|
||||
const { _participants, _visible, _showChatButton } = this.props;
|
||||
|
||||
if (!_visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style = { styles.knockingParticipantList }>
|
||||
<>
|
||||
{ _participants.map(p => (
|
||||
<View
|
||||
key = { p.id }
|
||||
style = { styles.knockingParticipantListEntry }>
|
||||
<Avatar
|
||||
<ParticipantItem
|
||||
displayName = { p.name }
|
||||
size = { 48 }
|
||||
url = { p.loadableAvatarUrl } />
|
||||
<View style = { styles.knockingParticipantListDetails }>
|
||||
<Text style = { styles.knockingParticipantListText }>
|
||||
{ p.name }
|
||||
</Text>
|
||||
{ p.email && !HIDDEN_EMAILS.includes(p.email) && (
|
||||
<Text style = { styles.knockingParticipantListText }>
|
||||
{ p.email }
|
||||
</Text>
|
||||
) }
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress = { this._onRespondToParticipant(p.id, true) }
|
||||
style = { [
|
||||
styles.knockingParticipantListButton,
|
||||
styles.knockingParticipantListPrimaryButton
|
||||
] }>
|
||||
<Text style = { styles.knockingParticipantListText }>
|
||||
{ t('lobby.allow') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress = { this._onRespondToParticipant(p.id, false) }
|
||||
style = { [
|
||||
styles.knockingParticipantListButton,
|
||||
styles.knockingParticipantListSecondaryButton
|
||||
] }>
|
||||
<Text style = { styles.knockingParticipantListText }>
|
||||
{ t('lobby.reject') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
{_showChatButton(p) ? (
|
||||
<TouchableOpacity
|
||||
onPress = { this._onInitializeLobbyChat(p.id) }
|
||||
style = { [
|
||||
styles.knockingParticipantListButton,
|
||||
styles.knockingParticipantListSecondaryButton
|
||||
] }>
|
||||
<Text style = { styles.knockingParticipantListText }>
|
||||
{ t('lobby.chat') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
isKnockingParticipant = { true }
|
||||
key = { p.id }
|
||||
participantID = { p.id }>
|
||||
<Button
|
||||
labelKey = { 'lobby.admit' }
|
||||
onClick = { this._onRespondToParticipant(p.id, true) }
|
||||
style = { styles.lobbyButtonAdmit }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
{
|
||||
_showChatButton(p)
|
||||
? (
|
||||
<Button
|
||||
labelKey = { 'lobby.chat' }
|
||||
onClick = { this._onInitializeLobbyChat(p.id) }
|
||||
style = { styles.lobbyButtonChat }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
) : null
|
||||
}
|
||||
<Button
|
||||
labelKey = { 'lobby.reject' }
|
||||
onClick = { this._onRespondToParticipant(p.id, false) }
|
||||
style = { styles.lobbyButtonReject }
|
||||
type = { BUTTON_TYPES.DESTRUCTIVE } />
|
||||
</ParticipantItem>
|
||||
</View>
|
||||
)) }
|
||||
</ScrollView>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -168,6 +143,7 @@ class KnockingParticipantList extends PureComponent<Props> {
|
||||
if (this.props._isPollsDisabled) {
|
||||
return navigate(screen.conference.chat);
|
||||
}
|
||||
|
||||
navigate(screen.conference.chatandpolls.main);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { LargeVideo } from '../../../large-video/components';
|
||||
import { navigate }
|
||||
from '../../../mobile/navigation/components/lobby/LobbyNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { preJoinStyles } from '../../../prejoin/components/native/styles';
|
||||
import AudioMuteButton from '../../../toolbox/components/AudioMuteButton';
|
||||
import VideoMuteButton from '../../../toolbox/components/VideoMuteButton';
|
||||
import AbstractLobbyScreen, {
|
||||
@@ -48,32 +49,39 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _aspectRatio } = this.props;
|
||||
const { _aspectRatio, _roomName } = this.props;
|
||||
let contentWrapperStyles;
|
||||
let contentContainerStyles;
|
||||
let largeVideoContainerStyles;
|
||||
|
||||
if (_aspectRatio === ASPECT_RATIO_NARROW) {
|
||||
contentWrapperStyles = styles.contentWrapper;
|
||||
largeVideoContainerStyles = styles.largeVideoContainer;
|
||||
contentWrapperStyles = preJoinStyles.contentWrapper;
|
||||
largeVideoContainerStyles = preJoinStyles.largeVideoContainer;
|
||||
contentContainerStyles = styles.contentContainer;
|
||||
} else {
|
||||
contentWrapperStyles = styles.contentWrapperWide;
|
||||
largeVideoContainerStyles = styles.largeVideoContainerWide;
|
||||
contentContainerStyles = styles.contentContainerWide;
|
||||
contentWrapperStyles = preJoinStyles.contentWrapperWide;
|
||||
largeVideoContainerStyles = preJoinStyles.largeVideoContainerWide;
|
||||
contentContainerStyles = preJoinStyles.contentContainerWide;
|
||||
}
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
safeAreaInsets = { [ 'left' ] }
|
||||
safeAreaInsets = { [ 'right' ] }
|
||||
style = { contentWrapperStyles }>
|
||||
<BrandingImageBackground />
|
||||
<View style = { largeVideoContainerStyles }>
|
||||
<View style = { preJoinStyles.displayRoomNameBackdrop }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { preJoinStyles.preJoinRoomName }>
|
||||
{ _roomName }
|
||||
</Text>
|
||||
</View>
|
||||
<LargeVideo />
|
||||
</View>
|
||||
<View style = { contentContainerStyles }>
|
||||
{ this._renderContent() }
|
||||
{ this._renderToolbarButtons() }
|
||||
{ this._renderContent() }
|
||||
</View>
|
||||
</JitsiScreen>
|
||||
);
|
||||
@@ -122,15 +130,10 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
*/
|
||||
_renderJoining() {
|
||||
return (
|
||||
<View>
|
||||
<View style = { styles.lobbyWaitingFragmentContainer }>
|
||||
<Text style = { styles.lobbyTitle }>
|
||||
{ this.props.t('lobby.joiningTitle') }
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { styles.lobbyRoomName }>
|
||||
{ this.props._roomName }
|
||||
</Text>
|
||||
<LoadingIndicator
|
||||
color = { BaseTheme.palette.icon01 }
|
||||
style = { styles.loadingIndicator } />
|
||||
@@ -153,7 +156,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
|
||||
return (
|
||||
<Input
|
||||
customStyles = {{ input: styles.customInput }}
|
||||
customStyles = {{ input: preJoinStyles.customInput }}
|
||||
onChange = { this._onChangeDisplayName }
|
||||
placeholder = { t('lobby.nameField') }
|
||||
value = { displayName } />
|
||||
@@ -178,19 +181,15 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
const { _passwordJoinFailed, t } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.formWrapper }>
|
||||
<Input
|
||||
autoCapitalize = 'none'
|
||||
autoCompleteType = 'off'
|
||||
customStyles = {{ input: styles.customInput }}
|
||||
onChange = { this._onChangePassword }
|
||||
placeholder = { t('lobby.passwordField') }
|
||||
secureTextEntry = { true }
|
||||
value = { this.state.password } />
|
||||
{ _passwordJoinFailed && <Text style = { styles.fieldError }>
|
||||
{ t('lobby.invalidPassword') }
|
||||
</Text> }
|
||||
</View>
|
||||
<Input
|
||||
autoCapitalize = 'none'
|
||||
autoCompleteType = 'off'
|
||||
customStyles = {{ input: styles.customInput }}
|
||||
error = { _passwordJoinFailed }
|
||||
onChange = { this._onChangePassword }
|
||||
placeholder = { t('lobby.passwordField') }
|
||||
secureTextEntry = { true }
|
||||
value = { this.state.password } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -201,20 +200,20 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
*/
|
||||
_renderPasswordJoinButtons() {
|
||||
return (
|
||||
<View style = { styles.passwordJoinButtonsWrapper }>
|
||||
<View style = { styles.passwordJoinButtons }>
|
||||
<Button
|
||||
accessibilityLabel = 'lobby.passwordJoinButton'
|
||||
disabled = { !this.state.password }
|
||||
labelKey = { 'lobby.passwordJoinButton' }
|
||||
onClick = { this._onJoinWithPassword }
|
||||
style = { preJoinStyles.joinButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
<Button
|
||||
accessibilityLabel = 'lobby.backToKnockModeButton'
|
||||
labelKey = 'lobby.backToKnockModeButton'
|
||||
onClick = { this._onSwitchToKnockMode }
|
||||
style = { styles.lobbyButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
<Button
|
||||
accessibilityLabel = 'lobby.passwordJoinButton'
|
||||
disabled = { !this.state.password }
|
||||
labelKey = 'lobby.passwordJoinButton'
|
||||
onClick = { this._onJoinWithPassword }
|
||||
style = { styles.lobbyButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
style = { preJoinStyles.joinButton }
|
||||
type = { BUTTON_TYPES.TERTIARY } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -225,21 +224,12 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderToolbarButtons() {
|
||||
const { _aspectRatio } = this.props;
|
||||
let toolboxContainerStyles;
|
||||
|
||||
if (_aspectRatio === ASPECT_RATIO_NARROW) {
|
||||
toolboxContainerStyles = styles.toolboxContainer;
|
||||
} else {
|
||||
toolboxContainerStyles = styles.toolboxContainerWide;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { toolboxContainerStyles }>
|
||||
<View style = { preJoinStyles.toolboxContainer }>
|
||||
<AudioMuteButton
|
||||
styles = { styles.buttonStylesBorderless } />
|
||||
styles = { preJoinStyles.buttonStylesBorderless } />
|
||||
<VideoMuteButton
|
||||
styles = { styles.buttonStylesBorderless } />
|
||||
styles = { preJoinStyles.buttonStylesBorderless } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -254,14 +244,14 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
const { displayName } = this.state;
|
||||
|
||||
return (
|
||||
<View style = { styles.standardButtonWrapper }>
|
||||
<View style = { styles.formWrapper }>
|
||||
{
|
||||
_knocking && _isLobbyChatActive
|
||||
&& <Button
|
||||
accessibilityLabel = 'toolbar.openChat'
|
||||
labelKey = 'toolbar.openChat'
|
||||
onClick = { this._onNavigateToLobbyChat }
|
||||
style = { styles.openChatButton }
|
||||
style = { preJoinStyles.joinButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
}
|
||||
{
|
||||
@@ -271,7 +261,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
disabled = { !displayName }
|
||||
labelKey = 'lobby.knockButton'
|
||||
onClick = { this._onAskToJoin }
|
||||
style = { styles.lobbyButton }
|
||||
style = { preJoinStyles.joinButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
}
|
||||
{
|
||||
@@ -280,7 +270,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
accessibilityLabel = 'lobby.enterPasswordButton'
|
||||
labelKey = 'lobby.enterPasswordButton'
|
||||
onClick = { this._onSwitchToPasswordMode }
|
||||
style = { styles.enterPasswordButton }
|
||||
style = { preJoinStyles.joinButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
}
|
||||
</View>
|
||||
|
||||
@@ -1,32 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
const SECONDARY_COLOR = BaseTheme.palette.border04;
|
||||
|
||||
const lobbyText = {
|
||||
...BaseTheme.typography.heading5,
|
||||
color: BaseTheme.palette.text01,
|
||||
textAlign: 'center'
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
buttonStylesBorderless: {
|
||||
iconStyle: {
|
||||
color: BaseTheme.palette.icon01,
|
||||
fontSize: 24
|
||||
},
|
||||
style: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginHorizontal: BaseTheme.spacing[3],
|
||||
height: 24,
|
||||
width: 24
|
||||
},
|
||||
underlayColor: 'transparent'
|
||||
},
|
||||
|
||||
lobbyChatWrapper: {
|
||||
backgroundColor: BaseTheme.palette.ui01,
|
||||
alignItems: 'stretch',
|
||||
@@ -35,118 +11,32 @@ export default {
|
||||
height: '100%'
|
||||
},
|
||||
|
||||
lobbyChatHeader: {
|
||||
flexDirection: 'row',
|
||||
padding: 20
|
||||
},
|
||||
|
||||
lobbyChatTitle: {
|
||||
color: BaseTheme.palette.text01,
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
flexShrink: 1
|
||||
},
|
||||
|
||||
lobbyChatCloseButton: {
|
||||
fontSize: 24,
|
||||
marginLeft: BaseTheme.spacing[3],
|
||||
marginTop: BaseTheme.spacing[1],
|
||||
color: BaseTheme.palette.icon01
|
||||
},
|
||||
|
||||
contentWrapper: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
contentWrapperWide: {
|
||||
flex: 1,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
largeVideoContainer: {
|
||||
minHeight: '50%'
|
||||
},
|
||||
|
||||
largeVideoContainerWide: {
|
||||
height: '100%',
|
||||
marginRight: 'auto',
|
||||
position: 'absolute',
|
||||
width: '50%'
|
||||
passwordJoinButtons: {
|
||||
top: 40
|
||||
},
|
||||
|
||||
contentContainer: {
|
||||
alignSelf: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
minHeight: '50%',
|
||||
paddingHorizontal: BaseTheme.spacing[3],
|
||||
width: 400
|
||||
},
|
||||
|
||||
contentContainerWide: {
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
backgroundColor: BaseTheme.palette.uiBackground,
|
||||
bottom: 0,
|
||||
display: 'flex',
|
||||
height: 388,
|
||||
justifyContent: 'center',
|
||||
left: '50%',
|
||||
paddingHorizontal: BaseTheme.spacing[3],
|
||||
position: 'absolute',
|
||||
width: '50%'
|
||||
},
|
||||
|
||||
toolboxContainer: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginTop: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
toolboxContainerWide: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginTop: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
displayNameText: {
|
||||
fontWeight: 'bold',
|
||||
marginVertical: 10
|
||||
},
|
||||
|
||||
editButton: {
|
||||
alignSelf: 'flex-end',
|
||||
paddingHorizontal: 10
|
||||
},
|
||||
|
||||
editIcon: {
|
||||
color: 'black',
|
||||
fontSize: 16
|
||||
width: '100%',
|
||||
zIndex: 1
|
||||
},
|
||||
|
||||
formWrapper: {
|
||||
alignSelf: 'stretch',
|
||||
justifyContent: 'center',
|
||||
marginTop: 38
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
customInput: {
|
||||
marginHorizontal: BaseTheme.spacing[3],
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
fieldError: {
|
||||
color: BaseTheme.palette.warning03,
|
||||
marginLeft: BaseTheme.spacing[3],
|
||||
fontSize: 16
|
||||
},
|
||||
|
||||
fieldLabel: {
|
||||
...BaseTheme.typography.heading6,
|
||||
color: BaseTheme.palette.text01,
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
standardButtonWrapper: {
|
||||
alignSelf: 'stretch'
|
||||
position: 'relative',
|
||||
textAlign: 'center',
|
||||
top: BaseTheme.spacing[6],
|
||||
width: 352
|
||||
},
|
||||
|
||||
joiningMessage: {
|
||||
@@ -155,84 +45,58 @@ export default {
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
passwordJoinButtonsWrapper: {
|
||||
alignItems: 'stretch',
|
||||
alignSelf: 'stretch',
|
||||
marginHorizontal: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
loadingIndicator: {
|
||||
marginBottom: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
participantBox: {
|
||||
alignItems: 'center',
|
||||
alignSelf: 'stretch',
|
||||
borderColor: SECONDARY_COLOR,
|
||||
borderRadius: 4,
|
||||
borderWidth: 1,
|
||||
marginVertical: 18,
|
||||
paddingVertical: 12
|
||||
},
|
||||
|
||||
lobbyButton: {
|
||||
marginTop: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
openChatButton: {
|
||||
marginHorizontal: BaseTheme.spacing[3],
|
||||
marginTop: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
enterPasswordButton: {
|
||||
marginHorizontal: BaseTheme.spacing[3],
|
||||
marginTop: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
// KnockingParticipantList
|
||||
|
||||
knockingParticipantList: {
|
||||
alignSelf: 'stretch',
|
||||
backgroundColor: 'rgba(22, 38, 55, 0.8)',
|
||||
flexDirection: 'column'
|
||||
backgroundColor: BaseTheme.palette.ui01
|
||||
},
|
||||
|
||||
knockingParticipantListButton: {
|
||||
borderRadius: 4,
|
||||
marginHorizontal: 3,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 5
|
||||
},
|
||||
|
||||
knockingParticipantListDetails: {
|
||||
flex: 1,
|
||||
marginLeft: 10
|
||||
marginLeft: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
knockingParticipantListEntry: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
padding: 10
|
||||
},
|
||||
|
||||
knockingParticipantListPrimaryButton: {
|
||||
backgroundColor: 'rgb(3, 118, 218)'
|
||||
},
|
||||
|
||||
knockingParticipantListSecondaryButton: {
|
||||
backgroundColor: 'transparent'
|
||||
backgroundColor: BaseTheme.palette.ui01,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
knockingParticipantListText: {
|
||||
color: 'white'
|
||||
},
|
||||
|
||||
lobbyTitle: {
|
||||
...lobbyText
|
||||
lobbyButtonAdmit: {
|
||||
position: 'absolute',
|
||||
right: 184,
|
||||
top: 6
|
||||
},
|
||||
|
||||
lobbyRoomName: {
|
||||
...lobbyText,
|
||||
marginBottom: BaseTheme.spacing[2]
|
||||
lobbyButtonChat: {
|
||||
position: 'absolute',
|
||||
right: 104,
|
||||
top: 6
|
||||
},
|
||||
|
||||
lobbyButtonReject: {
|
||||
position: 'absolute',
|
||||
right: 16,
|
||||
top: 6
|
||||
},
|
||||
|
||||
lobbyTitle: {
|
||||
...BaseTheme.typography.heading5,
|
||||
color: BaseTheme.palette.text01,
|
||||
marginBottom: BaseTheme.spacing[3],
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
lobbyWaitingFragmentContainer: {
|
||||
height: 260
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,8 +7,6 @@ import { isLocalParticipantModerator } from '../../../base/participants/function
|
||||
import { connect } from '../../../base/redux/functions';
|
||||
import Switch from '../../../base/ui/components/web/Switch';
|
||||
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { toggleLobbyMode } from '../../actions';
|
||||
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import ReducerRegistry from '../../base/redux/ReducerRegistry';
|
||||
import { set } from '../../base/redux/functions';
|
||||
|
||||
// @ts-ignore
|
||||
import CallKit from './CallKit';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import ConnectionService from './ConnectionService';
|
||||
import { _SET_CALL_INTEGRATION_SUBSCRIPTIONS } from './actionTypes';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { executeTrackOperation } from '../base/tracks/actions';
|
||||
import { getLocalJitsiAudioTrack } from '../base/tracks/functions';
|
||||
import { TrackOperationType } from '../base/tracks/types';
|
||||
import { showErrorNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
import { NoiseSuppressionEffect } from '../stream-effects/noise-suppression/NoiseSuppressionEffect';
|
||||
@@ -48,30 +50,32 @@ export function toggleNoiseSuppression(): any {
|
||||
*/
|
||||
export function setNoiseSuppressionEnabled(enabled: boolean): any {
|
||||
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
|
||||
const localAudio = getLocalJitsiAudioTrack(state);
|
||||
const noiseSuppressionEnabled = isNoiseSuppressionEnabled(state);
|
||||
|
||||
logger.info(`Attempting to set noise suppression enabled state: ${enabled}`);
|
||||
|
||||
try {
|
||||
if (enabled && !noiseSuppressionEnabled) {
|
||||
if (!canEnableNoiseSuppression(state, dispatch, localAudio)) {
|
||||
return;
|
||||
await dispatch(executeTrackOperation(TrackOperationType.Audio, async () => {
|
||||
const state = getState();
|
||||
|
||||
const localAudio = getLocalJitsiAudioTrack(state);
|
||||
const noiseSuppressionEnabled = isNoiseSuppressionEnabled(state);
|
||||
|
||||
if (enabled && !noiseSuppressionEnabled) {
|
||||
if (!canEnableNoiseSuppression(state, dispatch, localAudio)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await localAudio.setEffect(new NoiseSuppressionEffect());
|
||||
dispatch(setNoiseSuppressionEnabledState(true));
|
||||
logger.info('Noise suppression enabled.');
|
||||
|
||||
} else if (!enabled && noiseSuppressionEnabled) {
|
||||
await localAudio.setEffect(undefined);
|
||||
dispatch(setNoiseSuppressionEnabledState(false));
|
||||
logger.info('Noise suppression disabled.');
|
||||
} else {
|
||||
logger.warn(`Noise suppression enabled state already: ${enabled}`);
|
||||
}
|
||||
|
||||
await localAudio.setEffect(new NoiseSuppressionEffect());
|
||||
dispatch(setNoiseSuppressionEnabledState(true));
|
||||
logger.info('Noise suppression enabled.');
|
||||
|
||||
} else if (!enabled && noiseSuppressionEnabled) {
|
||||
await localAudio.setEffect(undefined);
|
||||
dispatch(setNoiseSuppressionEnabledState(false));
|
||||
logger.info('Noise suppression disabled.');
|
||||
} else {
|
||||
logger.warn(`Noise suppression enabled state already: ${enabled}`);
|
||||
}
|
||||
}));
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to set noise suppression enabled to: ${enabled}`,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import {
|
||||
@@ -9,9 +8,9 @@ import { connect } from '../../base/redux/functions';
|
||||
import {
|
||||
AbstractButton,
|
||||
type AbstractButtonProps
|
||||
|
||||
// @ts-ignore
|
||||
} from '../../base/toolbox/components';
|
||||
// @ts-ignore
|
||||
import { setOverflowMenuVisible } from '../../toolbox/actions';
|
||||
import { toggleNoiseSuppression } from '../actions';
|
||||
import { isNoiseSuppressionEnabled } from '../functions';
|
||||
|
||||
@@ -10,6 +10,7 @@ export function isOldJitsiMeetElectronApp() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const match = navigator.userAgent.match(/(JitsiMeet)\s*\/\s*((\d+)\.[^\s]*)/);
|
||||
|
||||
if (!Array.isArray(match) || match.length < 3) {
|
||||
|
||||
@@ -33,7 +33,6 @@ const BreakoutRoomParticipantItem = ({ item, room }: Props) => {
|
||||
return (
|
||||
<ParticipantItem
|
||||
displayName = { item.displayName || defaultRemoteDisplayName }
|
||||
isKnockingParticipant = { false }
|
||||
isModerator = { isParticipantModerator(item) }
|
||||
key = { item.jid }
|
||||
onPress = { onPress }
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -11,6 +10,7 @@ import ContextMenu from '../../../../../base/ui/components/web/ContextMenu';
|
||||
import ContextMenuItemGroup from '../../../../../base/ui/components/web/ContextMenuItemGroup';
|
||||
import { getBreakoutRooms } from '../../../../../breakout-rooms/functions';
|
||||
import { showOverflowDrawer } from '../../../../../toolbox/functions.web';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import SendToRoomButton from '../../../../../video-menu/components/web/SendToRoomButton';
|
||||
import { AVATAR_SIZE } from '../../../../constants';
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { hasRaisedHand } from '../../../base/participants';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { approveKnockingParticipant } from '../../../lobby/actions.native';
|
||||
import { showContextMenuReject } from '../../actions.native';
|
||||
import { MEDIA_STATE } from '../../constants';
|
||||
import { setKnockingParticipantApproval } from '../../../lobby/actions.native';
|
||||
|
||||
import ParticipantItem from './ParticipantItem';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
@@ -23,25 +19,26 @@ type Props = {
|
||||
|
||||
export const LobbyParticipantItem = ({ participant: p }: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
const admit = useCallback(() => dispatch(approveKnockingParticipant(p.id), [ dispatch ]));
|
||||
const openContextMenuReject = useCallback(() => dispatch(showContextMenuReject(p), [ dispatch ]));
|
||||
const admit = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, true), [ dispatch ]));
|
||||
const reject = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, false), [ dispatch ]));
|
||||
|
||||
return (
|
||||
<ParticipantItem
|
||||
audioMediaState = { MEDIA_STATE.NONE }
|
||||
displayName = { p.name }
|
||||
isKnockingParticipant = { true }
|
||||
local = { p.local }
|
||||
onPress = { openContextMenuReject }
|
||||
participant = { p }
|
||||
participantID = { p.id }
|
||||
raisedHand = { hasRaisedHand(p) }
|
||||
videoMediaState = { MEDIA_STATE.NONE }>
|
||||
key = { p.id }
|
||||
participantID = { p.id } >
|
||||
<Button
|
||||
accessibilityLabel = 'lobby.reject'
|
||||
labelKey = 'lobby.reject'
|
||||
onClick = { reject }
|
||||
style = { styles.lobbyButtonReject }
|
||||
type = { BUTTON_TYPES.DESTRUCTIVE } />
|
||||
<Button
|
||||
accessibilityLabel = 'lobby.admit'
|
||||
labelKey = 'lobby.admit'
|
||||
onClick = { admit }
|
||||
style = { styles.participantActionsButtonAdmit }
|
||||
style = { styles.lobbyButtonAdmit }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
</ParticipantItem>
|
||||
);
|
||||
|
||||
@@ -32,8 +32,8 @@ const LobbyParticipantList = () => {
|
||||
const title = (
|
||||
<View style = { styles.lobbyListDetails } >
|
||||
<Text style = { styles.lobbyListDescription }>
|
||||
{t('participantsPane.headings.waitingLobby',
|
||||
{ count: participants.length })}
|
||||
{ t('participantsPane.headings.waitingLobby',
|
||||
{ count: participants.length }) }
|
||||
</Text>
|
||||
{
|
||||
participants.length > 1 && (
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
@@ -152,7 +150,6 @@ class MeetingParticipantItem extends PureComponent<Props> {
|
||||
audioMediaState = { _audioMediaState }
|
||||
disableModeratorIndicator = { _disableModeratorIndicator }
|
||||
displayName = { _displayName }
|
||||
isKnockingParticipant = { false }
|
||||
isModerator = { _isModerator }
|
||||
local = { _local }
|
||||
onPress = { this._onPress }
|
||||
@@ -202,5 +199,3 @@ function mapStateToProps(state, ownProps): Object {
|
||||
|
||||
|
||||
export default translate(connect(mapStateToProps)(MeetingParticipantItem));
|
||||
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ function ParticipantItem({
|
||||
children,
|
||||
displayName,
|
||||
disableModeratorIndicator,
|
||||
isKnockingParticipant,
|
||||
isKnockingParticipant = false,
|
||||
isModerator,
|
||||
local,
|
||||
onPress,
|
||||
@@ -109,11 +109,11 @@ function ParticipantItem({
|
||||
numberOfLines = { 1 }
|
||||
style = { styles.participantName }>
|
||||
{ displayName }
|
||||
{local && ` (${t('chat.you')})` }
|
||||
{ local && ` (${t('chat.you')})` }
|
||||
</Text>
|
||||
</View>
|
||||
{isModerator && !disableModeratorIndicator
|
||||
&& <Text style = { styles.moderatorLabel }>{t('videothumbnail.moderator')}</Text>
|
||||
{ isModerator && !disableModeratorIndicator
|
||||
&& <Text style = { styles.moderatorLabel }>{ t('videothumbnail.moderator') }</Text>
|
||||
}
|
||||
</View>
|
||||
{
|
||||
@@ -121,8 +121,8 @@ function ParticipantItem({
|
||||
&& <>
|
||||
{ raisedHand && <RaisedHandIndicator /> }
|
||||
<View style = { styles.participantStatesContainer }>
|
||||
<View style = { styles.participantStateVideo }>{VideoStateIcons[videoMediaState]}</View>
|
||||
<View>{AudioStateIcons[audioMediaState]}</View>
|
||||
<View style = { styles.participantStateVideo }>{ VideoStateIcons[videoMediaState] }</View>
|
||||
<View>{ AudioStateIcons[audioMediaState] }</View>
|
||||
</View>
|
||||
</>
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user