Compare commits

...

23 Commits

Author SHA1 Message Date
Jonathan Lennox
357bbd1158 Load test: emulate Jitsi-Meet's lastN and selectParticipant behavior. (#8926) 2021-04-01 10:30:23 -04:00
Arnaud (Martient) Leherpeur
0ca47e9ffb fix (lang): update french and canadian french i18n
change "cryptage" to "chiffrement"
2021-04-01 08:29:12 -05:00
Дамян Минков
1123b4f2fe fix: Adds Portuguese to listed languages 2021-04-01 08:27:49 -05:00
tmoldovan8x8
1224597ede feat(e2ee): auto turns on e2ee when one participant enabled it 2021-04-01 12:34:01 +03:00
Avram Tudor
58b7663a97 Merge pull request #8866 from jitsi/tavram/sip-invite
feat(sipcall) implement sip invite
2021-04-01 11:39:31 +03:00
Christoph Settgast
cf8ab5e13b fix(lang) Differentiate prejoin and lobby better in German translation
Signed-off-by: Christoph Settgast <csett86@web.de>
2021-03-31 15:46:05 -05:00
Tudor-Ovidiu Avram
f99c919416 code review changes 2021-03-31 15:51:53 +03:00
Tudor-Ovidiu Avram
ae21a09bd6 feat(sipcall) implement sip invite 2021-03-31 09:53:55 +03:00
Tudor D. Pop
39011d8fd3 feat(virtual-background) persist settings 2021-03-30 23:27:44 +02:00
Johnny998
77f1a24344 Update main-sk.json
Translated a few missing strings.
2021-03-30 08:41:32 -05:00
tudordan7
3453e49182 fix(virtual-background): Hide scrollbar on loading action. 2021-03-30 13:43:57 +02:00
tmoldovan8x8
b1d7debfb9 feat(e2ee): adds sounds for e2ee enabling/disabling 2021-03-30 12:59:32 +03:00
Дамян Минков
b826fc1d5a fix: Correct some missing comas in config.js. 2021-03-30 08:51:43 +02:00
Jaya Allamsetty
c5626e99e9 chore(deps) lib-jitsi-meet@latest
* feat(stats): Get audio levels for the top 5 speakers only.

1249681a0e...43c589f409
2021-03-29 17:21:46 -04:00
Christoph Wiechert
ae28fcc12f Fix: used deprecated onmousewheel event
https://developer.mozilla.org/en-US/docs/Web/API/Element/mousewheel_event
2021-03-29 10:53:13 -05:00
chipechop
987760abbd Update main-it.json (#8795)
* Update main-it.json

Added roughly 20 missinig lines

* Update main-it.json

Fixed two typos, left behind...
2021-03-29 10:53:02 -05:00
KyungheeKo
a3b364f8d7 lang: Update korean translation (#8879)
* Update main-ko.json

update korean translation

* Update main-ko.json

fix comma error

* Update languages-ko.json

add korean translation
2021-03-29 10:52:50 -05:00
JohnProv
2ea317d721 Update main-nl.json (#8891)
* Update main-nl.json

Add missing keys for virtualBackground

* Update main-nl.json
2021-03-29 10:52:22 -05:00
Vlad Piersec
eb41a306a6 fix(lobby): Knocking participants list for small widths 2021-03-29 09:47:11 -05:00
Vlad Piersec
3426290bf2 fix(captions): Lift captions upper when invite box is shown & fix icon 2021-03-29 09:09:21 -05:00
Tudor D. Pop
dfd33521bf fix(virtual-background): Fixes upload virtual background on Firefox
Fixes: #8892
2021-03-29 14:28:22 +02:00
Hristo Terezov
be3bc75403 chore(deps) lib-jitsi-meet@latest
* fix(caps): features update event is not emitted.

0e180efdfa...1249681a0e
2021-03-26 17:43:54 -05:00
Jaya Allamsetty
4621fad832 fix(large-video): Always pin screenshare to large-video if it exists.
Set higher preference for screenshare over dominant speaker when trying to elect a participant for large-video. This prevents the dominant speaker from taking over the stage when a user toggles tile view on and off while a screenshare is in progress.
2021-03-26 09:14:03 -04:00
36 changed files with 585 additions and 193 deletions

View File

@@ -123,7 +123,7 @@ var config = {
// opusMaxAverageBitrate: 20000,
// Enables support for opus-red (redundancy for Opus).
// enableOpusRed: false
// enableOpusRed: false,
// Video
@@ -498,7 +498,7 @@ var config = {
// If set to true, it will prefer to use H.264 for P2P calls (if H.264
// is supported). This setting is deprecated, use preferredCodec instead.
// preferH264: true
// preferH264: true,
// Provides a way to set the video codec preference on the p2p connection. Acceptable
// codec values are 'VP8', 'VP9' and 'H264'.
@@ -540,7 +540,7 @@ var config = {
// The interval at which rtcstats will poll getStats, defaults to 1000ms.
// If the value is set to 0 getStats won't be polled and the rtcstats client
// will only send data related to RTCPeerConnection events.
// rtcstatsPolIInterval: 1000
// rtcstatsPolIInterval: 1000,
// Array of script URLs to load as lib-jitsi-meet "analytics handlers".
// scriptURLs: [
@@ -689,13 +689,13 @@ var config = {
// disableTileView: true,
// Hides the conference subject
// hideConferenceSubject: true
// hideConferenceSubject: true,
// Hides the conference timer.
// hideConferenceTimer: true,
// Hides the participants stats
// hideParticipantsStats: true
// hideParticipantsStats: true,
// Sets the conference subject
// subject: 'Conference Subject',

View File

@@ -9,7 +9,7 @@
.spinner {
margin: 30px;
}
.joining-message {
margin: 10px;
}
@@ -49,7 +49,7 @@
position: fixed;
top: 20;
transition: top 1s ease;
z-index: 100;
z-index: $toolbarZ + 1;
&.toolbox-visible {
// Same as toolbox subject position
@@ -62,31 +62,6 @@
padding: 15px
}
ul {
list-style-type: none;
padding: 0 15px 15px 15px;
li {
align-items: center;
display: flex;
flex-direction: row;
margin: 8px 0;
.details {
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-evenly;
margin: 0 30px 0 10px;
}
button {
align-self: unset;
margin: 0 5px;
}
}
}
button {
align-self: stretch;
margin: 8px 0;
@@ -116,3 +91,50 @@
}
}
}
.knocking-participants-container {
list-style-type: none;
max-height: 600px;
overflow-y: scroll;
padding: 0 15px 15px 15px;
}
.knocking-participant {
align-items: center;
display: flex;
flex-direction: row;
margin: 8px 0;
.details {
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-evenly;
margin: 0 30px 0 10px;
}
button {
align-self: unset;
margin: 0 5px;
}
}
@media (max-width: 300px) {
#knocking-participant-list {
margin: 0;
text-align: center;
width: 100%;
.avatar {
display: none;
}
}
.knocking-participant {
flex-direction: column;
.details {
margin: 0;
}
}
}

View File

@@ -1,10 +1,11 @@
.transcription-subtitles{
bottom: 10%;
.transcription-subtitles {
bottom: $newToolbarSize + 40px;
font-size: 16px;
font-weight: 1000;
left: 50%;
max-width: 50vw;
opacity: 0.80;
overflow-wrap: break-word;
pointer-events: none;
position: absolute;
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
@@ -14,6 +15,11 @@
transform: translateX(-50%);
z-index: $subtitlesZ;
&.lifted {
// Lift subtitle above toolbar+invite box.
bottom: $newToolbarSize + 112px + 40px;
}
span {
background: black;
}

View File

@@ -51,6 +51,10 @@
margin-right: 5px;
}
}
.modal-dialog-form .virtual-background-loading {
overflow: hidden;
}
.file-upload-btn {
display: none;
}

View File

@@ -1,27 +1,52 @@
{
"en": "영어",
"af": "",
"af": "아프리칸스어",
"ar": "아랍어",
"az": "아제르바이잔어",
"bg": "불가리어",
"cs": "체코어",
"ca": "카탈루냐어",
"cs": "체코어",
"da": "덴마크어",
"de": "독일어",
"el": "그리스어",
"enGB": "영어(영국)",
"eo": "에스페란토어",
"es": "스페인어",
"esUS": "스페인어(라틴 아메리카)",
"et": "에스토니아어",
"eu": "바스크어",
"fi": "핀란드어",
"fr": "프랑스어",
"frCA": "프랑스어(캐나다)",
"he": "히브리어",
"mr":"마라티어",
"hr": "크로아티아어",
"hu": "헝가리어",
"hy": "아르메니아어",
"id": "인도네시아어",
"it": "이탈리아어",
"ja": "일본어",
"kab": "커바일어",
"ko": "한국어",
"nb": "노르웨이어",
"oc": "",
"lt": "리투아니아어",
"ml": "말라얄람어",
"lv": "라트비아어",
"nl": "네덜란드어",
"oc": "오크어",
"fa": "페르시아어",
"pl": "폴란드어",
"ptBR": "포르투갈어(브라질)",
"ru": "러시아어",
"ro": "루마니아어",
"sc": "사르데냐어",
"sk": "슬로바키아어",
"sl": "슬로베니아어",
"sr": "세르비아어",
"sv": "스웨덴어",
"th": "태국어",
"tr": "터키어",
"uk": "우크라이나어",
"vi": "베트남어",
"zhCN": "중국어(중국)"
}
"zhCN": "중국어(중국)",
"zhTW": "중국어(대만)"
}

View File

@@ -34,6 +34,7 @@
"oc": "Occitan",
"fa": "Persian",
"pl": "Polish",
"pt": "Portuguese",
"ptBR": "Portuguese (Brazil)",
"ru": "Russian",
"ro": "Romanian",

View File

@@ -562,8 +562,8 @@
"linkCopied": "Link in die Zwischenablage kopiert",
"lookGood": "Ihr Mikrofon scheint zu funktionieren.",
"or": "oder",
"premeeting": "Vorraum",
"showScreen": "Konferenzvorraum aktivieren",
"premeeting": "Vorschau",
"showScreen": "Konferenzvorschau aktivieren",
"startWithPhone": "Mit Telefonaudio starten",
"screenSharingError": "Fehler bei Bildschirmfreigabe:",
"videoOnlyError": "Videofehler:",

View File

@@ -201,9 +201,9 @@
"dismiss": "Rejeter",
"displayNameRequired": "Bonjour ! Quel est votre nom ?",
"done": "Terminé",
"e2eeDescription": "Le cryptage de Bout-en-Bout est actuellement EXPERIMENTAL. Veuillez garder à l'esprit que l'activation du cryptage de Bout-en-Bout désactivera les services fournis côté serveur tels que : l'enregistrement, la diffusion en direct et la participation par téléphone. Gardez également à l'esprit que la réunion ne fonctionnera que pour les personnes qui se joignent à partir de navigateurs prenant en charge les flux insérables.",
"e2eeLabel": "Activer le cryptage de Bout-en-Bout",
"e2eeWarning": "ATTENTION : Tous les participants de cette réunion ne semblent pas prendre en charge le chiffrement de Bout-en-Bout. Si vous activez le cryptage, ils ne pourront ni vous voir, ni vous entendre.",
"e2eeDescription": "Le chiffrement de Bout-en-Bout est actuellement EXPERIMENTAL. Veuillez garder à l'esprit que l'activation du chiffrement de Bout-en-Bout désactivera les services fournis côté serveur tels que : l'enregistrement, la diffusion en direct et la participation par téléphone. Gardez également à l'esprit que la réunion ne fonctionnera que pour les personnes qui se joignent à partir de navigateurs prenant en charge les flux insérables.",
"e2eeLabel": "Activer le chiffrement de Bout-en-Bout",
"e2eeWarning": "ATTENTION : Tous les participants de cette réunion ne semblent pas prendre en charge le chiffrement de Bout-en-Bout. Si vous activez le chiffrement, ils ne pourront ni vous voir, ni vous entendre.",
"enterDisplayName": "Merci de saisir votre nom ici",
"error": "Erreur",
"externalInstallationMsg": "Vous devez installer notre extension de partage de bureau.",
@@ -326,7 +326,7 @@
"title": "Document partagé"
},
"e2ee": {
"labelToolTip": "L'audio et la vidéo de cette conférence sont cryptés de Bout-en-Bout"
"labelToolTip": "L'audio et la vidéo de cette conférence sont chiffrés de Bout-en-Bout"
},
"embedMeeting": {
"title": "Intégrer cette réunion"
@@ -757,7 +757,7 @@
"documentClose": "Fermer le document partagé",
"documentOpen": "Ouvrir le document partagé",
"download": "Télécharger nos applications",
"e2ee": "Cryptage de Bout-en-Bout",
"e2ee": "Chiffrement de Bout-en-Bout",
"embedMeeting": "Intégrer la réunion",
"enterFullScreen": "Afficher en plein écran",
"enterTileView": "Accéder au mode mosaïque",

View File

@@ -718,7 +718,7 @@
"join": "Toucher pour rejoindre",
"roomname": "Entrer le nom de la salle"
},
"appDescription": "Profitez de la conversation vidéo avec toute votre équipe. Allez-y, invitez tous ceux que vous connaissez. {{app}} est une solution 100 % libre de conférence vidéo entièrement cryptée que vous pouvez utiliser en tout temps et gratuitement, sans avoir besoin de compte.",
"appDescription": "Profitez de la conversation vidéo avec toute votre équipe. Allez-y, invitez tous ceux que vous connaissez. {{app}} est une solution 100 % libre de conférence vidéo entièrement chiffrée que vous pouvez utiliser en tout temps et gratuitement, sans avoir besoin de compte.",
"audioVideoSwitch": {
"audio": "Téléphone",
"video": "Vidéo"

View File

@@ -61,6 +61,7 @@
"today": "Oggi"
},
"chat": {
"enter": "Entra nella conversazione",
"error": "Errore: il tuo messaggio non è stato inviato. Motivo: {{error}}",
"fieldPlaceHolder": "Scrivi qui il tuo messaggio",
"messagebox": "Digitare un messaggio",
@@ -240,12 +241,19 @@
"muteEveryoneElseTitle": "Zittisco tutti eccetto {{whom}}?",
"muteEveryoneDialog": "Sei sicuro di voler zittire tutti? Non potrai riattivar loro il microfono, ma loro potranno farlo in qualsiasi momento.",
"muteEveryoneTitle": "Zittisco tutti?",
"muteEveryoneElsesVideoDialog": "Una volta spente le videocamere, non potrai riaccenderle, ma ogni partecipante potrà farlo in ogni momento.",
"muteEveryoneElsesVideoTitle": "Spegnere tutte le videocamere, tranne quella di {{whom}}?",
"muteEveryonesVideoDialog": "Sei sicuro di voler spegnere le videocamere di tutti? Non potrai riaccenderle, ma ogni partecipante potrà farlo in ogni momento.",
"muteEveryonesVideoTitle": "Vuoi spegnere le videocamere di tutti?",
"muteEveryoneSelf": "te stesso",
"muteEveryoneStartMuted": "Tutti cominciano a microfono spento, d'adesso in avanti",
"muteParticipantBody": "Non sarai in grado di riattivare il loro microfono, ma loro potranno riattivarlo in qualsiasi momento.",
"muteParticipantButton": "Zittisci",
"muteParticipantDialog": "Sei sicuro di voler zittire questo partecipante? Sarà lui a dovere riattivare l'audio, per parlare.",
"muteParticipantTitle": "Zittisco questo partecipante?",
"muteParticipantsVideoButton": "Spegnere videocamera",
"muteParticipantsVideoTitle": "Vuoi spegnere la videocamera di questo partecipante?",
"muteParticipantsVideoBody": "Una volta spenta la videocamera, non potrai riaccenderla, ma lui potrà riattivarla in qualsiasi momento.",
"Ok": "OK",
"passwordLabel": "La riunione è stata bloccata da un partecipante. Immetti la $t(lockRoomPassword) per collegarti, per favore.",
"passwordNotSupported": "Impostare una $t(lockRoomPassword) non è supportato.",
@@ -305,6 +313,7 @@
"unlockRoom": "Togli la $t(lockRoomPassword) alla riunione",
"user": "utente",
"userPassword": "password utente",
"videoLink": "Collegamento video",
"WaitForHostMsg": "La riunione <b>{{room}}</b> non è ancora cominciata. Se sei l'organizzatore, per favore autenticati. Altrimenti, aspetta l'arrivo dell'organizzatore.",
"WaitForHostMsgWOk": "La riunione <b>{{room}}</b> non è ancora cominciata. Se sei l'organizzatore, allora premi OK per autenticarti. Altrimenti, aspetta l'arrivo dell'organizzatore.",
"WaitingForHost": "In attesa dell'organizzatore...",
@@ -323,6 +332,11 @@
"embedMeeting": {
"title": "Incorpora questa riunione"
},
"virtualBackground": {
"title": "Sfondi",
"enableBlur": "Attiva sfocatura",
"removeBackground": "Togli sfondo"
},
"feedback": {
"average": "Media",
"bad": "Scadente",
@@ -483,6 +497,8 @@
"mutedTitle": "Hai l'audio disattivato!",
"mutedRemotelyTitle": "Ti è stato disattivato l'audio da {{participantDisplayName}}!",
"mutedRemotelyDescription": "Puoi sempre attivare il microfono, quando vuoi parlare. Spegni il microfono quando hai finito, per non introdurre rumori di fondo nella riunione.",
"videoMutedRemotelyTitle": "La videocamera ti è stata spenta da {{participantDisplayName}}!",
"videoMutedRemotelyDescription": "Puoi riaccenderla in qualsiasi momento.",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) è stata tolta da un altro partecipante",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) è stata messa da un altro partecipante",
"raisedHand": "{{name}} vorrebbe intervenire.",
@@ -715,12 +731,16 @@
"moreOptions": "Più opzioni",
"mute": "Attiva/disattiva audio",
"muteEveryone": "Zittisci tutti",
"muteEveryoneElse": "Zittisci tutti gli altri",
"muteEveryonesVideo": "Spegni le videocamere di tutti",
"muteEveryoneElsesVideo": "Spegni le videocamere di tutti gli altri",
"pip": "Attiva/disattiva immagine nellimmagine",
"privateMessage": "Invia messaggio privato",
"profile": "Modifica profilo",
"raiseHand": "Attiva/disattiva alzata di mano",
"recording": "Attiva/disattiva registrazione",
"remoteMute": "Zittisci partecipante",
"remoteVideoMute": "Spegni videocamera del partecipante",
"security": "Impostazioni di sicurezza",
"Settings": "Attiva/disattiva impostazioni",
"sharedvideo": "Attiva/disattiva condivisione YouTube",
@@ -733,9 +753,10 @@
"toggleCamera": "Cambia videocamera",
"toggleFilmstrip": "Attiva/disattiva pellicola",
"videomute": "Attiva/disattiva videocamera",
"videoblur": "Attiva/disattiva offuscamento video"
"selectBackground": "Scegli sfondo"
},
"addPeople": "Aggiungi persone alla chiamata",
"audioSettings": "Impostazioni audio",
"audioOnlyOff": "Disabilita modalità per banda limitata",
"audioOnlyOn": "Abilita modalità per banda limitata",
"audioRoute": "Scegli l'uscita audio",
@@ -765,6 +786,7 @@
"moreOptions": "Più opzioni",
"mute": "Attiva / Disattiva microfono",
"muteEveryone": "Zittisci tutti",
"muteEveryonesVideo": "Spegni videocamera di tutti",
"noAudioSignalTitle": "Non arrivano suoni dal tuo microfono!",
"noAudioSignalDesc": "Se non l'hai disabilitato intenzionalmente nelle impostazioni, prova a cambiare dispositivo di input.",
"noAudioSignalDescSuggestion": "Se non l'hai disabilitato intenzionalmente nelle impostazioni, prova a scegliere il dispositivo consigliato.",
@@ -793,7 +815,6 @@
"tileViewToggle": "Vedi tutti i partecipanti insieme, o uno solo",
"toggleCamera": "Cambia videocamera",
"videomute": "Attiva / Disattiva videocamera",
"startvideoblur": "Sfoca lo sfondo del video",
"stopvideoblur": "Non sfocare lo sfondo video"
},
"transcribing": {
@@ -849,13 +870,16 @@
"videothumbnail": {
"connectionInfo": "Info connessione",
"domute": "Disattiva audio",
"domuteOthers": "Zittisci tutti gli altri",
"domuteVideo": "Disattiva video",
"domuteOthers": "Zittisci tutti gli altri",
"domuteVideoOfOthers": "Disattiva video di tutti gli altri",
"flip": "Rifletti",
"grantModerator": "Autorizza moderatore",
"kick": "Espelli",
"moderator": "Moderatore",
"mute": "Il partecipante ha il microfono spento",
"muted": "Audio disattivato",
"videoMuted": "Video disattivato",
"remoteControl": "Avvia/ferma il controllo remoto",
"show": "Mostra in primo piano",
"videomute": "Il partecipante ha la videocamera spenta"

View File

@@ -57,10 +57,11 @@
"ongoingMeeting": "진행중인 회의",
"permissionButton": "설정 열기",
"permissionMessage": "앱에 회의를 나열하려면 캘린더 권한이 필요합니다",
"refresh": "달력 새로고침",
"refresh": "캘린더 새로고침",
"today": "오늘"
},
"chat": {
"enter": "채팅방 입장",
"error": "오류 : 메시지가 전송되지 않았습니다. 이유 : {{error}}",
"fieldPlaceHolder": "메세지를 여기에 입력하세요",
"messagebox": "메시지 입력",
@@ -212,7 +213,7 @@
"lockMessage": "회의를 비공개하지 못했습니다",
"lockRoom": "회의 추가 $t(lockRoomPasswordUppercase)",
"lockTitle": "비공개 실패",
"logoutQuestion": "로그 아웃하고 컨퍼런스를 중지하시겠습니까?",
"logoutQuestion": "로그아웃하고 컨퍼런스를 중지하시겠습니까?",
"logoutTitle": "로그아웃",
"maxUsersLimitReached": "회의의 최대 참가자 수에 도달했습니다. 회의 소유자에게 연락하거나 나중에 다시 시도하십시오!",
"maxUsersLimitReachedTitle": "최대 참가자 수에 도달했습니다.",
@@ -222,10 +223,23 @@
"micNotSendingDataTitle": "시스템 설정에 의해 마이크가 음소거되었습니다.",
"micPermissionDeniedError": "마이크를 사용할 수있는 권한을 부여하지 않았습니다. 회의에 계속 참여할 수는 있지만 다른 사람들은 듣지 않습니다. 검색 주소창의 카메라 버튼을 사용하여 문제를 해결하십시오.",
"micUnknownError": "알 수 없는 이유로 마이크를 사용할 수 없습니다",
"muteEveryoneElseDialog": "당신이 다른 사람들의 음소거를 해제 할 수는 없지만 언제든지 다른 사람들은 스스로 음소거를 해제할 수 있습니다.",
"muteEveryoneElseTitle": "{{whom}} 를 제외하고 전부 음소거 하시겠습니까?",
"muteEveryoneDialog": "모든 참가자를 음소거 하시겠습니까? 당신이 다른 사람들의 음소거를 해제 할 수는 없지만 언제든지 다른 사람들은 스스로 음소거를 해제할 수 있습니다.",
"muteEveryoneTitle": "모두 음소거 하시겠습니까?",
"muteEveryoneElsesVideoDialog": "당신이 다른 사람들의 카메라를 다시 켤 수는 없지만 언제든지 다른 사람들은 스스로 카메라를 켤 수 있습니다.",
"muteEveryoneElsesVideoTitle": "{{whom}} 를 제외하고 전부 카메라를 비활성화 하시겠습니까?",
"muteEveryonesVideoDialog": "모든 참가자의 카메라를 비활성화 하시겠습니까? 당신이 다른 사람들의 카메라를 다시 켤 수는 없지만 언제든지 다른 사람들은 스스로 카메라를 켤 수 있습니다.",
"muteEveryonesVideoDialogOk": "비활성화",
"muteEveryonesVideoTitle": "모든 카메라를 비활성화 하시겠습니까?",
"muteEveryoneStartMuted": "지금부터 모두 음소거 됩니다.",
"muteParticipantBody": "당신이 다른 사람들의 음소거를 해제 할 수는 없지만 언제든지 다른 사람들은 스스로 음소거를 해제할 수 있습니다.",
"muteParticipantButton": "음소거",
"muteParticipantDialog": "",
"muteParticipantDialog": "이 참가자를 음소거 하시겠습니까? 당신이 다른 사람들의 음소거를 해제 할 수는 없지만 언제든지 다른 사람들은 스스로 음소거를 해제할 수 있습니다.",
"muteParticipantTitle": "이 참가자를 음소거 하시겠습니까?",
"muteParticipantsVideoButton": "카메라 비활성화",
"muteParticipantsVideoTitle": "이 참가자의 카메라를 비활성화 하시겠습니까?",
"muteParticipantsVideoBody": "당신이 다른 사람들의 카메라를 다시 켤 수는 없지만 언제든지 다른 사람들은 스스로 카메라를 켤 수 있습니다.",
"Ok": "확인",
"passwordLabel": "잠긴 회의입니다. 회의에 참여하려면 비밀번호를 입력하세요.",
"passwordNotSupported": "회의 비밀번호 설정은 지원되지 않습니다",
@@ -233,7 +247,9 @@
"passwordRequired": "비밀번호 필수",
"popupError": "브라우저가이 사이트의 팝업 창을 차단하고 있습니다. 브라우저의 보안 설정에서 팝업을 활성화하고 다시 시도하십시오.",
"popupErrorTitle": "팝업 차단됨",
"readMore": "더보기",
"recording": "녹화",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "라이브 스트리밍 중에는 사용하실 수 없습니다.",
"recordingDisabledForGuestTooltip": "게스트는 녹화를 시작할 수 없습니다.",
"recordingDisabledTooltip": "녹화가 비활성화 되었습니다.",
"rejoinNow": "지금 재가입",
@@ -297,6 +313,14 @@
"documentSharing": {
"title": "문서 공유"
},
"virtualBackground": {
"title": "배경",
"enableBlur": "흐린 배경 활성화",
"removeBackground": "배경 제거",
"uploadImage": "이미지 업로드",
"pleaseWait": "잠시만 기다려주세요...",
"none": "없음"
},
"feedback": {
"average": "보통",
"bad": "나쁨",
@@ -322,12 +346,12 @@
"dialANumber": "회의에 참여하려면이 번호 중 하나를 누른 다음 PIN을 입력하십시오.",
"dialInConferenceID": "PIN:",
"dialInNotSupported": "죄송합니다. 현재 전화를 걸 수 없습니다.",
"dialInNumber": "Dial-in:",
"dialInNumber": "전화 접속:",
"dialInSummaryError": "지금 전화 접속 정보를 가져 오는 중에 오류가 발생했습니다. 나중에 다시 시도하십시오.",
"dialInTollFree": "",
"genericError": "일반적인 오류가 발생했습니다",
"inviteLiveStream": "이 회의의 실시간 스트림을 보려면이 링크를 클릭하십시오: {{url}}",
"invitePhone": "",
"invitePhone": "폰으로 참여하려면, 이것을 누르십시오: {{number}},,{{conferenceID}}#\n",
"invitePhoneAlternatives": "",
"inviteURLFirstPartGeneral": "회의에 초대되었습니다.",
"inviteURLFirstPartPersonal": "{{name}}이 회의에 초대하였습니다.\n",
@@ -406,9 +430,9 @@
},
"localRecording": {
"clientState": {
"off": "",
"on": "",
"unknown": ""
"off": "꺼짐",
"on": "켜짐",
"unknown": "알 수 없음"
},
"dialogTitle": "",
"duration": "",
@@ -417,7 +441,7 @@
"label": "",
"labelToolTip": "",
"localRecording": "",
"me": "",
"me": "",
"messages": {
"engaged": "",
"finished": "",
@@ -454,6 +478,8 @@
"mutedTitle": "음소거 상태입니다!",
"mutedRemotelyTitle": "{{participantDisplayName}}에 의해 음소거되었습니다!",
"mutedRemotelyDescription": "말할 준비가되면 언제든지 음소거를 해제 할 수 있습니다.",
"videoMutedRemotelyTitle": "{{participantDisplayName}}에 의해 카메라가 비활성화되었습니다!",
"videoMutedRemotelyDescription": "언제든지 카메라를 다시 켤 수 있습니다.",
"passwordRemovedRemotely": "다른 참가자가 $t(lockRoomPasswordUppercase)를 제거했습니다.",
"passwordSetRemotely": "다른 참가자가 $t(lockRoomPasswordUppercase)를 설정했습니다.",
"raisedHand": "{{name}}님이 말하고 싶어합니다.",
@@ -518,6 +544,12 @@
"sectionList": {
"pullToRefresh": "당겨서 새로고침"
},
"security": {
"about": "회의에 $t(lockRoomPassword)를 추가할 수 있습니다. 참가자는 회의에 참여하기 위해 $t(lockRoomPassword)를 입력해야합니다.",
"aboutReadOnly": "방장은 회의에 $t(lockRoomPassword)를 추가할 수 있습니다. 참가자는 회의에 참여하기 위해 $t(lockRoomPassword)를 입력해야합니다.",
"insecureRoomNameWarning": "원하지 않은 참가자가 회의에 참여할 수 있습니다. 보안 옵션 버튼을 통해 회의를 보호하는 것을 고려하십시오.",
"securityOptions": "보안 옵션"
},
"settings": {
"calendar": {
"about": "{{appName}} 캘린더 통합은 예정된 일정을 읽을 수 있도록 캘린더에 안전하게 액세스하는 데 사용됩니다.",
@@ -582,40 +614,49 @@
},
"toolbar": {
"accessibilityLabel": {
"audioOnly": "",
"audioOnly": "음성 전용 모드 전환",
"audioRoute": "음성 장비 선택하기",
"callQuality": "",
"cc": "",
"chat": "",
"document": "",
"callQuality": "비디오 품질 관리",
"cc": "자막 사용 전환",
"chat": "채팅창 보이기 전환",
"document": "문서 전환",
"feedback": "피드백 남기기",
"fullScreen": "",
"hangup": "",
"invite": "",
"kick": "",
"fullScreen": "전체 화면 전환",
"hangup": "떠나기",
"help": "도움말",
"invite": "사용자 초대",
"kick": "참가자 추방",
"lobbyButton": "로비 모드 활성화/비활성화",
"localRecording": "",
"lockRoom": "",
"lockRoom": "회의 비밀번호 전환",
"moreActions": "",
"moreActionsMenu": "",
"mute": "",
"mute": "음소거 전환",
"muteEveryone": "모두 음소거",
"muteEveryoneElse": "다른 사람 모두 음소거",
"muteEveryonesVideo": "모든 카메라 비활성화",
"muteEveryoneElsesVideo": "다른 사람의 카메라 모두 비활성화",
"pip": "",
"profile": "",
"raiseHand": "",
"recording": "",
"remoteMute": "",
"Settings": "",
"sharedvideo": "",
"shareRoom": "",
"shareYourScreen": "",
"privateMessage": "비공개 메세지 보내기",
"profile": "프로필 수정",
"raiseHand": "손 들기",
"recording": "녹화 전환",
"remoteMute": "참가자 음소거",
"Settings": "설정 전환",
"sharedvideo": "YouTube 비디오 공유 전환",
"shareRoom": "초대하기",
"shareYourScreen": "화면 공유 전환",
"shortcuts": "단축키 전환",
"show": "",
"speakerStats": "",
"tileView": "",
"speakerStats": "접속자 통계 전환",
"tileView": "타일뷰 전환",
"toggleCamera": "카메라 전환",
"videomute": "",
"videoblur": ""
"videomute": "비디오 비활성화 전환",
"videoblur": "",
"selectBackground": "배경 선택"
},
"addPeople": "통화에 사용자 추가",
"audioSettings": "오디오 설정",
"audioOnlyOff": "음성전용 모드 끄기",
"audioOnlyOn": "음성전용 모드 끄기",
"audioRoute": "음성 장비 선택하기",
@@ -632,6 +673,7 @@
"exitTileView": "타일보기 종료",
"feedback": "피드백 남기기",
"hangup": "떠나기",
"help": "도움말",
"invite": "초대",
"login": "로그인",
"logout": "로그아웃",
@@ -646,6 +688,7 @@
"profile": "프로필 수정",
"raiseHand": "말하기 요청/해제",
"raiseYourHand": "손 들어주세요",
"security": "보안 옵션",
"Settings": "설정",
"sharedvideo": "YouTube 비디오 공유",
"shareRoom": "초대하기",
@@ -655,11 +698,13 @@
"startSubtitles": "자막 시작",
"stopScreenSharing": "화면 공유 중지",
"stopSubtitles": "자막 중지",
"stopSharedVideo": "UouTube 비디오 공유 중지",
"stopSharedVideo": "YouTube 비디오 공유 중지",
"talkWhileMutedPopup": "음소거 상태입니다.",
"tileViewToggle": "타일뷰 전환",
"toggleCamera": "카메라 전환",
"videomute": "카메라 시작/중지",
"videoSettings": "비디오 설정",
"selectBackground": "배경 선택",
"startvideoblur": "내 배경을 흐리게",
"stopvideoblur": "배경 흐림 비활성화"
},

View File

@@ -338,6 +338,14 @@
"embedMeeting": {
"title": "Deze vergadering embedden"
},
"virtualBackground": {
"title": "Achtergronden",
"enableBlur": "Vervagen inschakelen",
"removeBackground": "Verwijder achtergrond",
"uploadImage": "Afbeelding uploaden",
"pleaseWait": "Even geduld a.u.b...",
"none": "Geen"
},
"feedback": {
"average": "Gemiddeld",
"bad": "Slecht",

View File

@@ -113,6 +113,7 @@
"maxEnabledResolution": "send max",
"more": "Zobraziť viac",
"packetloss": "Strata paketov:",
"participant_id": "ID účastníka:",
"quality": {
"good": "Dobré",
"inactive": "Neaktívne",
@@ -316,10 +317,13 @@
"e2ee": {
"labelToolTip": "Zvuková a obrazová komunikácia je koncovo šifrovaná"
},
"embedMeeting": {
"title": "Vložiť toto stretnutie"
},
"feedback": {
"average": "Priemerný",
"bad": "Zlý",
"detailsLabel": "Povedzte nám viac.",
"detailsLabel": "Povedzte nám viac",
"good": "Dobrý",
"rateExperience": "Ohodnoťte dojem",
"veryBad": "Veľmi zlý",
@@ -762,7 +766,8 @@
"toggleCamera": "Zmeniť kameru",
"videomute": "Vypnúť / Zapnúť kameru",
"startvideoblur": "Rozmazať pozadie",
"stopvideoblur": "Ukončiť rozmazanie pozadia"
"stopvideoblur": "Ukončiť rozmazanie pozadia",
"selectBackground": "Vybrať pozadie"
},
"transcribing": {
"ccButtonTooltip": "titulky vypnuť/zapnúť",
@@ -827,7 +832,9 @@
"muted": "Vypnutý mikrofón",
"remoteControl": "Vzdialené ovládanie",
"show": "Ukázať v popredí",
"videomute": "Účastník vypol kameru"
"videomute": "Účastník vypol kameru",
"videoMuted": "Vypnutá kamera",
"domuteVideoOfOthers": "Vypnúť kamery ostatným"
},
"welcomepage": {
"accessibilityLabel": {

View File

@@ -28,6 +28,7 @@
"shareInvite": "Share meeting invitation",
"shareLink": "Share the meeting link to invite others",
"shareStream": "Share the live streaming link",
"sip": "SIP: {{address}}",
"telephone": "Telephone: {{number}}",
"title": "Invite people to this meeting",
"yahooEmail": "Yahoo Email"

4
package-lock.json generated
View File

@@ -10513,8 +10513,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#0e180efdfa46a048ae1276cfbdf9cf8e051405c7",
"from": "github:jitsi/lib-jitsi-meet#0e180efdfa46a048ae1276cfbdf9cf8e051405c7",
"version": "github:jitsi/lib-jitsi-meet#2b94da12e81626c7b9deb24dd95f8ca2038a2f5a",
"from": "github:jitsi/lib-jitsi-meet#2b94da12e81626c7b9deb24dd95f8ca2038a2f5a",
"requires": {
"@jitsi/js-utils": "1.0.2",
"@jitsi/sdp-interop": "1.0.3",

View File

@@ -54,7 +54,7 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0e180efdfa46a048ae1276cfbdf9cf8e051405c7",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#2b94da12e81626c7b9deb24dd95f8ca2038a2f5a",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.21",
"moment": "2.29.1",

View File

@@ -1,3 +1,3 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.66671 3.66665H18.3334V18.3333H3.66671V3.66665ZM1.83337 3.66665C1.83337 2.65412 2.65419 1.83331 3.66671 1.83331H18.3334C19.3459 1.83331 20.1667 2.65412 20.1667 3.66665V18.3333C20.1667 19.3458 19.3459 20.1666 18.3334 20.1666H3.66671C2.65419 20.1666 1.83337 19.3458 1.83337 18.3333V3.66665ZM6.87046 13.9461C7.31535 14.1924 7.82776 14.3156 8.40771 14.3156C8.74932 14.3156 9.08298 14.2719 9.40871 14.1845C9.73443 14.0891 10.0204 13.962 10.2667 13.8031C10.4018 13.7237 10.4971 13.6363 10.5527 13.541C10.6163 13.4377 10.648 13.3185 10.648 13.1835C10.648 13.0166 10.6043 12.8776 10.517 12.7664C10.4296 12.6552 10.3223 12.5996 10.1952 12.5996C10.1078 12.5996 10.0204 12.6194 9.93304 12.6591C9.84565 12.6909 9.74237 12.7426 9.62321 12.8141C9.43254 12.9173 9.25776 13.0008 9.09887 13.0643C8.94793 13.1199 8.77315 13.1477 8.57454 13.1477C8.08198 13.1477 7.70065 12.9888 7.43054 12.6711C7.16837 12.3453 7.03729 11.8846 7.03729 11.2887C7.03729 10.685 7.16837 10.2242 7.43054 9.9064C7.70065 9.58067 8.08198 9.41781 8.57454 9.41781C8.7811 9.41781 8.96382 9.44959 9.12271 9.51315C9.2816 9.56876 9.44843 9.6482 9.62321 9.75148C9.71854 9.80709 9.81387 9.85476 9.90921 9.89448C10.0045 9.9342 10.0959 9.95406 10.1833 9.95406C10.3183 9.95406 10.4256 9.90242 10.505 9.79915C10.5924 9.68792 10.6361 9.54492 10.6361 9.37015C10.6361 9.11592 10.509 8.9054 10.2548 8.73856C10.0165 8.58762 9.7384 8.46845 9.42062 8.38106C9.11079 8.29367 8.80096 8.24998 8.49112 8.24998C7.90323 8.24998 7.38287 8.37709 6.93004 8.63131C6.47721 8.88554 6.12368 9.24701 5.86946 9.71573C5.62318 10.1765 5.50004 10.7088 5.50004 11.3126C5.50004 11.9163 5.61921 12.4446 5.85754 12.8975C6.09587 13.3503 6.43351 13.6999 6.87046 13.9461ZM12.6891 13.9461C13.134 14.1924 13.6464 14.3156 14.2264 14.3156C14.568 14.3156 14.9017 14.2719 15.2274 14.1845C15.5531 14.0891 15.8391 13.962 16.0854 13.8031C16.2204 13.7237 16.3158 13.6363 16.3714 13.541C16.4349 13.4377 16.4667 13.3185 16.4667 13.1835C16.4667 13.0166 16.423 12.8776 16.3356 12.7664C16.2483 12.6552 16.141 12.5996 16.0139 12.5996C15.9265 12.5996 15.8391 12.6194 15.7517 12.6591C15.6643 12.6909 15.5611 12.7426 15.4419 12.8141C15.2512 12.9173 15.0764 13.0008 14.9176 13.0643C14.7666 13.1199 14.5918 13.1477 14.3932 13.1477C13.9007 13.1477 13.5193 12.9888 13.2492 12.6711C12.9871 12.3453 12.856 11.8846 12.856 11.2887C12.856 10.685 12.9871 10.2242 13.2492 9.9064C13.5193 9.58067 13.9007 9.41781 14.3932 9.41781C14.5998 9.41781 14.7825 9.44959 14.9414 9.51315C15.1003 9.56876 15.2671 9.6482 15.4419 9.75148C15.5372 9.80709 15.6326 9.85476 15.7279 9.89448C15.8232 9.9342 15.9146 9.95406 16.002 9.95406C16.137 9.95406 16.2443 9.90242 16.3237 9.79915C16.4111 9.68792 16.4548 9.54492 16.4548 9.37015C16.4548 9.11592 16.3277 8.9054 16.0735 8.73856C15.8351 8.58762 15.5571 8.46845 15.2393 8.38106C14.9295 8.29367 14.6196 8.24998 14.3098 8.24998C13.7219 8.24998 13.2016 8.37709 12.7487 8.63131C12.2959 8.88554 11.9424 9.24701 11.6881 9.71573C11.4419 10.1765 11.3187 10.7088 11.3187 11.3126C11.3187 11.9163 11.4379 12.4446 11.6762 12.8975C11.9146 13.3503 12.2522 13.6999 12.6891 13.9461Z" />
</svg>
<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.33335 3.33335H16.6667V16.6667H3.33335V3.33335ZM1.66669 3.33335C1.66669 2.41288 2.41288 1.66669 3.33335 1.66669H16.6667C17.5872 1.66669 18.3334 2.41288 18.3334 3.33335V16.6667C18.3334 17.5872 17.5872 18.3334 16.6667 18.3334H3.33335C2.41288 18.3334 1.66669 17.5872 1.66669 16.6667V3.33335ZM6.24585 12.6784C6.6503 12.9022 7.11613 13.0142 7.64335 13.0142C7.95391 13.0142 8.25724 12.9745 8.55335 12.895C8.84946 12.8084 9.10946 12.6928 9.33335 12.5484C9.45613 12.4761 9.5428 12.3967 9.59335 12.31C9.65113 12.2161 9.68002 12.1078 9.68002 11.985C9.68002 11.8334 9.6403 11.707 9.56085 11.6059C9.48141 11.5047 9.38391 11.4542 9.26835 11.4542C9.18891 11.4542 9.10946 11.4722 9.03002 11.5084C8.95058 11.5372 8.85669 11.5842 8.74835 11.6492C8.57502 11.7431 8.41613 11.8189 8.27169 11.8767C8.13446 11.9272 7.97558 11.9525 7.79502 11.9525C7.34724 11.9525 7.00058 11.8081 6.75502 11.5192C6.51669 11.2231 6.39752 10.8042 6.39752 10.2625C6.39752 9.71363 6.51669 9.29474 6.75502 9.00585C7.00058 8.70974 7.34724 8.56169 7.79502 8.56169C7.9828 8.56169 8.14891 8.59058 8.29335 8.64835C8.4378 8.69891 8.58946 8.77113 8.74835 8.86502C8.83502 8.91558 8.92169 8.95891 9.00835 8.99502C9.09502 9.03113 9.17808 9.04919 9.25752 9.04919C9.3803 9.04919 9.4778 9.00224 9.55002 8.90835C9.62946 8.80724 9.66919 8.67724 9.66919 8.51835C9.66919 8.28724 9.55363 8.09585 9.32252 7.94419C9.10585 7.80696 8.85308 7.69863 8.56419 7.61919C8.28252 7.53974 8.00085 7.50002 7.71919 7.50002C7.18474 7.50002 6.71169 7.61558 6.30002 7.84669C5.88835 8.0778 5.56696 8.40641 5.33585 8.83252C5.11196 9.25141 5.00002 9.7353 5.00002 10.2842C5.00002 10.8331 5.10835 11.3134 5.32502 11.725C5.54169 12.1367 5.84863 12.4545 6.24585 12.6784ZM11.5356 12.6784C11.94 12.9022 12.4058 13.0142 12.9331 13.0142C13.2436 13.0142 13.547 12.9745 13.8431 12.895C14.1392 12.8084 14.3992 12.6928 14.6231 12.5484C14.7458 12.4761 14.8325 12.3967 14.8831 12.31C14.9408 12.2161 14.9697 12.1078 14.9697 11.985C14.9697 11.8334 14.93 11.707 14.8506 11.6059C14.7711 11.5047 14.6736 11.4542 14.5581 11.4542C14.4786 11.4542 14.3992 11.4722 14.3197 11.5084C14.2403 11.5372 14.1464 11.5842 14.0381 11.6492C13.8647 11.7431 13.7058 11.8189 13.5614 11.8767C13.4242 11.9272 13.2653 11.9525 13.0847 11.9525C12.637 11.9525 12.2903 11.8081 12.0447 11.5192C11.8064 11.2231 11.6872 10.8042 11.6872 10.2625C11.6872 9.71363 11.8064 9.29474 12.0447 9.00585C12.2903 8.70974 12.637 8.56169 13.0847 8.56169C13.2725 8.56169 13.4386 8.59058 13.5831 8.64835C13.7275 8.69891 13.8792 8.77113 14.0381 8.86502C14.1247 8.91558 14.2114 8.95891 14.2981 8.99502C14.3847 9.03113 14.4678 9.04919 14.5472 9.04919C14.67 9.04919 14.7675 9.00224 14.8397 8.90835C14.9192 8.80724 14.9589 8.67724 14.9589 8.51835C14.9589 8.28724 14.8433 8.09585 14.6122 7.94419C14.3956 7.80696 14.1428 7.69863 13.8539 7.61919C13.5722 7.53974 13.2906 7.50002 13.0089 7.50002C12.4745 7.50002 12.0014 7.61558 11.5897 7.84669C11.1781 8.0778 10.8567 8.40641 10.6256 8.83252C10.4017 9.25141 10.2897 9.7353 10.2897 10.2842C10.2897 10.8331 10.3981 11.3134 10.6147 11.725C10.8314 12.1367 11.1383 12.4545 11.5356 12.6784Z" />
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,6 +1,7 @@
// @flow
import UIEvents from '../../../../service/UI/UIEvents';
import { toggleE2EE } from '../../e2ee/actions';
import { NOTIFICATION_TIMEOUT, showNotification } from '../../notifications';
import { CALLING, INVITED } from '../../presence-status';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
@@ -207,7 +208,7 @@ StateListenerRegistry.register(
(conference, store) => {
if (conference) {
const propertyHandlers = {
'e2eeEnabled': (participant, value) => _e2eeUpdated(store, conference, participant.getId(), value),
'e2ee.enabled': (participant, value) => _e2eeUpdated(store, conference, participant.getId(), value),
'features_e2ee': (participant, value) =>
store.dispatch(participantUpdated({
conference,
@@ -270,12 +271,16 @@ StateListenerRegistry.register(
* @param {Function} dispatch - The Redux dispatch function.
* @param {Object} conference - The conference for which we got an update.
* @param {string} participantId - The ID of the participant from which we got an update.
* @param {boolean} newValue - The new value of the E2EE enabled status.
* @param {boolean} newValue - The new value of the E2EE enabled status.
* @returns {void}
*/
function _e2eeUpdated({ dispatch }, conference, participantId, newValue) {
const e2eeEnabled = newValue === 'true';
if (e2eeEnabled) {
dispatch(toggleE2EE(e2eeEnabled));
}
dispatch(participantUpdated({
conference,
id: participantId,
@@ -383,7 +388,7 @@ function _maybePlaySounds({ getState, dispatch }, action) {
*/
function _participantJoinedOrUpdated(store, next, action) {
const { dispatch, getState } = store;
const { participant: { avatarURL, e2eeEnabled, email, id, local, name, raisedHand } } = action;
const { participant: { avatarURL, email, id, local, name, raisedHand } } = action;
// Send an external update of the local participant's raised hand state
// if a new raised hand state is defined in the action.
@@ -398,16 +403,6 @@ function _participantJoinedOrUpdated(store, next, action) {
}
}
// Send an external update of the local participant's E2EE enabled state
// if a new state is defined in the action.
if (typeof e2eeEnabled !== 'undefined') {
if (local) {
const { conference } = getState()['features/base/conference'];
conference && conference.setLocalParticipantProperty('e2eeEnabled', e2eeEnabled);
}
}
// Allow the redux update to go through and compare the old avatar
// to the new avatar and emit out change events if necessary.
const result = next(action);

View File

@@ -0,0 +1,15 @@
// @flow
/**
* The identifier of the sound to be played when e2ee is disabled.
*
* @type {string}
*/
export const E2EE_OFF_SOUND_ID = 'E2EE_OFF_SOUND';
/**
* The identifier of the sound to be played when e2ee is enabled.
*
* @type {string}
*/
export const E2EE_ON_SOUND_ID = 'E2EE_ON_SOUND';

View File

@@ -1,12 +1,16 @@
// @flow
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
import { getCurrentConference } from '../base/conference';
import { getLocalParticipant, participantUpdated } from '../base/participants';
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import { TOGGLE_E2EE } from './actionTypes';
import { toggleE2EE } from './actions';
import { E2EE_OFF_SOUND_ID, E2EE_ON_SOUND_ID } from './constants';
import logger from './logger';
import { E2EE_OFF_SOUND_FILE, E2EE_ON_SOUND_FILE } from './sounds';
/**
* Middleware that captures actions related to E2EE.
@@ -16,10 +20,25 @@ import logger from './logger';
*/
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
switch (action.type) {
case APP_WILL_MOUNT:
dispatch(registerSound(
E2EE_OFF_SOUND_ID,
E2EE_OFF_SOUND_FILE));
dispatch(registerSound(
E2EE_ON_SOUND_ID,
E2EE_ON_SOUND_FILE));
break;
case APP_WILL_UNMOUNT:
dispatch(unregisterSound(E2EE_OFF_SOUND_ID));
dispatch(unregisterSound(E2EE_ON_SOUND_ID));
break;
case TOGGLE_E2EE: {
const conference = getCurrentConference(getState);
if (conference) {
if (conference && conference.isE2EEEnabled() !== action.enabled) {
logger.debug(`E2EE will be ${action.enabled ? 'enabled' : 'disabled'}`);
conference.toggleE2EE(action.enabled);
@@ -31,6 +50,10 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
id: participant.id,
local: true
}));
const soundID = action.enabled ? E2EE_ON_SOUND_ID : E2EE_OFF_SOUND_ID;
dispatch(playSound(soundID));
}
break;

View File

@@ -0,0 +1,15 @@
// @flow
/**
* The name of the bundled audio file which will be played when e2ee is disabled.
*
* @type {string}
*/
export const E2EE_OFF_SOUND_FILE = 'e2eeOff.mp3';
/**
* The name of the bundled audio file which will be played when e2ee is enabled.
*
* @type {string}
*/
export const E2EE_ON_SOUND_FILE = 'e2eeOn.mp3';

View File

@@ -3,7 +3,7 @@
import type { Dispatch } from 'redux';
import { getInviteURL } from '../base/connection';
import { getParticipants } from '../base/participants';
import { getLocalParticipant, getParticipants } from '../base/participants';
import { inviteVideoRooms } from '../videosipgw';
import {
@@ -18,7 +18,8 @@ import {
import {
getDialInConferenceID,
getDialInNumbers,
invitePeopleAndChatRooms
invitePeopleAndChatRooms,
inviteSipEndpoints
} from './functions';
import logger from './logger';
@@ -102,7 +103,9 @@ export function invite(
inviteServiceCallFlowsUrl
} = state['features/base/config'];
const inviteUrl = getInviteURL(state);
const { sipInviteUrl } = state['features/base/config'];
const { jwt } = state['features/base/jwt'];
const { name: displayName } = getLocalParticipant(state);
// First create all promises for dialing out.
const phoneNumbers
@@ -164,6 +167,20 @@ export function invite(
invitesLeftToSend
= invitesLeftToSend.filter(({ type }) => type !== 'videosipgw');
const sipEndpoints
= invitesLeftToSend.filter(({ type }) => type === 'sip');
conference && inviteSipEndpoints(
sipEndpoints,
sipInviteUrl,
jwt,
conference.options.name,
displayName
);
invitesLeftToSend
= invitesLeftToSend.filter(({ type }) => type !== 'sip');
return (
Promise.all(allInvitePromises)
.then(() => invitesLeftToSend));

View File

@@ -12,7 +12,8 @@ import {
getInviteResultsForQuery,
getInviteTypeCounts,
isAddPeopleEnabled,
isDialOutEnabled
isDialOutEnabled,
isSipInviteEnabled
} from '../../functions';
import logger from '../../logger';
@@ -38,6 +39,11 @@ export type Props = {
*/
_dialOutEnabled: boolean,
/**
* Whether or not to allow sip invites.
*/
_sipInviteEnabled: boolean,
/**
* The JWT token.
*/
@@ -96,7 +102,7 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
/**
* Invite people and numbers to the conference. The logic works by inviting
* numbers, people/rooms, and videosipgw in parallel. All invitees are
* numbers, people/rooms, sip endpoints and videosipgw in parallel. All invitees are
* stored in an array. As each invite succeeds, the invitee is removed
* from the array. After all invites finish, close the modal if there are
* no invites left to send. If any are left, that means an invite failed
@@ -214,7 +220,8 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
_dialOutEnabled: dialOutEnabled,
_jwt: jwt,
_peopleSearchQueryTypes: peopleSearchQueryTypes,
_peopleSearchUrl: peopleSearchUrl
_peopleSearchUrl: peopleSearchUrl,
_sipInviteEnabled: sipInviteEnabled
} = this.props;
const options = {
addPeopleEnabled,
@@ -222,7 +229,8 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
dialOutEnabled,
jwt,
peopleSearchQueryTypes,
peopleSearchUrl
peopleSearchUrl,
sipInviteEnabled
};
return getInviteResultsForQuery(query, options);
@@ -259,6 +267,7 @@ export function _mapStateToProps(state: Object) {
_dialOutEnabled: isDialOutEnabled(state),
_jwt: state['features/base/jwt'].jwt,
_peopleSearchQueryTypes: peopleSearchQueryTypes,
_peopleSearchUrl: peopleSearchUrl
_peopleSearchUrl: peopleSearchUrl,
_sipInviteEnabled: isSipInviteEnabled(state)
};
}

View File

@@ -285,7 +285,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
*/
_parseQueryResults(response = []) {
const { t, _dialOutEnabled } = this.props;
const users = response.filter(item => item.type !== 'phone');
const users = response.filter(item => item.type !== 'phone' && item.type !== 'sip');
const userDisplayItems = [];
for (const user of users) {
@@ -348,9 +348,25 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
};
});
const sipAddresses = response.filter(item => item.type === 'sip');
const sipDisplayItems = sipAddresses.map(sip => {
return {
filterValues: [
sip.address
],
content: t('addPeople.sip', { address: sip.address }),
description: '',
item: sip,
value: sip.address
};
});
return [
...userDisplayItems,
...numberDisplayItems
...numberDisplayItems,
...sipDisplayItems
];
}

View File

@@ -42,3 +42,9 @@ export const OUTGOING_CALL_RINGING_SOUND_ID = 'OUTGOING_CALL_RINGING_SOUND_ID';
* @type {string}
*/
export const OUTGOING_CALL_START_SOUND_ID = 'OUTGOING_CALL_START_SOUND_ID';
/**
* Regex for matching sip addresses.
*/
// eslint-disable-next-line max-len
export const SIP_ADDRESS_REGEX = /^[a-zA-Z]+(?:([^\s>:@]+)(?::([^\s@>]+))?@)?([\w\-.]+)(?::(\d+))?((?:;[^\s=?>;]+(?:=[^\s?;]+)?)*)(?:\?(([^\s&=>]+=[^\s&=>]+)(&[^\s&=>]+=[^\s&=>]+)*))?$/;

View File

@@ -10,6 +10,7 @@ import { toState } from '../base/redux';
import { doGetJSON, parseURIString } from '../base/util';
import { isVpaasMeeting } from '../billing-counter/functions';
import { SIP_ADDRESS_REGEX } from './constants';
import logger from './logger';
declare var $: Function;
@@ -122,6 +123,11 @@ export type GetInviteResultsOptions = {
*/
peopleSearchUrl: string,
/**
* Whether or not to check sip invites.
*/
sipInviteEnabled: boolean,
/**
* The jwt token to pass to the search service.
*/
@@ -149,6 +155,7 @@ export function getInviteResultsForQuery(
dialOutEnabled,
peopleSearchQueryTypes,
peopleSearchUrl,
sipInviteEnabled,
jwt
} = options;
@@ -233,6 +240,13 @@ export function getInviteResultsForQuery(
});
}
if (sipInviteEnabled && isASipAddress(text)) {
results.push({
type: 'sip',
address: text
});
}
return results;
});
}
@@ -374,6 +388,21 @@ export function isDialOutEnabled(state: Object): boolean {
&& conference && conference.isSIPCallingSupported();
}
/**
* Determines if inviting sip endpoints is enabled or not.
*
* @param {Object} state - Current state.
* @returns {boolean} Indication of whether dial out is currently enabled.
*/
export function isSipInviteEnabled(state: Object): boolean {
const { sipInviteUrl } = state['features/base/config'];
const { features = {} } = getLocalParticipant(state);
return state['features/base/jwt'].jwt
&& Boolean(sipInviteUrl)
&& String(features['sip-outbound-call']) === 'true';
}
/**
* Checks whether a string looks like it could be for a phone number.
*
@@ -392,6 +421,16 @@ function isMaybeAPhoneNumber(text: string): boolean {
return Boolean(digits.length);
}
/**
* Checks whether a string matches a sip address format.
*
* @param {string} text - The text to check.
* @returns {boolean} True if provided text matches a sip address format.
*/
function isASipAddress(text: string): boolean {
return SIP_ADDRESS_REGEX.test(text);
}
/**
* RegExp to use to determine if some text might be a phone number.
*
@@ -764,3 +803,47 @@ export function isSharingEnabled(sharingFeature: string) {
|| typeof interfaceConfig.SHARING_FEATURES === 'undefined'
|| (interfaceConfig.SHARING_FEATURES.length && interfaceConfig.SHARING_FEATURES.indexOf(sharingFeature) > -1);
}
/**
* Sends a post request to an invite service.
*
* @param {Array} inviteItems - The list of the "sip" type items to invite.
* @param {string} sipInviteUrl - The invite service that generates the invitation.
* @param {string} jwt - The jwt token.
* @param {string} roomName - The name to the conference.
* @param {string} displayName - The user display name.
* @returns {Promise} - The promise created by the request.
*/
export function inviteSipEndpoints( // eslint-disable-line max-params
inviteItems: Array<Object>,
sipInviteUrl: string,
jwt: string,
roomName: string,
displayName: string
): Promise<void> {
if (inviteItems.length === 0) {
return Promise.resolve();
}
return fetch(
`${sipInviteUrl}?token=${jwt}`,
{
body: JSON.stringify({
callParams: {
callUrlInfo: {
baseUrl: window.location.origin,
callName: roomName
}
},
sipClientParams: {
displayName,
sipAddress: inviteItems.map(item => item.address)
}
}),
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
}
);
}

View File

@@ -129,49 +129,46 @@ function _electLastVisibleRemoteVideo(tracks) {
* @returns {(string|undefined)}
*/
function _electParticipantInLargeVideo(state) {
// 1. If a participant is pinned, they will be shown in the LargeVideo (
// regardless of whether they are local or remote).
// 1. If a participant is pinned, they will be shown in the LargeVideo
// (regardless of whether they are local or remote).
const participants = state['features/base/participants'];
let participant = participants.find(p => p.pinned);
let id = participant && participant.id;
if (!id) {
// 2. No participant is pinned so get the dominant speaker. But the
// local participant won't be displayed in LargeVideo even if she is
// the dominant speaker.
participant = participants.find(p => p.dominantSpeaker && !p.local);
id = participant && participant.id;
if (!id) {
// 3. There is no dominant speaker so select the remote participant
// who last had visible video.
const tracks = state['features/base/tracks'];
const videoTrack = _electLastVisibleRemoteVideo(tracks);
id = videoTrack && videoTrack.participantId;
if (!id) {
// 4. It's possible there is no participant with visible video.
// This can happen for a number of reasons:
// - there is only one participant (i.e. the local user),
// - other participants joined with video muted.
// As a last resort, pick the last participant who joined the
// conference (regardless of whether they are local or
// remote).
//
// HOWEVER: We don't want to show poltergeist or other bot type participants on stage
// automatically, because it's misleading (users may think they are already
// joined and maybe speaking).
for (let i = participants.length; i > 0 && !participant; i--) {
const p = participants[i - 1];
!p.botType && (participant = p);
}
id = participant && participant.id;
}
}
if (participant) {
return participant.id;
}
return id;
// 2. Next, pick the most recent remote screenshare that was added to the conference.
const remoteScreenShares = state['features/video-layout'].remoteScreenShares;
if (remoteScreenShares?.length) {
return remoteScreenShares[remoteScreenShares.length - 1];
}
// 3. Next, pick the dominant speaker (other than self).
participant = participants.find(p => p.dominantSpeaker && !p.local);
if (participant) {
return participant.id;
}
// 4. Next, pick the most recent participant with video.
const tracks = state['features/base/tracks'];
const videoTrack = _electLastVisibleRemoteVideo(tracks);
if (videoTrack) {
return videoTrack.participantId;
}
// 5. As a last resort, select the participant that joined last (other than poltergist or other bot type
// participants).
for (let i = participants.length; i > 0 && !participant; i--) {
const p = participants[i - 1];
!p.botType && (participant = p);
}
if (participant) {
return participant.id;
}
return participants.find(p => p.local)?.id;
}

View File

@@ -42,9 +42,11 @@ class KnockingParticipantList extends AbstractKnockingParticipantList<Props> {
<span className = 'title'>
{ t('lobby.knockingParticipantList') }
</span>
<ul>
<ul className = 'knocking-participants-container'>
{ _participants.map(p => (
<li key = { p.id }>
<li
className = 'knocking-participant'
key = { p.id }>
<Avatar
displayName = { p.name }
size = { 48 }

View File

@@ -689,7 +689,7 @@ export function resume() {
area.mouseup(event => dispatch(mouseClicked(EVENTS.mouseup, event)));
area.dblclick(event => dispatch(mouseClicked(EVENTS.mousedblclick, event)));
area.contextmenu(() => false);
area[0].onmousewheel = event => {
area[0].onwheel = event => {
event.preventDefault();
event.stopPropagation();
dispatch(mouseScrolled(event));
@@ -739,7 +739,7 @@ export function pause() {
area.off('mousedown');
area.off('mousemove');
area.off('mouseup');
area[0].onmousewheel = undefined;
area[0].onwheel = undefined;
}
$(window).off('keydown');

View File

@@ -7,9 +7,17 @@ import { connect } from '../../base/redux';
import {
_abstractMapStateToProps,
AbstractCaptions,
type AbstractCaptionsProps as Props
type AbstractCaptionsProps
} from './AbstractCaptions';
type Props = {
/**
* Whether the subtitles container is lifted above the invite box.
*/
_isLifted: boolean
} & AbstractCaptionsProps;
/**
* React {@code Component} which can display speech-to-text results from
* Jigasi as subtitles.
@@ -45,12 +53,30 @@ class Captions
*/
_renderSubtitlesContainer(
paragraphs: Array<React$Element<*>>): React$Element<*> {
const className = this.props._isLifted ? 'transcription-subtitles lifted' : 'transcription-subtitles';
return (
<div className = 'transcription-subtitles' >
<div className = { className } >
{ paragraphs }
</div>
);
}
}
export default connect(_abstractMapStateToProps)(Captions);
/**
* Maps (parts of) the redux state to the associated {@code }'s
* props.
*
* @param {Object} state - The redux state.
* @private
* @returns {Object}
*/
function mapStateToProps(state) {
return {
..._abstractMapStateToProps(state),
_isLifted: state['features/base/participants'].length < 2
};
}
export default connect(mapStateToProps)(Captions);

View File

@@ -139,7 +139,7 @@ function VirtualBackground({ dispatch, t }: Props) {
titleKey = { 'virtualBackground.title' }
width = 'small'>
{loading ? (
<div>
<div className = 'virtual-background-loading'>
<span className = 'loading-content-text'>{t('virtualBackground.pleaseWait')}</span>
<Spinner
isCompleting = { false }

View File

@@ -25,12 +25,13 @@ export function checkBlurSupport() {
* @param {Blob} blob - The link to add info with.
* @returns {Promise<string>}
*/
export const blobToData = (blob: Blob): Promise<string> => new Promise(resolve => {
const reader = new FileReader();
export const blobToData = (blob: Blob): Promise<string> =>
new Promise(resolve => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result.toString());
reader.readAsDataURL(blob);
});
reader.onloadend = () => resolve(reader.result.toString());
reader.readAsDataURL(blob);
});
/**
* Convert blob to base64.
@@ -52,28 +53,35 @@ export const toDataURL = async (url: string) => {
* @param {Object} base64image - Base64 image extraction.
* @param {number} width - Value for resizing the image width.
* @param {number} height - Value for resizing the image height.
* @returns {Object} Returns the canvas output.
* @returns {Promise<string>}
*
*/
export async function resizeImage(base64image: any, width: number = 1920, height: number = 1080) {
const img = document.createElement('img');
export function resizeImage(base64image: any, width: number = 1920, height: number = 1080): Promise<string> {
img.src = base64image;
/* eslint-disable no-empty-function */
img.onload = await function() {};
// In order to work on Firefox browser we need to handle the asynchronous nature of image loading; We need to use
// a promise mechanism. The reason why it 'works' without this mechanism in Chrome is actually 'by accident' because
// the image happens to be in the cache and the browser is able to deliver the uncompressed/decoded image
// before using the image in the drawImage call.
return new Promise(resolve => {
const img = document.createElement('img');
// Create an off-screen canvas.
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
img.onload = function() {
// Create an off-screen canvas.
const canvas = document.createElement('canvas');
// Set its dimension to target size.
canvas.width = width;
canvas.height = height;
// Set its dimension to target size.
const context = canvas.getContext('2d');
// Draw source image into the off-screen canvas.
// TODO: keep aspect ratio and implement object-fit: cover.
ctx.drawImage(img, 0, 0, width, height);
canvas.width = width;
canvas.height = height;
// Encode image to data-uri with base64 version of compressed image.
return canvas.toDataURL('image/jpeg', 0.5);
// Draw source image into the off-screen canvas.
// TODO: keep aspect ratio and implement object-fit: cover.
context.drawImage(img, 0, 0, width, height);
// Encode image to data-uri with base64 version of compressed image.
resolve(canvas.toDataURL('image/jpeg', 0.5));
};
img.src = base64image;
});
}

View File

@@ -1,9 +1,16 @@
// @flow
import { ReducerRegistry } from '../base/redux';
import { PersistenceRegistry, ReducerRegistry } from '../base/redux';
import { BACKGROUND_ENABLED, SET_VIRTUAL_BACKGROUND } from './actionTypes';
const STORE_NAME = 'features/virtual-background';
/**
* Sets up the persistence of the feature {@code virtual-background}.
*/
PersistenceRegistry.register(STORE_NAME, true);
/**
* Reduces redux actions which activate/deactivate virtual background image, or
* indicate if the virtual image background is activated/deactivated. The
@@ -15,7 +22,7 @@ import { BACKGROUND_ENABLED, SET_VIRTUAL_BACKGROUND } from './actionTypes';
* @returns {State} The next redux state that is the result of reducing the
* specified action.
*/
ReducerRegistry.register('features/virtual-background', (state = {}, action) => {
ReducerRegistry.register(STORE_NAME, (state = {}, action) => {
const { virtualSource, isVirtualBackground, backgroundEffectEnabled } = action;
switch (action.type) {

View File

@@ -3,6 +3,7 @@ import 'jquery';
import { setConfigFromURLParams } from '../../react/features/base/config/functions';
import { parseURLParams } from '../../react/features/base/util/parseURLParams';
import { parseURIString } from '../../react/features/base/util/uri';
import { validateLastNLimits, limitLastN } from '../../react/features/base/lastn/functions';
setConfigFromURLParams(config, {}, {}, window.location);
@@ -107,12 +108,35 @@ function updateMaxFrameHeight() {
}
}
/**
* Simple emulation of jitsi-meet's lastN behavior
*/
function updateLastN() {
let lastN = typeof config.channelLastN === 'undefined' ? -1 : config.channelLastN;
const limitedLastN = limitLastN(numParticipants, validateLastNLimits(config.lastNLimits));
if (limitedLastN !== undefined) {
lastN = lastN === -1 ? limitedLastN : Math.min(limitedLastN, lastN);
}
if (lastN === room.getLastN()) {
return;
}
room.setLastN(lastN);
}
/**
*
*/
function setNumberOfParticipants() {
$('#participants').text(numParticipants);
/* jitsi-meet's current Tile View behavior. */
const ids = room.getParticipants().map(participant => participant.id);
room.selectParticipants(ids);
updateMaxFrameHeight();
updateLastN();
}
/**
@@ -194,6 +218,16 @@ function onStartMuted() {
}, 2000);
}
/**
*
* @param id
*/
function onUserJoined(id) {
numParticipants++;
setNumberOfParticipants();
remoteTracks[id] = [];
}
/**
*
* @param id
@@ -224,11 +258,7 @@ function onConnectionSuccess() {
room.on(JitsiMeetJS.events.conference.STARTED_MUTED, onStartMuted);
room.on(JitsiMeetJS.events.conference.TRACK_ADDED, onRemoteTrack);
room.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, onConferenceJoined);
room.on(JitsiMeetJS.events.conference.USER_JOINED, id => {
numParticipants++;
setNumberOfParticipants();
remoteTracks[id] = [];
});
room.on(JitsiMeetJS.events.conference.USER_JOINED, onUserJoined);
room.on(JitsiMeetJS.events.conference.USER_LEFT, onUserLeft);
const devices = [];

BIN
sounds/e2eeOff.mp3 Normal file

Binary file not shown.

BIN
sounds/e2eeOn.mp3 Normal file

Binary file not shown.