mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-09-12 08:18:41 +00:00
Compare commits
54 Commits
4237
...
feat/ui-4-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
402eed5150 | ||
|
|
b3bb0725ad | ||
|
|
7d845a4982 | ||
|
|
9d1ea76162 | ||
|
|
22b5bfcea4 | ||
|
|
b0188a7184 | ||
|
|
53281c2d42 | ||
|
|
3da1b65757 | ||
|
|
0e5091adba | ||
|
|
f376542441 | ||
|
|
bd65108692 | ||
|
|
546b0abe32 | ||
|
|
62ad7d3451 | ||
|
|
5bc3128c71 | ||
|
|
b91d6b97a9 | ||
|
|
ce812591f9 | ||
|
|
f32140c4b7 | ||
|
|
7d513738d2 | ||
|
|
8d1bde3cb1 | ||
|
|
95825dcdd7 | ||
|
|
a61f272303 | ||
|
|
3538761543 | ||
|
|
f22d5ed629 | ||
|
|
f30dd9d881 | ||
|
|
4a3cd2596a | ||
|
|
4e1f42a665 | ||
|
|
1fff5d2567 | ||
|
|
bbcc40a97e | ||
|
|
bbf76296ed | ||
|
|
e0b3a81a41 | ||
|
|
1c122705bf | ||
|
|
29c16e42bd | ||
|
|
8a19a34d19 | ||
|
|
d5832f226d | ||
|
|
4cfc8cd7a2 | ||
|
|
873ede0e06 | ||
|
|
f73e9947c0 | ||
|
|
82711b3f23 | ||
|
|
2f841fab73 | ||
|
|
b3a2905849 | ||
|
|
5f579e9a15 | ||
|
|
ea2ea89ef7 | ||
|
|
a5f17a8033 | ||
|
|
4c6e9e7788 | ||
|
|
a7e0df2623 | ||
|
|
da9a70129e | ||
|
|
6d3d15a64b | ||
|
|
b10a45bf98 | ||
|
|
858a3d953c | ||
|
|
1ff27b7298 | ||
|
|
bc43f00d28 | ||
|
|
bfd5db355d | ||
|
|
a4ca247056 | ||
|
|
eac891585b |
2
Makefile
2
Makefile
@@ -74,7 +74,7 @@ deploy-rnnoise-binary:
|
||||
|
||||
deploy-css:
|
||||
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
|
||||
$(CLEANCSS) $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
|
||||
$(CLEANCSS) --skip-rebase $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
|
||||
rm $(STYLES_BUNDLE)
|
||||
|
||||
deploy-local:
|
||||
|
||||
@@ -147,6 +147,7 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
.setWelcomePageEnabled(true)
|
||||
.setServerURL(buildURL(defaultURL))
|
||||
.setFeatureFlag("call-integration.enabled", false)
|
||||
.setFeatureFlag("resolution", 360)
|
||||
.setFeatureFlag("server-url-change.enabled", !configurationByRestrictions)
|
||||
.build();
|
||||
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="false">localhost</domain>
|
||||
<domain includeSubdomains="false">10.0.2.2</domain>
|
||||
</domain-config>
|
||||
<network-security-config>
|
||||
<base-config>
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
<certificates src="user" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="false">localhost</domain>
|
||||
<domain includeSubdomains="false">10.0.2.2</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
|
||||
13
config.js
13
config.js
@@ -44,6 +44,10 @@ var config = {
|
||||
//
|
||||
|
||||
testing: {
|
||||
// Disables the End to End Encryption feature. Useful for debugging
|
||||
// issues related to insertable streams.
|
||||
// disableE2EE: false,
|
||||
|
||||
// P2P test mode disables automatic switching to P2P when there are 2
|
||||
// participants in the conference.
|
||||
p2pTestMode: false
|
||||
@@ -107,6 +111,11 @@ var config = {
|
||||
// participants and to enable it back a reload is needed.
|
||||
// startSilent: false
|
||||
|
||||
// Sets the preferred target bitrate for the Opus audio codec by setting its
|
||||
// 'maxaveragebitrate' parameter. Currently not available in p2p mode.
|
||||
// Valid values are in the range 6000 to 510000
|
||||
// opusMaxAvgBitrate: 20000,
|
||||
|
||||
// Video
|
||||
|
||||
// Sets the preferred resolution (height) for local video. Defaults to 720.
|
||||
@@ -306,7 +315,7 @@ var config = {
|
||||
// When 'true', it shows an intermediate page before joining, where the user can configure their devices.
|
||||
// prejoinPageEnabled: false,
|
||||
|
||||
// If true, shows the unsafe roon name warning label when a room name is
|
||||
// If true, shows the unsafe room name warning label when a room name is
|
||||
// deemed unsafe (due to the simplicity in the name) and a password is not
|
||||
// set or the lobby is not enabled.
|
||||
// enableInsecureRoomNameWarning: false,
|
||||
@@ -361,7 +370,7 @@ var config = {
|
||||
// The STUN servers that will be used in the peer to peer connections
|
||||
stunServers: [
|
||||
|
||||
// { urls: 'stun:jitsi-meet.example.com:4446' },
|
||||
// { urls: 'stun:jitsi-meet.example.com:3478' },
|
||||
{ urls: 'stun:meet-jit-si-turnrelay.jitsi.net:443' }
|
||||
]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* global APP, JitsiMeetJS, config */
|
||||
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import Logger from 'jitsi-meet-logger';
|
||||
import { jitsiLocalStorage } from 'js-utils';
|
||||
|
||||
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
||||
import {
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
JitsiConnectionErrors,
|
||||
JitsiConnectionEvents
|
||||
} from './react/features/base/lib-jitsi-meet';
|
||||
import { setPrejoinDisplayNameRequired } from './react/features/prejoin/actions';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
@@ -113,6 +114,10 @@ function connect(id, password, roomName) {
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailedHandler);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.DISPLAY_NAME_REQUIRED,
|
||||
displayNameRequiredHandler
|
||||
);
|
||||
|
||||
/* eslint-disable max-params */
|
||||
/**
|
||||
@@ -166,6 +171,14 @@ function connect(id, password, roomName) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the display name for the prejoin screen as required.
|
||||
* This can happen if a user tries to join a room with lobby enabled.
|
||||
*/
|
||||
function displayNameRequiredHandler() {
|
||||
APP.store.dispatch(setPrejoinDisplayNameRequired());
|
||||
}
|
||||
|
||||
checkForAttachParametersAndConnect(id, password, connection);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -96,6 +96,11 @@
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.prejoin-dialog-btn.primary,
|
||||
.action-btn.prejoin-dialog-btn.text {
|
||||
width: 310px;
|
||||
}
|
||||
}
|
||||
|
||||
.prejoin-dialog-callout {
|
||||
|
||||
@@ -1,22 +1,90 @@
|
||||
/**
|
||||
* Shared style for full screen local track based dialogs/modals.
|
||||
*/
|
||||
.premeeting-screen,
|
||||
.preview-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.premeeting-screen {
|
||||
align-items: stretch;
|
||||
background: #1C2025;
|
||||
bottom: 0;
|
||||
background: radial-gradient(50% 50% at 50% 50%, #5D95C7 0%, #376288 100%), #FFFFFF;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1.3em;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: $toolbarZ + 1;
|
||||
|
||||
.action-btn {
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
margin-top: 16px;
|
||||
padding: 7px 16px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 286px;
|
||||
|
||||
&.primary {
|
||||
background: #0376DA;
|
||||
border: 1px solid #0376DA;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background: transparent;
|
||||
border: 1px solid #5E6D7A;
|
||||
}
|
||||
|
||||
&.text {
|
||||
width: auto;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: #5E6D7A;
|
||||
border: 1px solid #5E6D7A;
|
||||
color: #AFB6BC;
|
||||
cursor: initial;
|
||||
|
||||
.icon {
|
||||
& > svg {
|
||||
fill: #AFB6BC;
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
border-left: 1px solid #AFB6BC;
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
align-items: center;
|
||||
border-left: 1px solid #fff;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-overlay {
|
||||
background-image: linear-gradient(transparent, black);
|
||||
z-index: $toolbarZ + 1;
|
||||
}
|
||||
|
||||
.content {
|
||||
align-items: center;
|
||||
background-image: linear-gradient(transparent, black);
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
@@ -97,66 +165,6 @@
|
||||
color: $defaultWarningColor;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
margin-top: 16px;
|
||||
padding: 7px 16px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 286px;
|
||||
|
||||
&.primary {
|
||||
background: #0376DA;
|
||||
border: 1px solid #0376DA;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background: transparent;
|
||||
border: 1px solid #5E6D7A;
|
||||
}
|
||||
|
||||
&.text {
|
||||
width: auto;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: #5E6D7A;
|
||||
border: 1px solid #5E6D7A;
|
||||
color: #AFB6BC;
|
||||
cursor: initial;
|
||||
|
||||
.icon {
|
||||
& > svg {
|
||||
fill: #AFB6BC;
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
border-left: 1px solid #AFB6BC;
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
align-items: center;
|
||||
border-left: 1px solid #fff;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-btn-container {
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
font-size: 14px;
|
||||
color: #6FB1EA;
|
||||
}
|
||||
|
||||
& > :first-child:not(:last-child) {
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
debian/jitsi-meet-turnserver.postinst
vendored
4
debian/jitsi-meet-turnserver.postinst
vendored
@@ -49,7 +49,7 @@ case "$1" in
|
||||
# nothing to do
|
||||
echo "------------------------------------------------"
|
||||
echo ""
|
||||
echo "turnserver is listening on tcp 4445 as other nginx sites use port 443"
|
||||
echo "turnserver is listening on tcp 5349 as other nginx sites use port 443"
|
||||
echo ""
|
||||
echo "------------------------------------------------"
|
||||
NGINX_MULTIPLEXING="false"
|
||||
@@ -152,7 +152,7 @@ case "$1" in
|
||||
PROSODY_HOST_CONFIG="/etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua"
|
||||
if [ -f $PROSODY_HOST_CONFIG ] ; then
|
||||
# If we are not multiplexing we need to change the port in prosody config
|
||||
sed -i 's/"443"/"4445"/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/"443"/"5349"/g' $PROSODY_HOST_CONFIG
|
||||
invoke-rc.d prosody restart || true
|
||||
fi
|
||||
fi
|
||||
|
||||
2
debian/jitsi-meet-web-config.postinst
vendored
2
debian/jitsi-meet-web-config.postinst
vendored
@@ -98,7 +98,7 @@ case "$1" in
|
||||
-reqexts SAN \
|
||||
-extensions SAN \
|
||||
-config <(cat /etc/ssl/openssl.cnf \
|
||||
<(printf '[SAN]\nsubjectAltName=DNS:localhost,DNS:$JVB_HOSTNAME,IP:$JVB_HOSTNAME'))
|
||||
<(printf "[SAN]\nsubjectAltName=DNS:localhost,DNS:$JVB_HOSTNAME"))
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ muc_mapper_domain_base = "jitmeet.example.com";
|
||||
turncredentials_secret = "__turnSecret__";
|
||||
|
||||
turncredentials = {
|
||||
{ type = "stun", host = "jitmeet.example.com", port = "4446" },
|
||||
{ type = "turn", host = "jitmeet.example.com", port = "4446", transport = "udp" },
|
||||
{ type = "stun", host = "jitmeet.example.com", port = "3478" },
|
||||
{ type = "turn", host = "jitmeet.example.com", port = "3478", transport = "udp" },
|
||||
{ type = "turns", host = "jitmeet.example.com", port = "443", transport = "tcp" }
|
||||
};
|
||||
|
||||
|
||||
@@ -5,14 +5,31 @@ static-auth-secret=__turnSecret__
|
||||
realm=jitsi-meet.example.com
|
||||
cert=/etc/jitsi/meet/jitsi-meet.example.com.crt
|
||||
pkey=/etc/jitsi/meet/jitsi-meet.example.com.key
|
||||
|
||||
no-multicast-peers
|
||||
no-cli
|
||||
no-loopback-peers
|
||||
no-tcp-relay
|
||||
no-tcp
|
||||
listening-port=4446
|
||||
tls-listening-port=4445
|
||||
listening-port=3478
|
||||
tls-listening-port=5349
|
||||
external-ip=__external_ip_address__
|
||||
no-tlsv1
|
||||
no-tlsv1_1
|
||||
# https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=intermediate&openssl=1.1.0g&guideline=5.4
|
||||
cipher-list=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
|
||||
|
||||
denied-peer-ip=0.0.0.0-0.255.255.255
|
||||
denied-peer-ip=10.0.0.0-10.255.255.255
|
||||
denied-peer-ip=100.64.0.0-100.127.255.255
|
||||
denied-peer-ip=127.0.0.0-127.255.255.255
|
||||
denied-peer-ip=169.254.0.0-169.254.255.255
|
||||
denied-peer-ip=127.0.0.0-127.255.255.255
|
||||
denied-peer-ip=172.16.0.0-172.31.255.255
|
||||
denied-peer-ip=192.0.0.0-192.0.0.255
|
||||
denied-peer-ip=192.0.2.0-192.0.2.255
|
||||
denied-peer-ip=192.88.99.0-192.88.99.255
|
||||
denied-peer-ip=192.168.0.0-192.168.255.255
|
||||
denied-peer-ip=198.18.0.0-198.19.255.255
|
||||
denied-peer-ip=198.51.100.0-198.51.100.255
|
||||
denied-peer-ip=203.0.113.0-203.0.113.255
|
||||
denied-peer-ip=240.0.0.0-255.255.255.255
|
||||
syslog
|
||||
|
||||
@@ -7,7 +7,7 @@ stream {
|
||||
server 127.0.0.1:4444;
|
||||
}
|
||||
upstream turn {
|
||||
server 127.0.0.1:4445;
|
||||
server 127.0.0.1:5349;
|
||||
}
|
||||
# since 1.13.10
|
||||
map $ssl_preread_alpn_protocols $upstream {
|
||||
|
||||
10
index.html
10
index.html
@@ -8,7 +8,17 @@
|
||||
|
||||
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
|
||||
<link rel="stylesheet" href="css/all.css">
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!JitsiMeetJS.app) {
|
||||
return;
|
||||
}
|
||||
|
||||
JitsiMeetJS.app.renderEntryPoint({
|
||||
Component: JitsiMeetJS.app.entryPoints.APP
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<script>
|
||||
// IE11 and earlier can be identified via their user agent and be
|
||||
// redirected to a page that is known to have no newer js syntax.
|
||||
|
||||
@@ -293,7 +293,7 @@ PODS:
|
||||
- React
|
||||
- react-native-netinfo (4.1.5):
|
||||
- React
|
||||
- react-native-webrtc (1.75.3):
|
||||
- react-native-webrtc (1.84.0):
|
||||
- React
|
||||
- react-native-webview (7.4.1):
|
||||
- React
|
||||
@@ -569,7 +569,7 @@ SPEC CHECKSUMS:
|
||||
react-native-calendar-events: 1442fad71a00388f933cfa25512588fec300fcf8
|
||||
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
||||
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
|
||||
react-native-webrtc: 86d841823e66d68cc1f86712db1c2956056bf0c2
|
||||
react-native-webrtc: 9268ae9a2bc9730796b0968d012327e92c392adf
|
||||
react-native-webview: 4dbc1d2a4a6b9c5e9e723c62651917aa2b5e579e
|
||||
React-RCTActionSheet: b72ddbfbe15b44ce691d128e4b582f4bb9abb540
|
||||
React-RCTAnimation: cfaefba5024499d336b76ab850e6bd33b232b5e3
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
jitsiMeet.universalLinkDomains = @[@"meet.jit.si", @"alpha.jitsi.net", @"beta.meet.jit.si"];
|
||||
|
||||
jitsiMeet.defaultConferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
|
||||
[builder setFeatureFlag:@"resolution" withValue:@(360)];
|
||||
builder.serverURL = [NSURL URLWithString:@"https://meet.jit.si"];
|
||||
builder.welcomePageEnabled = YES;
|
||||
|
||||
|
||||
@@ -38,5 +38,11 @@
|
||||
"lt": "Lituanian",
|
||||
"id": "Indonesian",
|
||||
"he": "Ebrèu",
|
||||
"eu": "Basc"
|
||||
"eu": "Basc",
|
||||
"mr": "Marathi",
|
||||
"sl": "Eslovèn",
|
||||
"ro": "Romanian",
|
||||
"ar": "Arabi"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,21 +1,36 @@
|
||||
{
|
||||
"addPeople": {
|
||||
"add": "Inviter",
|
||||
"addContacts": "Inviter vos contacts",
|
||||
"copyInvite": "Copier l'invitation à la réunion",
|
||||
"copyLink": "Copier le lien de la réunion",
|
||||
"copyStream": "Copier le lien de diffision en direct",
|
||||
"countryNotSupported": "Cette destination n'est pas actuellement supportée.",
|
||||
"countryReminder": "Appel hors des États-Unis ? Veuillez débuter par le code du pays !",
|
||||
"defaultEmail": "Votre email par défaut",
|
||||
"disabled": "Vous ne pouvez pas inviter quelqu'un.",
|
||||
"failedToAdd": "Erreur lors de l'ajout des participants",
|
||||
"footerText": "Appels sortants désactivés.",
|
||||
"googleEmail": "Gmail",
|
||||
"inviteMoreHeader": "Vous êtes seul(e) dans la réunion",
|
||||
"inviteMoreMailSubject": "Rejoindre une réunion {{appName}}",
|
||||
"inviteMorePrompt": "Inviter d'autres personnes",
|
||||
"linkCopied": "Lien copié dans le presse-papiers",
|
||||
"loading": "Rechercher des personnes et des numéros de téléphone",
|
||||
"loadingNumber": "Validation du numéro de téléphone",
|
||||
"loadingPeople": "Recherche de personnes à inviter",
|
||||
"noResults": "Aucun résultat de recherche correspondant",
|
||||
"noValidNumbers": "Veuillez entrer un numéro de téléphone",
|
||||
"outlookEmail": "Outlook",
|
||||
"searchNumbers": "Ajouter des numéros de téléphone",
|
||||
"searchPeople": "Rechercher une personne",
|
||||
"searchPeopleAndNumbers": "Rechercher des personnes ou ajouter leurs numéros de téléphone",
|
||||
"shareInvite": "Partager l'invitation à la réunion",
|
||||
"shareLink": "Partager le lien de la réunion pour inviter d'autres personnes",
|
||||
"shareStream": "Partager le lien de diffusion en direct",
|
||||
"telephone": "Téléphone : {{number}}",
|
||||
"title": "Inviter une personne à cette réunion"
|
||||
"title": "Inviter une personne à cette réunion",
|
||||
"yahooEmail": "Yahoo"
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "Bluetooth",
|
||||
@@ -147,6 +162,7 @@
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "Diffusion en direct"
|
||||
},
|
||||
"add": "Ajouter",
|
||||
"allow": "Autoriser",
|
||||
"alreadySharedVideoMsg": "Un autre participant est en train de partager sa vidéo. Cette conférence ne permet de partager qu'une seule vidéo à la fois.",
|
||||
"alreadySharedVideoTitle": "Une seule vidéo partagée est autorisée à la fois",
|
||||
@@ -468,6 +484,40 @@
|
||||
"passwordSetRemotely": "défini par un autre participant",
|
||||
"passwordDigitsOnly": "Jusqu'à {{number}} chiffres",
|
||||
"poweredby": "produit par",
|
||||
"prejoin": {
|
||||
"audioAndVideoError": "Erreur audio et video:",
|
||||
"audioOnlyError": "Erreur audio:",
|
||||
"audioTrackError": "N'a pas pu créer la piste audio.",
|
||||
"callMe": "Appelez-moi",
|
||||
"callMeAtNumber": "Appelez-moi à ce numéro:",
|
||||
"configuringDevices": "Configuration des appareils...",
|
||||
"connectedWithAudioQ": "Êtes-vous connecté avec le microphone?",
|
||||
"copyAndShare": "Copier & partager le lien",
|
||||
"dialInMeeting": "Participez à la réunion",
|
||||
"dialInPin": "Participez à la réunion et saisir le code PIN:",
|
||||
"dialing": "Numérotation",
|
||||
"doNotShow": "Ne plus afficher ceci",
|
||||
"errorDialOut": "Impossible de composer le numéro",
|
||||
"errorDialOutDisconnected": "Impossible de composer le numéro. Déconnecté",
|
||||
"errorDialOutFailed": "Impossible de composer le numéro. L'appel a échoué",
|
||||
"errorDialOutStatus": "Erreur lors de l'obtention de l'état d'appel sortant",
|
||||
"errorStatusCode": "Erreur de numérotation, code d'état: {{status}}",
|
||||
"errorValidation": "La validation du numéro a échoué",
|
||||
"iWantToDialIn": "Je veux me connecter",
|
||||
"joinAudioByPhone": "Rejoindre avec l'audio du téléphone",
|
||||
"joinMeeting": "Rejoindre la réunion",
|
||||
"joinWithoutAudio": "Rejoignez sans microphone",
|
||||
"initiated": "Appel lancé",
|
||||
"linkCopied": "Lien copié dans le presse-papiers",
|
||||
"lookGood": "Il semble que votre microphone fonctionne correctement",
|
||||
"or": "ou",
|
||||
"calling": "Appel",
|
||||
"startWithPhone": "Commencez avec l'audio du téléphone",
|
||||
"screenSharingError": "Erreur de partage d'écran:",
|
||||
"videoOnlyError": "Erreur vidéo:",
|
||||
"videoTrackError": "Impossible de créer une piste vidéo.",
|
||||
"viewAllNumbers": "voir tous les numéros"
|
||||
},
|
||||
"presenceStatus": {
|
||||
"busy": "Occupé",
|
||||
"calling": "Appel...",
|
||||
@@ -783,5 +833,39 @@
|
||||
},
|
||||
"helpView": {
|
||||
"header": "Centre d'aide"
|
||||
},
|
||||
"lobby": {
|
||||
"knockingParticipantList" : "Liste des participants en attente",
|
||||
"allow": "Autoriser",
|
||||
"backToKnockModeButton": "Aucun mot de passe, demander à rejoindre plutôt",
|
||||
"dialogTitle": "Mode lobby",
|
||||
"disableDialogContent": "Le mode lobby est actuellement activé. Cette fonctionnalité garantit que les participants indésirables ne peuvent pas rejoindre votre réunion. Souhaitez-vous la désactiver?",
|
||||
"disableDialogSubmit": "Désactiver",
|
||||
"emailField": "Saisissez votre adresse email",
|
||||
"enableDialogPasswordField": "Définir le mot de passe (optionel)",
|
||||
"enableDialogSubmit": "Activer",
|
||||
"enableDialogText": "Le mode lobby vous permet de protéger votre réunion en autorisant les personnes à entrer qu'après l'approbation formelle d'un modérateur.",
|
||||
"enterPasswordButton": "Saisissez un mot de passe de réunion",
|
||||
"enterPasswordTitle": "Saisissez le mot de passe pour rejoindre la réunion",
|
||||
"invalidPassword": "Mot de passe invalide",
|
||||
"joiningMessage": "Vous allez rejoindre une réunion dès que quelqu'un aura accepté votre demande",
|
||||
"joinWithPasswordMessage": "Tentative de rejoindre avec mot de passe, patientez s'il vous plait...",
|
||||
"joinRejectedMessage": "Votre requête pour rejoindre une réunion a été refusée par un modérateur.",
|
||||
"joinTitle": "Rejoindre une réunion",
|
||||
"joiningTitle": "Demander à rejoindre une réunion...",
|
||||
"joiningWithPasswordTitle": "Rejoindre avec mot de passe...",
|
||||
"knockButton": "Demander à rejoindre",
|
||||
"knockTitle": "Quelqu'un souhaite rejoindre la réunion",
|
||||
"nameField": "Saisissez votre nom",
|
||||
"passwordField": "Saisissez le mot de passe de la réunion",
|
||||
"passwordJoinButton": "Rejoindre",
|
||||
"reject": "Refuser",
|
||||
"toggleLabel": "Activer le lobby"
|
||||
},
|
||||
"security": {
|
||||
"about": "Vous pouvez ajouter un mot de passe à votre réunion. Les participants devront fournir le mot de passe avant qu'ils soient autorisés à rejoindre la réunion.",
|
||||
"aboutReadOnly": "Les modérateurs peuvent ajouter un mot de passe à la réunion. Les participants devront fournir le mot de passe avant qu'ils soient autorisés à rejoindre la réunion.",
|
||||
"insecureRoomNameWarning": "Le nom de la salle est peu sûr. Des participants non désirés peuvent rejoindre votre réunion. Pensez à sécuriser votre réunion en cliquant sur le bouton de sécurité.",
|
||||
"securityOptions": "Options de sécurité"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,22 @@
|
||||
"searchPeople": "Cercar de monde",
|
||||
"searchPeopleAndNumbers": "Cercar de monde o apondre lor numèros de telefòn",
|
||||
"telephone": "Telefòn : {{number}}",
|
||||
"title": "Convidatz de monde a vòstra conferéncia"
|
||||
"title": "Convidatz de monde a vòstra conferéncia",
|
||||
"shareStream": "Partejar la ligam de la difusion en dirècte",
|
||||
"copyStream": "Copiar lo ligam de la difusion en dirècte",
|
||||
"yahooEmail": "Yahoo Email",
|
||||
"outlookEmail": "Outlook Email",
|
||||
"shareLink": "Partejar lo ligam de la conferéncia per convidar de monde",
|
||||
"shareInvite": "Partejar invitacion conferéncia",
|
||||
"linkCopied": "Ligam copiat al quichapapièrs",
|
||||
"inviteMorePrompt": "Convidar mai de monde",
|
||||
"inviteMoreMailSubject": "Rejónher la conferéncia {{appName}}",
|
||||
"inviteMoreHeader": "Sètz l’unica persona de la conferéncia",
|
||||
"googleEmail": "Google Email",
|
||||
"defaultEmail": "Vòstre email per defaut",
|
||||
"copyLink": "Copiar lo ligam de la conferéncia",
|
||||
"copyInvite": "Copiar l’invitacion a la conferéncia",
|
||||
"addContacts": "Convidatz vòstres contactes"
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "Bluetooth",
|
||||
@@ -122,7 +137,10 @@
|
||||
"launchWebButton": "Lançar del navigador",
|
||||
"openApp": "Telecargar l’aplicacion",
|
||||
"title": "Aviada de vòstra conferéncia dins {{app}}…",
|
||||
"tryAgainButton": "Tornar ensajar del burèu"
|
||||
"tryAgainButton": "Tornar ensajar del burèu",
|
||||
"joinInApp": "Rejónher la conferéncia en utilizant l’aplicacion",
|
||||
"ifHaveApp": "S’avètz ja l’aplicacion :",
|
||||
"ifDoNotHaveApp": "S’avètz pas encara l’aplicacion :"
|
||||
},
|
||||
"defaultLink": "ex. {{url}}",
|
||||
"defaultNickname": "ex. Joan Delpuèch",
|
||||
@@ -238,7 +256,7 @@
|
||||
"screenSharingFailedToInstallTitle": "Fracàs de l'installacion de partatge d'ecran",
|
||||
"screenSharingFirefoxPermissionDeniedError": "Quicòm a fach mèuca quand èrem a ensajar de partejar vòstre ecran. Mercés de verificar qu’avètz donat l’autorizacion de lo partejar.",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "Ops ! Avèm pas pogut aviar lo partatge d’ecran.",
|
||||
"screenSharingPermissionDeniedError": "Òups ! Quicòm s'es pas ben passat amb l'autorizacion de vòstra extension de partatge d'ecran. Mercés de recargar e tornar ensajar.",
|
||||
"screenSharingPermissionDeniedError": "Òps ! Quicòm s'es pas ben passat amb l'autorizacion de vòstra autorizacion de partatge d'ecran. Mercés de recargar e tornar ensajar.",
|
||||
"sendPrivateMessage": "Avètz recentament recebut un messatge privat. Avètz ensajat d’i respondre en privat, o volètz enviar lo messatge al grop ?",
|
||||
"sendPrivateMessageCancel": "Enviar al grop",
|
||||
"sendPrivateMessageOk": "Enviar en privat",
|
||||
@@ -257,7 +275,7 @@
|
||||
"stopLiveStreaming": "Arrestar lo dirècte",
|
||||
"stopRecording": "Arrestar l'enregistrament",
|
||||
"stopRecordingWarning": "Sètz segur que volètz arrestar l'enregistrament?",
|
||||
"stopStreamingWarning": "Sètz segur que volètz arrestar lo dirècte?",
|
||||
"stopStreamingWarning": "Volètz vertadièrament arrestar lo dirècte ?",
|
||||
"streamKey": "Clau del dirècte",
|
||||
"Submit": "Validar",
|
||||
"thankYou": "Mercé d'aver utilizat {{appName}} !",
|
||||
@@ -273,12 +291,11 @@
|
||||
"Yes": "Òc",
|
||||
"yourEntireScreen": "Vòstre ecran complet",
|
||||
"screenSharingAudio": "Partejar l’àudio",
|
||||
"muteEveryoneStartMuted": "",
|
||||
"muteEveryoneSelf": "",
|
||||
"muteEveryoneTitle": "",
|
||||
"muteEveryoneDialog": "",
|
||||
"muteEveryoneElseTitle": "Copar lo son a totes levat {{whom}} ?",
|
||||
"muteEveryoneElseDialog": ""
|
||||
"muteEveryoneStartMuted": "D'ara enlà tot lo monde comença mut",
|
||||
"muteEveryoneSelf": "vos",
|
||||
"muteEveryoneTitle": "Rendre mut tot lo monde ?",
|
||||
"muteEveryoneDialog": "Volètz vertadièrament copar lo son a tot lo monde ? Poiretz pas lo restablir, mas eles poiràn o far quora que vòlgan.",
|
||||
"muteEveryoneElseTitle": "Copar lo son a totes levat {{whom}} ?"
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "ara es {{status}}"
|
||||
@@ -381,7 +398,7 @@
|
||||
"invalidStreamKey": "La clau de difusion en dirècte es benlèu pas corrècta.",
|
||||
"off": "La difusion en dirècte es estada arrestada",
|
||||
"offBy": "{{name}} a arrestat la difusion en dirècte",
|
||||
"on": "La difusion en dirècte es estada arrestada",
|
||||
"on": "Difusion en dirècte",
|
||||
"onBy": "{{name}} a començat la difusion en dirècte",
|
||||
"pending": "Començar lo dirècte…",
|
||||
"serviceName": "Servici de difusion en dirècte",
|
||||
@@ -458,7 +475,9 @@
|
||||
"newDeviceAudioTitle": "Nòu periferic àudio detectat",
|
||||
"newDeviceAction": "Utilizar",
|
||||
"oldElectronClientDescription3": " ara !",
|
||||
"oldElectronClientDescription2": "darrièra compilacion"
|
||||
"oldElectronClientDescription2": "darrièra compilacion",
|
||||
"oldElectronClientDescription1": "Sembla qu’utilizatz una version anciana del client Jitsi Meet qu’es conegut per conténer de problèmas de seguretat. Mercés de vos assegurar de metre a jorn ",
|
||||
"OldElectronAPPTitle": "Problèma de seguretat !"
|
||||
},
|
||||
"passwordSetRemotely": "causit per qualqu'un mai",
|
||||
"passwordDigitsOnly": "Fins a {{number}} chifras",
|
||||
@@ -557,7 +576,9 @@
|
||||
"startWithAudioMuted": "Començar sens son",
|
||||
"startWithVideoMuted": "Començar sens vièdo",
|
||||
"version": "Version",
|
||||
"alertCancel": "Anullar"
|
||||
"alertCancel": "Anullar",
|
||||
"disableCrashReportingWarning": "Volètz vertadièrament desactivar lo rapòrt de plantatge ? Lo paramètre serà aplicat aprèp la reaviada de l’aplicacion.",
|
||||
"disableCrashReporting": "Desactivar lo rapòrt de plantatge"
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "\n\n=====\n\nVolètz sonar de vòstre telefòn estant ?\n\n{{defaultDialInNumber}}Clicatz lo ligam per veire los numèros de telefòn d’aquesta conferéncia\n{{dialInfoPageUrl}}",
|
||||
@@ -618,9 +639,10 @@
|
||||
"toggleCamera": "Passar a la camèra",
|
||||
"videomute": "Silenciar la vidèo",
|
||||
"videoblur": "Enfoscar o non la vidèo",
|
||||
"muteEveryone": "",
|
||||
"muteEveryone": "Rendre mut tot lo monde",
|
||||
"moreOptions": "Mostrar mai d’opcions",
|
||||
"e2ee": "Chiframent del cap a la fin"
|
||||
"e2ee": "Chiframent del cap a la fin",
|
||||
"security": "Opcions de seguretat"
|
||||
},
|
||||
"addPeople": "Ajustar de monde a vòstra sonada",
|
||||
"audioOnlyOff": "Desactivar lo mòde connexion febla",
|
||||
@@ -672,12 +694,13 @@
|
||||
"startvideoblur": "Trebolar mon rèire-plan",
|
||||
"stopvideoblur": "Desactivar lo borrolatge del rèire-plan",
|
||||
"noisyAudioInputDesc": "",
|
||||
"noisyAudioInputTitle": "",
|
||||
"noisyAudioInputTitle": "Vòstre microfòn sembla brusent !",
|
||||
"noAudioSignalDialInLinkDesc": "",
|
||||
"noAudioSignalDialInDesc": "",
|
||||
"muteEveryone": "",
|
||||
"muteEveryone": "Rendre mut tot lo monde",
|
||||
"moreOptions": "Autras opcions",
|
||||
"e2ee": "Chiframent del cap a la fin"
|
||||
"e2ee": "Chiframent del cap a la fin",
|
||||
"security": "Opcions de seguretat"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Aviar / Arrestat los sostítols",
|
||||
@@ -741,7 +764,7 @@
|
||||
"remoteControl": "Contraròtle alonhat",
|
||||
"show": "Mostrar davant",
|
||||
"videomute": "Lo participant a arrestat la camèra",
|
||||
"domuteOthers": ""
|
||||
"domuteOthers": "Rendre mut totes los autres"
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
@@ -778,8 +801,8 @@
|
||||
"header": "Centre d’ajuda"
|
||||
},
|
||||
"lonelyMeetingExperience": {
|
||||
"youAreAlone": "",
|
||||
"button": ""
|
||||
"youAreAlone": "Sètz l’unica persona de la conferéncia",
|
||||
"button": "Convidar d’autres"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"dontShowAgain": "Me mostrar pas mai aquò",
|
||||
@@ -796,6 +819,53 @@
|
||||
"callMe": "Sona-me",
|
||||
"audioTrackError": "Creacion impossibla de la pista àudio.",
|
||||
"audioOnlyError": "Error àudio :",
|
||||
"audioAndVideoError": "Error àudio e vidèo :"
|
||||
"audioAndVideoError": "Error àudio e vidèo :",
|
||||
"dialing": "A sonar",
|
||||
"viewAllNumbers": "veire totes los numèros",
|
||||
"videoTrackError": "Creacion d’una pista àudio impossibla.",
|
||||
"videoOnlyError": "Error vidèo :",
|
||||
"screenSharingError": "Error de partatge d’ecran :",
|
||||
"startWithPhone": "Començar amb l’àudio del telefòn",
|
||||
"calling": "A sonar",
|
||||
"lookGood": "Sembla que lo microfòn fonciona pas coma cal",
|
||||
"linkCopied": "Ligam copiat al quichapapièrs",
|
||||
"initiated": "Sonada aviada",
|
||||
"joinWithoutAudio": "Rejónher sens àudio",
|
||||
"joinMeeting": "Rejónher la conferéncia",
|
||||
"joinAudioByPhone": "Rejónher amb l’àudio del telefòn"
|
||||
},
|
||||
"lobby": {
|
||||
"reject": "Regetar",
|
||||
"passwordJoinButton": "Rejónher",
|
||||
"passwordField": "Picatz lo senhal de la conferéncia",
|
||||
"nameField": "Escrivètz vòstre nom",
|
||||
"knockTitle": "Qualqu’un vòl rejónher la conferéncia",
|
||||
"knockButton": "Demandar a rejónher",
|
||||
"joiningWithPasswordTitle": "A rejónher amb senhal...",
|
||||
"joiningTitle": "Demanda a rejónher la conferéncia...",
|
||||
"joinTitle": "Rejónher la conferéncia",
|
||||
"joinRejectedMessage": "Un moderator a regetat vòstra demanda de participacion.",
|
||||
"joinWithPasswordMessage": "Ensag de participacion amb senhal, volgatz esperar...",
|
||||
"joiningMessage": "Dintraretz dins la conferéncia tre que qualqu’un aurà acceptat vòstra demanda",
|
||||
"invalidPassword": "Senhal invalid",
|
||||
"enterPasswordTitle": "Dintratz lo senhal per rejónher la conferéncia",
|
||||
"enterPasswordButton": "Dintratz lo senhal de la conferéncia",
|
||||
"enableDialogSubmit": "Activar",
|
||||
"enableDialogPasswordField": "Definir un senhal (opcional)",
|
||||
"emailField": "Picata vòstra adreça electronica",
|
||||
"disableDialogSubmit": "Desactivar",
|
||||
"backToKnockModeButton": "Cap de senhal, demandar a participar a la plaça",
|
||||
"allow": "Autorizar"
|
||||
},
|
||||
"security": {
|
||||
"securityOptions": "Opcions de seguretat",
|
||||
"insecureRoomNameWarning": "Lo nom de la sala es pas segur. De monde indesirables poirián rejónher vòstra conferéncia.",
|
||||
"aboutReadOnly": "Los participants que son moderators pòdon ajustar un $t(lockRoomPassword) a la conferéncia. Los participants deuràn fornir lo $t(lockRoomPassword) abans d’aver l’autorizacion de rejónher la conferéncia.",
|
||||
"about": "Podètz ajustar un $t(lockRoomPassword) per rejónher una conferéncia. Los participants deuràn fornir lo $t(lockRoomPassword) abans d’obténer l’autorizacion de dintrar dins la conferéncia."
|
||||
},
|
||||
"e2ee": {
|
||||
"labelToolTip": "La comunicacion àudio e vidèo d’aquesta sonada es chifrada del cap a la fin"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -71,9 +71,8 @@
|
||||
},
|
||||
"privateNotice": "{{recipient}} için özel mesaj",
|
||||
"title": "Sohbet",
|
||||
"you": "sen",
|
||||
|
||||
|
||||
"you": "sen"
|
||||
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "Toplantıya bağlanılıyor..."
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
"localaddress_plural": "Local addresses:",
|
||||
"localport": "Local port:",
|
||||
"localport_plural": "Local ports:",
|
||||
"maxEnabledResolution": "send max",
|
||||
"more": "Show more",
|
||||
"packetloss": "Packet loss:",
|
||||
"quality": {
|
||||
@@ -864,6 +865,7 @@
|
||||
"header": "Help center"
|
||||
},
|
||||
"lobby": {
|
||||
"knockingParticipantList" : "Knocking participant list",
|
||||
"allow": "Allow",
|
||||
"backToKnockModeButton": "No password, ask to join instead",
|
||||
"dialogTitle": "Lobby mode",
|
||||
@@ -885,6 +887,11 @@
|
||||
"knockButton": "Ask to Join",
|
||||
"knockTitle": "Someone wants to join the meeting",
|
||||
"nameField": "Enter your name",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} has been rejected to join by {{originParticipantName}}",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} has been allowed to join by {{originParticipantName}}",
|
||||
"notificationLobbyDisabled": "Lobby has been disabled by {{originParticipantName}}",
|
||||
"notificationLobbyEnabled": "Lobby has been enabled by {{originParticipantName}}",
|
||||
"notificationTitle": "Lobby",
|
||||
"passwordField": "Enter meeting password",
|
||||
"passwordJoinButton": "Join",
|
||||
"reject": "Reject",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* global $, APP */
|
||||
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import Logger from 'jitsi-meet-logger';
|
||||
import { jitsiLocalStorage } from 'js-utils';
|
||||
|
||||
import {
|
||||
NOTIFICATION_TIMEOUT,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// FIXME: change to '../API' when we update to webpack2. If we do this now all
|
||||
// files from API modules will be included in external_api.js.
|
||||
import { PostMessageTransportBackend, Transport } from 'js-utils/transport';
|
||||
import { PostMessageTransportBackend, Transport } from '@jitsi/js-utils/transport';
|
||||
|
||||
import { getJitsiMeetGlobalNS } from '../../react/features/base/util';
|
||||
import { API_ID } from '../API/constants';
|
||||
|
||||
52
package-lock.json
generated
52
package-lock.json
generated
@@ -2832,6 +2832,23 @@
|
||||
"@types/yargs": "^13.0.0"
|
||||
}
|
||||
},
|
||||
"@jitsi/js-utils": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.0.tgz",
|
||||
"integrity": "sha512-at9GPMP7IL0v6QS1Gs9c5MbbiN2AT0uKzsgKM8qS2wqXxqvpfT3p4U3+LH2IUyXiHlkmvlBMcNM02MltBdyRmQ==",
|
||||
"requires": {
|
||||
"bowser": "2.7.0",
|
||||
"js-md5": "0.7.3",
|
||||
"postis": "2.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"js-md5": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
|
||||
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@jitsi/sdp-interop": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/sdp-interop/-/sdp-interop-1.0.3.tgz",
|
||||
@@ -10538,22 +10555,6 @@
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
|
||||
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
|
||||
},
|
||||
"js-utils": {
|
||||
"version": "github:jitsi/js-utils#cf11996bd866fdb47326c59a5d3bc24be17282d4",
|
||||
"from": "github:jitsi/js-utils#cf11996bd866fdb47326c59a5d3bc24be17282d4",
|
||||
"requires": {
|
||||
"bowser": "2.7.0",
|
||||
"js-md5": "0.7.3",
|
||||
"postis": "2.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"js-md5": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
|
||||
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsbn": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
@@ -10724,15 +10725,16 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#4fec06db7fc59a88021ec0b409eda47f21c42902",
|
||||
"from": "github:jitsi/lib-jitsi-meet#4fec06db7fc59a88021ec0b409eda47f21c42902",
|
||||
"version": "github:jitsi/lib-jitsi-meet#cd008d726f1f57562eb5d8e6a3cd91c7e69826a0",
|
||||
"from": "github:jitsi/lib-jitsi-meet#cd008d726f1f57562eb5d8e6a3cd91c7e69826a0",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "1.0.0",
|
||||
"@jitsi/sdp-interop": "1.0.3",
|
||||
"@jitsi/sdp-simulcast": "0.3.0",
|
||||
"async": "0.9.0",
|
||||
"current-executing-script": "0.1.3",
|
||||
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b",
|
||||
"js-utils": "github:jitsi/js-utils#cf11996bd866fdb47326c59a5d3bc24be17282d4",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"sdp-transform": "2.3.0",
|
||||
"strophe.js": "1.3.4",
|
||||
@@ -14319,9 +14321,9 @@
|
||||
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "1.75.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.75.3.tgz",
|
||||
"integrity": "sha512-WIQu7jpH4osb4IMdjkpr62UYwt9kHIU4vWvvTHMxSqkGZwMGpTyFW/E+Y0T7If1azAvhq3GBuQjI3npxIXGbSQ==",
|
||||
"version": "1.84.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.84.0.tgz",
|
||||
"integrity": "sha512-xPOFbrcehuBzLnFy3keCM2HyMsyCVDQjQNAn8SIHKH/PA8Q7kZ4spuytc2E1hBTr7zH/vQ2Px+DWqu7on12jag==",
|
||||
"requires": {
|
||||
"base64-js": "^1.1.2",
|
||||
"event-target-shim": "^1.0.5",
|
||||
@@ -14330,9 +14332,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
|
||||
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"@atlaskit/theme": "7.0.2",
|
||||
"@atlaskit/toggle": "5.0.14",
|
||||
"@atlaskit/tooltip": "12.1.13",
|
||||
"@jitsi/js-utils": "1.0.0",
|
||||
"@microsoft/microsoft-graph-client": "1.1.0",
|
||||
"@react-native-community/async-storage": "1.3.4",
|
||||
"@react-native-community/google-signin": "3.0.1",
|
||||
@@ -54,9 +55,8 @@
|
||||
"jquery-contextmenu": "2.4.5",
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"js-utils": "github:jitsi/js-utils#cf11996bd866fdb47326c59a5d3bc24be17282d4",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#4fec06db7fc59a88021ec0b409eda47f21c42902",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#304b0a2b4e18216d792f499c74fc24bc3849303e",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.13",
|
||||
"moment": "2.19.4",
|
||||
@@ -81,7 +81,7 @@
|
||||
"react-native-svg-transformer": "0.13.0",
|
||||
"react-native-swipeout": "2.3.6",
|
||||
"react-native-watch-connectivity": "0.4.3",
|
||||
"react-native-webrtc": "1.75.3",
|
||||
"react-native-webrtc": "1.84.0",
|
||||
"react-native-webview": "7.4.1",
|
||||
"react-native-youtube-iframe": "1.2.3",
|
||||
"react-redux": "7.1.0",
|
||||
|
||||
@@ -117,7 +117,9 @@ export function createHandlers({ getState }: { getState: Function }) {
|
||||
})
|
||||
.catch(e => {
|
||||
analytics.dispose();
|
||||
logger.error(e);
|
||||
if (handlers.length !== 0) {
|
||||
logger.error(e);
|
||||
}
|
||||
|
||||
return [];
|
||||
}));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { generateRoomWithoutSeparator } from 'js-utils/random';
|
||||
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
|
||||
import type { Component } from 'react';
|
||||
|
||||
import { isRoomValid } from '../base/conference';
|
||||
|
||||
@@ -41,6 +41,7 @@ import '../subtitles/middleware';
|
||||
import '../toolbox/middleware';
|
||||
import '../transcribing/middleware';
|
||||
import '../video-layout/middleware';
|
||||
import '../video-quality/middleware';
|
||||
import '../videosipgw/middleware';
|
||||
|
||||
import './middleware';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { jitsiLocalStorage } from 'js-utils';
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import _ from 'lodash';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
@@ -122,14 +122,14 @@ export default class BaseApp extends Component<*, State> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { route: { component }, store } = this.state;
|
||||
const { route: { component, props }, store } = this.state;
|
||||
|
||||
if (store) {
|
||||
return (
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
<Provider store = { store }>
|
||||
<Fragment>
|
||||
{ this._createMainElement(component) }
|
||||
{ this._createMainElement(component, props) }
|
||||
<SoundCollection />
|
||||
{ this._createExtraElement() }
|
||||
{ this._renderDialogContainer() }
|
||||
|
||||
@@ -117,14 +117,15 @@ StateListenerRegistry.register(
|
||||
maxReceiverVideoQuality,
|
||||
preferredVideoQuality
|
||||
} = currentState;
|
||||
const changedConference = conference !== previousState.conference;
|
||||
const changedPreferredVideoQuality
|
||||
= preferredVideoQuality !== previousState.preferredVideoQuality;
|
||||
const changedMaxVideoQuality = maxReceiverVideoQuality !== previousState.maxReceiverVideoQuality;
|
||||
|
||||
if (changedPreferredVideoQuality || changedMaxVideoQuality) {
|
||||
if (changedConference || changedPreferredVideoQuality || changedMaxVideoQuality) {
|
||||
_setReceiverVideoConstraint(conference, preferredVideoQuality, maxReceiverVideoQuality);
|
||||
}
|
||||
if (changedPreferredVideoQuality) {
|
||||
if (changedConference || changedPreferredVideoQuality) {
|
||||
_setSenderVideoConstraint(conference, preferredVideoQuality);
|
||||
}
|
||||
});
|
||||
@@ -460,7 +461,10 @@ function _sendTones({ getState }, next, action) {
|
||||
*/
|
||||
function _setReceiverVideoConstraint(conference, preferred, max) {
|
||||
if (conference) {
|
||||
conference.setReceiverVideoConstraint(Math.min(preferred, max));
|
||||
const value = Math.min(preferred, max);
|
||||
|
||||
conference.setReceiverVideoConstraint(value);
|
||||
logger.info(`setReceiverVideoConstraint: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { jitsiLocalStorage } from 'js-utils';
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { addKnownDomains } from '../known-domains';
|
||||
|
||||
@@ -125,6 +125,7 @@ export default [
|
||||
'minParticipants',
|
||||
'nick',
|
||||
'openBridgeChannel',
|
||||
'opusMaxAvgBitrate',
|
||||
'p2p',
|
||||
'pcStatsInterval',
|
||||
'preferH264',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { jitsiLocalStorage } from 'js-utils';
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { parseURLParams } from '../util';
|
||||
|
||||
@@ -24,6 +24,7 @@ export default [
|
||||
'DISABLE_TRANSCRIPTION_SUBTITLES',
|
||||
'DISABLE_VIDEO_BACKGROUND',
|
||||
'DISPLAY_WELCOME_PAGE_CONTENT',
|
||||
'ENABLE_DIAL_OUT',
|
||||
'ENABLE_FEEDBACK_ANIMATION',
|
||||
'ENFORCE_NOTIFICATION_AUTO_DISMISS_TIMEOUT',
|
||||
'FILM_STRIP_MAX_HEIGHT',
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import { jitsiLocalStorage } from 'js-utils';
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
|
||||
import { APP_WILL_MOUNT } from '../app';
|
||||
import { getFeatureFlag } from '../flags/functions';
|
||||
import { addKnownDomains } from '../known-domains';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { parseURIString } from '../util';
|
||||
@@ -107,6 +108,12 @@ function _setConfig({ dispatch, getState }, next, action) {
|
||||
config.p2p = { enabled: !settings.disableP2P };
|
||||
}
|
||||
|
||||
const resolutionFlag = getFeatureFlag(state, 'resolution');
|
||||
|
||||
if (typeof resolutionFlag !== 'undefined') {
|
||||
config.resolution = resolutionFlag;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: _UPDATE_CONFIG,
|
||||
config
|
||||
|
||||
@@ -15,14 +15,19 @@ import {
|
||||
NOTIFY_CAMERA_ERROR,
|
||||
NOTIFY_MIC_ERROR,
|
||||
SET_AUDIO_INPUT_DEVICE,
|
||||
SET_VIDEO_INPUT_DEVICE
|
||||
SET_VIDEO_INPUT_DEVICE,
|
||||
UPDATE_DEVICE_LIST
|
||||
} from './actionTypes';
|
||||
import {
|
||||
removePendingDeviceRequests,
|
||||
setAudioInputDevice,
|
||||
setVideoInputDevice
|
||||
} from './actions';
|
||||
import { formatDeviceLabel, setAudioOutputDeviceId } from './functions';
|
||||
import {
|
||||
formatDeviceLabel,
|
||||
groupDevicesByKind,
|
||||
setAudioOutputDeviceId
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
|
||||
@@ -41,6 +46,24 @@ const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs the current device list.
|
||||
*
|
||||
* @param {Object} deviceList - Whatever is returned by {@link groupDevicesByKind}.
|
||||
* @returns {string}
|
||||
*/
|
||||
function logDeviceList(deviceList) {
|
||||
const devicesToStr = list => list.map(device => `\t\t${device.label}[${device.deviceId}]`).join('\n');
|
||||
const audioInputs = devicesToStr(deviceList.audioInput);
|
||||
const audioOutputs = devicesToStr(deviceList.audioOutput);
|
||||
const videoInputs = devicesToStr(deviceList.videoInput);
|
||||
|
||||
logger.debug('Device list updated:\n'
|
||||
+ `audioInput:\n${audioInputs}\n`
|
||||
+ `audioOutput:\n${audioOutputs}\n`
|
||||
+ `videoInput:\n${videoInputs}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the middleware of the feature base/devices.
|
||||
*
|
||||
@@ -123,6 +146,9 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
APP.UI.emitEvent(UIEvents.VIDEO_DEVICE_CHANGED, action.deviceId);
|
||||
}
|
||||
break;
|
||||
case UPDATE_DEVICE_LIST:
|
||||
logDeviceList(groupDevicesByKind(action.devices));
|
||||
break;
|
||||
case CHECK_AND_NOTIFY_FOR_NEW_DEVICE:
|
||||
_checkAndNotifyForNewDevice(store, action.newDevices, action.oldDevices);
|
||||
break;
|
||||
|
||||
@@ -19,24 +19,6 @@ const DEFAULT_STATE = {
|
||||
pendingRequests: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs the current device list.
|
||||
*
|
||||
* @param {Object} deviceList - Whatever is returned by {@link groupDevicesByKind}.
|
||||
* @returns {string}
|
||||
*/
|
||||
function logDeviceList(deviceList) {
|
||||
const devicesToStr = list => list.map(device => `\t\t${device.label}[${device.deviceId}]`).join('\n');
|
||||
const audioInputs = devicesToStr(deviceList.audioInput);
|
||||
const audioOutputs = devicesToStr(deviceList.audioOutput);
|
||||
const videoInputs = devicesToStr(deviceList.videoInput);
|
||||
|
||||
logger.debug('Device list updated:\n'
|
||||
+ `audioInput:\n${audioInputs}\n`
|
||||
+ `audioOutput:\n${audioOutputs}\n`
|
||||
+ `videoInput:\n${videoInputs}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for actions which changes the state of known and used devices.
|
||||
*
|
||||
@@ -54,8 +36,6 @@ ReducerRegistry.register(
|
||||
case UPDATE_DEVICE_LIST: {
|
||||
const deviceList = groupDevicesByKind(action.devices);
|
||||
|
||||
logDeviceList(deviceList);
|
||||
|
||||
return {
|
||||
...state,
|
||||
availableDevices: deviceList
|
||||
|
||||
@@ -81,6 +81,13 @@ export const RAISE_HAND_ENABLED = 'raise-hand.enabled';
|
||||
*/
|
||||
export const RECORDING_ENABLED = 'recording.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating the local and (maximum) remote video resolution. Overrides
|
||||
* the server configuration.
|
||||
* Default: (unset).
|
||||
*/
|
||||
export const RESOLUTION = 'resolution';
|
||||
|
||||
/**
|
||||
* Flag indicating if server URL change is enabled.
|
||||
* Default: enabled (true)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { isOnline } from '../net-info/selectors';
|
||||
|
||||
import JitsiMeetJS from './_';
|
||||
import {
|
||||
LIB_DID_DISPOSE,
|
||||
@@ -37,7 +39,8 @@ export function disposeLib() {
|
||||
*/
|
||||
export function initLib() {
|
||||
return (dispatch: Dispatch<any>, getState: Function): void => {
|
||||
const config = getState()['features/base/config'];
|
||||
const state = getState();
|
||||
const config = state['features/base/config'];
|
||||
|
||||
if (!config) {
|
||||
throw new Error('Cannot init lib-jitsi-meet without config');
|
||||
@@ -50,6 +53,9 @@ export function initLib() {
|
||||
enableAnalyticsLogging: isAnalyticsEnabled(getState),
|
||||
...config
|
||||
});
|
||||
JitsiMeetJS.setNetworkInfo({
|
||||
isOnline: isOnline(state)
|
||||
});
|
||||
dispatch({ type: LIB_DID_INIT });
|
||||
} catch (error) {
|
||||
dispatch(libInitError(error));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { SET_CONFIG } from '../config';
|
||||
import { setLoggingConfig } from '../logging';
|
||||
import { SET_NETWORK_INFO } from '../net-info';
|
||||
import { PARTICIPANT_LEFT } from '../participants';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
|
||||
@@ -31,6 +32,12 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
break;
|
||||
|
||||
case SET_NETWORK_INFO:
|
||||
JitsiMeetJS.setNetworkInfo({
|
||||
isOnline: action.isOnline
|
||||
});
|
||||
break;
|
||||
|
||||
case PARTICIPANT_LEFT:
|
||||
action.participant.local && store.dispatch(disposeLib());
|
||||
break;
|
||||
|
||||
11
react/features/base/net-info/selectors.js
Normal file
11
react/features/base/net-info/selectors.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
/**
|
||||
* A selector for the internet online status.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isOnline(state) {
|
||||
return state[STORE_NAME].isOnline;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
import { getGravatarURL } from 'js-utils/avatar';
|
||||
|
||||
import { getGravatarURL } from '@jitsi/js-utils/avatar';
|
||||
|
||||
import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
|
||||
|
||||
@@ -19,6 +19,21 @@ type Props = {
|
||||
*/
|
||||
footer?: React$Node,
|
||||
|
||||
/**
|
||||
* The name of the participant.
|
||||
*/
|
||||
name?: string,
|
||||
|
||||
/**
|
||||
* Indicates whether the avatar should be shown when video is off
|
||||
*/
|
||||
showAvatar: boolean,
|
||||
|
||||
/**
|
||||
* Indicates whether the label and copy url action should be shown
|
||||
*/
|
||||
showConferenceInfo: boolean,
|
||||
|
||||
/**
|
||||
* Title of the screen.
|
||||
*/
|
||||
@@ -40,26 +55,43 @@ type Props = {
|
||||
* on the prejoin screen (pre-connection) or lobby (post-connection).
|
||||
*/
|
||||
export default class PreMeetingScreen extends PureComponent<Props> {
|
||||
/**
|
||||
* Default values for {@code Prejoin} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
showAvatar: true,
|
||||
showConferenceInfo: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { title, videoMuted, videoTrack } = this.props;
|
||||
const { name, showAvatar, showConferenceInfo, title, videoMuted, videoTrack } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'premeeting-screen'
|
||||
id = 'lobby-screen'>
|
||||
<Preview
|
||||
name = { name }
|
||||
showAvatar = { showAvatar }
|
||||
videoMuted = { videoMuted }
|
||||
videoTrack = { videoTrack } />
|
||||
{!videoMuted && <div className = 'preview-overlay' />}
|
||||
<div className = 'content'>
|
||||
<div className = 'title'>
|
||||
{ title }
|
||||
</div>
|
||||
<CopyMeetingUrl />
|
||||
{showConferenceInfo && (
|
||||
<>
|
||||
<div className = 'title'>
|
||||
{ title }
|
||||
</div>
|
||||
<CopyMeetingUrl />
|
||||
</>
|
||||
)}
|
||||
{ this.props.children }
|
||||
<div className = 'media-btn-container'>
|
||||
<AudioSettingsButton visible = { true } />
|
||||
|
||||
@@ -14,6 +14,11 @@ export type Props = {
|
||||
*/
|
||||
name: string,
|
||||
|
||||
/**
|
||||
* Indicates whether the avatar should be shown when video is off
|
||||
*/
|
||||
showAvatar: boolean,
|
||||
|
||||
/**
|
||||
* Flag signaling the visibility of camera preview.
|
||||
*/
|
||||
@@ -32,7 +37,7 @@ export type Props = {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function Preview(props: Props) {
|
||||
const { name, videoMuted, videoTrack } = props;
|
||||
const { name, showAvatar, videoMuted, videoTrack } = props;
|
||||
|
||||
if (!videoMuted && videoTrack) {
|
||||
return (
|
||||
@@ -44,18 +49,27 @@ function Preview(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'no-video'
|
||||
id = 'preview'>
|
||||
<Avatar
|
||||
className = 'preview-avatar'
|
||||
displayName = { name }
|
||||
size = { 200 } />
|
||||
</div>
|
||||
);
|
||||
if (showAvatar) {
|
||||
return (
|
||||
<div
|
||||
className = 'no-video'
|
||||
id = 'preview'>
|
||||
<Avatar
|
||||
className = 'preview-avatar'
|
||||
displayName = { name }
|
||||
participantId = 'local'
|
||||
size = { 200 } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Preview.defaultProps = {
|
||||
showAvatar: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import md5 from 'js-md5';
|
||||
import { jitsiLocalStorage } from 'js-utils';
|
||||
|
||||
import logger from './logger';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { jitsiLocalStorage } from 'js-utils';
|
||||
import { randomHexString } from 'js-utils/random';
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import { randomHexString } from '@jitsi/js-utils/random';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { APP_WILL_MOUNT } from '../app/actionTypes';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* global APP */
|
||||
|
||||
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../media';
|
||||
import { MEDIA_TYPE, setAudioMuted } from '../media';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
getUserSelectedMicDeviceId
|
||||
@@ -125,6 +125,89 @@ export function createLocalTracksF(options = {}, firePermissionPromptIsShownEven
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing a promise which resolves with the created tracks &
|
||||
* the errors resulting from that process.
|
||||
*
|
||||
* @returns {Promise<JitsiLocalTrack>}
|
||||
*
|
||||
* @todo Refactor to not use APP
|
||||
*/
|
||||
export function createPrejoinTracks() {
|
||||
const errors = {};
|
||||
const initialDevices = [ 'audio' ];
|
||||
const requestedAudio = true;
|
||||
let requestedVideo = false;
|
||||
const { startAudioOnly, startWithAudioMuted, startWithVideoMuted } = APP.store.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));
|
||||
}
|
||||
|
||||
if (!startWithVideoMuted && !startAudioOnly) {
|
||||
initialDevices.push('video');
|
||||
requestedVideo = true;
|
||||
}
|
||||
|
||||
let tryCreateLocalTracks;
|
||||
|
||||
if (!requestedAudio && !requestedVideo) {
|
||||
// Resolve with no tracks
|
||||
tryCreateLocalTracks = Promise.resolve([]);
|
||||
} else {
|
||||
tryCreateLocalTracks = createLocalTracksF({ devices: initialDevices }, true)
|
||||
.catch(err => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
|
||||
// Try audio only...
|
||||
errors.audioAndVideoError = err;
|
||||
|
||||
return (
|
||||
createLocalTracksF({ devices: [ 'audio' ] }, true));
|
||||
} else if (requestedAudio && !requestedVideo) {
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
return [];
|
||||
} else if (requestedVideo && !requestedAudio) {
|
||||
errors.videoOnlyError = err;
|
||||
|
||||
return [];
|
||||
}
|
||||
logger.error('Should never happen');
|
||||
})
|
||||
.catch(err => {
|
||||
// Log this just in case...
|
||||
if (!requestedAudio) {
|
||||
logger.error('The impossible just happened', err);
|
||||
}
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
// Try video only...
|
||||
return requestedVideo
|
||||
? createLocalTracksF({ devices: [ 'video' ] }, true)
|
||||
: [];
|
||||
})
|
||||
.catch(err => {
|
||||
// Log this just in case...
|
||||
if (!requestedVideo) {
|
||||
logger.error('The impossible just happened', err);
|
||||
}
|
||||
errors.videoOnlyError = err;
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
tryCreateLocalTracks,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns local audio track.
|
||||
*
|
||||
|
||||
@@ -143,6 +143,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
if (typeof APP !== 'undefined') {
|
||||
const result = next(action);
|
||||
|
||||
if (isPrejoinPageVisible(store.getState())) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const { jitsiTrack } = action.track;
|
||||
const muted = jitsiTrack.isMuted();
|
||||
const participantID = jitsiTrack.getParticipantId();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @flow
|
||||
import { generateRoomWithoutSeparator } from 'js-utils/random';
|
||||
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { getDefaultURL } from '../app/functions';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { generateRoomWithoutSeparator } from 'js-utils/random';
|
||||
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { createCalendarConnectedEvent, sendAnalytics } from '../analytics';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { jitsiLocalStorage } from 'js-utils';
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
VIDEO_QUALITY_LEVELS,
|
||||
conferenceLeft,
|
||||
getCurrentConference,
|
||||
setPreferredVideoQuality
|
||||
setMaxReceiverVideoQuality
|
||||
} from '../base/conference';
|
||||
import { hideDialog, isDialogOpen } from '../base/dialog';
|
||||
import { setActiveModalId } from '../base/modal';
|
||||
@@ -33,7 +33,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
dispatch(setFilmstripEnabled(!reducedUI));
|
||||
|
||||
dispatch(
|
||||
setPreferredVideoQuality(
|
||||
setMaxReceiverVideoQuality(
|
||||
reducedUI
|
||||
? VIDEO_QUALITY_LEVELS.LOW
|
||||
: VIDEO_QUALITY_LEVELS.HIGH));
|
||||
|
||||
@@ -342,6 +342,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||
bridgeCount,
|
||||
e2eRtt,
|
||||
framerate,
|
||||
maxEnabledResolution,
|
||||
packetLoss,
|
||||
region,
|
||||
resolution,
|
||||
@@ -358,6 +359,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||
e2eRtt = { e2eRtt }
|
||||
framerate = { framerate }
|
||||
isLocalVideo = { this.props.isLocalVideo }
|
||||
maxEnabledResolution = { maxEnabledResolution }
|
||||
onShowMore = { this._onToggleShowMore }
|
||||
packetLoss = { packetLoss }
|
||||
region = { region }
|
||||
|
||||
@@ -57,6 +57,12 @@ type Props = {
|
||||
*/
|
||||
isLocalVideo: boolean,
|
||||
|
||||
/**
|
||||
* The send-side max enabled resolution (aka the highest layer that is not
|
||||
* suspended on the send-side).
|
||||
*/
|
||||
maxEnabledResolution: number,
|
||||
|
||||
/**
|
||||
* Callback to invoke when the show additional stats link is clicked.
|
||||
*/
|
||||
@@ -380,8 +386,8 @@ class ConnectionStatsTable extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderResolution() {
|
||||
const { resolution, t } = this.props;
|
||||
const resolutionString = Object.keys(resolution || {})
|
||||
const { resolution, maxEnabledResolution, t } = this.props;
|
||||
let resolutionString = Object.keys(resolution || {})
|
||||
.map(ssrc => {
|
||||
const { width, height } = resolution[ssrc];
|
||||
|
||||
@@ -389,6 +395,12 @@ class ConnectionStatsTable extends Component<Props> {
|
||||
})
|
||||
.join(', ') || 'N/A';
|
||||
|
||||
if (maxEnabledResolution && maxEnabledResolution < 720) {
|
||||
const maxEnabledResolutionTitle = t('connectionindicator.maxEnabledResolution');
|
||||
|
||||
resolutionString += ` (${maxEnabledResolutionTitle} ${maxEnabledResolution}p)`;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
@@ -40,7 +40,7 @@ class KnockingParticipantList extends AbstractKnockingParticipantList<Props> {
|
||||
className = { _toolboxVisible ? 'toolbox-visible' : '' }
|
||||
id = 'knocking-participant-list'>
|
||||
<span className = 'title'>
|
||||
Knocking participant list
|
||||
{ t('lobby.knockingParticipantList') }
|
||||
</span>
|
||||
<ul>
|
||||
{ _participants.map(p => (
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
// @flow
|
||||
|
||||
import { getCurrentConference } from '../base/conference';
|
||||
import { toState } from '../base/redux';
|
||||
|
||||
const JID_PATTERN = '[^@]+@[^/]+/(.+)';
|
||||
|
||||
/**
|
||||
* Returns a knocking participant by ID or JID.
|
||||
*
|
||||
* @param {Function | Object} stateful - The Redux state or a function that resolves to the Redux state.
|
||||
* @param {string} id - The ID or JID of the participant.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getKnockingParticipantById(stateful: Function | Object, id: string): Object {
|
||||
const { knockingParticipants } = toState(stateful)['features/lobby'];
|
||||
const idToFind = getIdFromJid(id) || id;
|
||||
|
||||
return knockingParticipants.find(p => p.id === idToFind);
|
||||
}
|
||||
|
||||
/**
|
||||
* Approves (lets in) or rejects a knocking participant.
|
||||
@@ -21,3 +38,15 @@ export function setKnockingParticipantApproval(getState: Function, id: string, a
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an ID from a JID, if a JID is provided, undefined otherwise.
|
||||
*
|
||||
* @param {string} jid - The JID to get the ID from.
|
||||
* @returns {?string}
|
||||
*/
|
||||
function getIdFromJid(jid: string): ?string {
|
||||
const match = new RegExp(JID_PATTERN, 'g').exec(jid) || [];
|
||||
|
||||
return match[1];
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../base/conference';
|
||||
import { JitsiConferenceErrors, JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { getFirstLoadableAvatarUrl } from '../base/participants';
|
||||
import { getFirstLoadableAvatarUrl, getParticipantDisplayName } from '../base/participants';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
||||
import { NOTIFICATION_TYPE, showNotification } from '../notifications';
|
||||
import { isPrejoinPageEnabled } from '../prejoin/functions';
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
startKnocking,
|
||||
setPasswordJoinFailed
|
||||
} from './actions';
|
||||
import { getKnockingParticipantById } from './functions';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
@@ -43,7 +44,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
state => state['features/base/conference'].conference,
|
||||
(conference, { dispatch }, previousConference) => {
|
||||
(conference, { dispatch, getState }, previousConference) => {
|
||||
if (conference && !previousConference) {
|
||||
conference.on(JitsiConferenceEvents.MEMBERS_ONLY_CHANGED, enabled => {
|
||||
dispatch(setLobbyModeEnabled(enabled));
|
||||
@@ -66,6 +67,13 @@ StateListenerRegistry.register(
|
||||
conference.on(JitsiConferenceEvents.LOBBY_USER_LEFT, id => {
|
||||
dispatch(knockingParticipantLeft(id));
|
||||
});
|
||||
|
||||
conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, (origin, sender) =>
|
||||
_maybeSendLobbyNotification(origin, sender, {
|
||||
dispatch,
|
||||
getState
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -151,3 +159,43 @@ function _findLoadableAvatarForKnockingParticipant({ dispatch, getState }, { id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the endpoint message that arrived through the conference and
|
||||
* sends a lobby notification, if the message belongs to the feature.
|
||||
*
|
||||
* @param {Object} origin - The origin (initiator) of the message.
|
||||
* @param {Object} message - The actual message.
|
||||
* @param {Object} store - The Redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _maybeSendLobbyNotification(origin, message, { dispatch, getState }) {
|
||||
if (!origin?._id || message?.type !== 'lobby-notify') {
|
||||
return;
|
||||
}
|
||||
|
||||
const notificationProps: any = {
|
||||
descriptionArguments: {
|
||||
originParticipantName: getParticipantDisplayName(getState, origin._id)
|
||||
},
|
||||
titleKey: 'lobby.notificationTitle'
|
||||
};
|
||||
|
||||
switch (message.event) {
|
||||
case 'LOBBY-ENABLED':
|
||||
notificationProps.descriptionKey = `lobby.notificationLobby${message.value ? 'En' : 'Dis'}abled`;
|
||||
break;
|
||||
case 'LOBBY-ACCESS-GRANTED':
|
||||
notificationProps.descriptionKey = 'lobby.notificationLobbyAccessGranted';
|
||||
notificationProps.descriptionArguments.targetParticipantName
|
||||
= getKnockingParticipantById(getState, message.value)?.name;
|
||||
break;
|
||||
case 'LOBBY-ACCESS-DENIED':
|
||||
notificationProps.descriptionKey = 'lobby.notificationLobbyAccessDenied';
|
||||
notificationProps.descriptionArguments.targetParticipantName
|
||||
= getKnockingParticipantById(getState, message.value)?.name;
|
||||
break;
|
||||
}
|
||||
|
||||
dispatch(showNotification(notificationProps, 5000));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* @flow */
|
||||
|
||||
import { jitsiLocalStorage } from 'js-utils';
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
|
||||
import logger from '../logger';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { randomInt } from 'js-utils/random';
|
||||
import { randomInt } from '@jitsi/js-utils/random';
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
|
||||
@@ -14,6 +14,11 @@ export const SET_DEVICE_STATUS = 'SET_DEVICE_STATUS';
|
||||
*/
|
||||
export const SET_SKIP_PREJOIN = 'SET_SKIP_PREJOIN';
|
||||
|
||||
/**
|
||||
* Action type used to set the mandatory stance of the prejoin display name.
|
||||
*/
|
||||
export const SET_PREJOIN_DISPLAY_NAME_REQUIRED = 'SET_PREJOIN_DISPLAY_NAME_REQUIRED';
|
||||
|
||||
/**
|
||||
* Action type to set the country to dial out to.
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
SET_DIALOUT_COUNTRY,
|
||||
SET_DIALOUT_NUMBER,
|
||||
SET_DIALOUT_STATUS,
|
||||
SET_PREJOIN_DISPLAY_NAME_REQUIRED,
|
||||
SET_SKIP_PREJOIN,
|
||||
SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
|
||||
SET_PREJOIN_DEVICE_ERRORS,
|
||||
@@ -198,14 +199,13 @@ export function initPrejoin(tracks: Object[], errors: Object) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins the conference.
|
||||
* Action used to start the conference.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function joinConference() {
|
||||
return function(dispatch: Function) {
|
||||
dispatch(setPrejoinPageVisibility(false));
|
||||
dispatch(startConference());
|
||||
return {
|
||||
type: PREJOIN_START_CONFERENCE
|
||||
};
|
||||
}
|
||||
|
||||
@@ -342,6 +342,17 @@ export function setDialOutCountry(value: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to set the stance of the display name.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinDisplayNameRequired() {
|
||||
return {
|
||||
type: SET_PREJOIN_DISPLAY_NAME_REQUIRED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to set the dial out number.
|
||||
*
|
||||
@@ -406,14 +417,3 @@ export function setPrejoinPageVisibility(value: boolean) {
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to mark the start of the conference.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
function startConference() {
|
||||
return {
|
||||
type: PREJOIN_START_CONFERENCE
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ import {
|
||||
setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
|
||||
} from '../actions';
|
||||
import {
|
||||
isJoinByPhoneButtonVisible,
|
||||
isDeviceStatusVisible,
|
||||
isDisplayNameRequired,
|
||||
isJoinByPhoneButtonVisible,
|
||||
isJoinByPhoneDialogVisible
|
||||
} from '../functions';
|
||||
|
||||
@@ -38,6 +39,11 @@ type Props = {
|
||||
*/
|
||||
hasJoinByPhoneButton: boolean,
|
||||
|
||||
/**
|
||||
* If join button is disabled or not.
|
||||
*/
|
||||
joinButtonDisabled: boolean,
|
||||
|
||||
/**
|
||||
* Joins the current meeting.
|
||||
*/
|
||||
@@ -73,11 +79,21 @@ type Props = {
|
||||
*/
|
||||
setJoinByPhoneDialogVisiblity: Function,
|
||||
|
||||
/**
|
||||
* Indicates whether the avatar should be shown when video is off
|
||||
*/
|
||||
showAvatar: boolean,
|
||||
|
||||
/**
|
||||
* Flag signaling the visibility of camera preview.
|
||||
*/
|
||||
showCameraPreview: boolean,
|
||||
|
||||
/**
|
||||
* Flag signaling the visibility of join label, input and buttons
|
||||
*/
|
||||
showJoinActions: boolean,
|
||||
|
||||
/**
|
||||
* If 'JoinByPhoneDialog' is visible or not.
|
||||
*/
|
||||
@@ -106,6 +122,15 @@ type State = {
|
||||
* This component is displayed before joining a meeting.
|
||||
*/
|
||||
class Prejoin extends Component<Props, State> {
|
||||
/**
|
||||
* Default values for {@code Prejoin} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
showJoinActions: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Prejoin} instance.
|
||||
*
|
||||
@@ -212,12 +237,15 @@ class Prejoin extends Component<Props, State> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
joinButtonDisabled,
|
||||
hasJoinByPhoneButton,
|
||||
joinConference,
|
||||
joinConferenceWithoutAudio,
|
||||
name,
|
||||
showAvatar,
|
||||
showCameraPreview,
|
||||
showDialog,
|
||||
showJoinActions,
|
||||
t,
|
||||
videoTrack
|
||||
} = this.props;
|
||||
@@ -228,61 +256,66 @@ class Prejoin extends Component<Props, State> {
|
||||
return (
|
||||
<PreMeetingScreen
|
||||
footer = { this._renderFooter() }
|
||||
name = { name }
|
||||
showAvatar = { showAvatar }
|
||||
showConferenceInfo = { showJoinActions }
|
||||
title = { t('prejoin.joinMeeting') }
|
||||
videoMuted = { !showCameraPreview }
|
||||
videoTrack = { videoTrack }>
|
||||
<div className = 'prejoin-input-area-container'>
|
||||
<div className = 'prejoin-input-area'>
|
||||
<InputField
|
||||
onChange = { _setName }
|
||||
onSubmit = { joinConference }
|
||||
placeHolder = { t('dialog.enterDisplayName') }
|
||||
value = { name } />
|
||||
{showJoinActions && (
|
||||
<div className = 'prejoin-input-area-container'>
|
||||
<div className = 'prejoin-input-area'>
|
||||
<InputField
|
||||
onChange = { _setName }
|
||||
onSubmit = { joinConference }
|
||||
placeHolder = { t('dialog.enterDisplayName') }
|
||||
value = { name } />
|
||||
|
||||
<div className = 'prejoin-preview-dropdown-container'>
|
||||
<InlineDialog
|
||||
content = { <div className = 'prejoin-preview-dropdown-btns'>
|
||||
<div
|
||||
className = 'prejoin-preview-dropdown-btn'
|
||||
onClick = { joinConferenceWithoutAudio }>
|
||||
<Icon
|
||||
className = 'prejoin-preview-dropdown-icon'
|
||||
size = { 24 }
|
||||
src = { IconVolumeOff } />
|
||||
{ t('prejoin.joinWithoutAudio') }
|
||||
</div>
|
||||
{hasJoinByPhoneButton && <div
|
||||
className = 'prejoin-preview-dropdown-btn'
|
||||
onClick = { _showDialog }>
|
||||
<Icon
|
||||
className = 'prejoin-preview-dropdown-icon'
|
||||
size = { 24 }
|
||||
src = { IconPhone } />
|
||||
{ t('prejoin.joinAudioByPhone') }
|
||||
</div>}
|
||||
</div> }
|
||||
isOpen = { showJoinByPhoneButtons }
|
||||
onClose = { _onDropdownClose }>
|
||||
<ActionButton
|
||||
disabled = { !name }
|
||||
hasOptions = { true }
|
||||
onClick = { joinConference }
|
||||
onOptionsClick = { _onOptionsClick }
|
||||
type = 'primary'>
|
||||
{ t('prejoin.joinMeeting') }
|
||||
</ActionButton>
|
||||
</InlineDialog>
|
||||
<div className = 'prejoin-preview-dropdown-container'>
|
||||
<InlineDialog
|
||||
content = { <div className = 'prejoin-preview-dropdown-btns'>
|
||||
<div
|
||||
className = 'prejoin-preview-dropdown-btn'
|
||||
onClick = { joinConferenceWithoutAudio }>
|
||||
<Icon
|
||||
className = 'prejoin-preview-dropdown-icon'
|
||||
size = { 24 }
|
||||
src = { IconVolumeOff } />
|
||||
{ t('prejoin.joinWithoutAudio') }
|
||||
</div>
|
||||
{hasJoinByPhoneButton && <div
|
||||
className = 'prejoin-preview-dropdown-btn'
|
||||
onClick = { _showDialog }>
|
||||
<Icon
|
||||
className = 'prejoin-preview-dropdown-icon'
|
||||
size = { 24 }
|
||||
src = { IconPhone } />
|
||||
{ t('prejoin.joinAudioByPhone') }
|
||||
</div>}
|
||||
</div> }
|
||||
isOpen = { showJoinByPhoneButtons }
|
||||
onClose = { _onDropdownClose }>
|
||||
<ActionButton
|
||||
disabled = { joinButtonDisabled }
|
||||
hasOptions = { true }
|
||||
onClick = { joinConference }
|
||||
onOptionsClick = { _onOptionsClick }
|
||||
type = 'primary'>
|
||||
{ t('prejoin.joinMeeting') }
|
||||
</ActionButton>
|
||||
</InlineDialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className = 'prejoin-checkbox-container'>
|
||||
<input
|
||||
className = 'prejoin-checkbox'
|
||||
onChange = { _onCheckboxChange }
|
||||
type = 'checkbox' />
|
||||
<span>{t('prejoin.doNotShow')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className = 'prejoin-checkbox-container'>
|
||||
<input
|
||||
className = 'prejoin-checkbox'
|
||||
onChange = { _onCheckboxChange }
|
||||
type = 'checkbox' />
|
||||
<span>{t('prejoin.doNotShow')}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{ showDialog && (
|
||||
<JoinByPhoneDialog
|
||||
joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
|
||||
@@ -309,9 +342,13 @@ class Prejoin extends Component<Props, State> {
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state): Object {
|
||||
const name = getDisplayName(state);
|
||||
const joinButtonDisabled = isDisplayNameRequired(state) && !name;
|
||||
|
||||
return {
|
||||
joinButtonDisabled,
|
||||
name,
|
||||
deviceStatusVisible: isDeviceStatusVisible(state),
|
||||
name: getDisplayName(state),
|
||||
roomName: getRoomName(state),
|
||||
showDialog: isJoinByPhoneDialogVisible(state),
|
||||
hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
|
||||
|
||||
93
react/features/prejoin/components/PrejoinApp.js
Normal file
93
react/features/prejoin/components/PrejoinApp.js
Normal file
@@ -0,0 +1,93 @@
|
||||
// @flow
|
||||
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
import React from 'react';
|
||||
|
||||
import { BaseApp } from '../../../features/base/app';
|
||||
import { setConfig } from '../../base/config';
|
||||
import { createPrejoinTracks } from '../../base/tracks';
|
||||
import { initPrejoin } from '../actions';
|
||||
|
||||
import Prejoin from './Prejoin';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Indicates whether the avatar should be shown when video is off
|
||||
*/
|
||||
showAvatar: boolean,
|
||||
|
||||
/**
|
||||
* Flag signaling the visibility of join label, input and buttons
|
||||
*/
|
||||
showJoinActions: boolean,
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper application for prejoin.
|
||||
*
|
||||
* @extends BaseApp
|
||||
*/
|
||||
export default class PrejoinApp extends BaseApp<Props> {
|
||||
_init: Promise<*>;
|
||||
|
||||
/**
|
||||
* Navigates to {@link Prejoin} upon mount.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
|
||||
this._init.then(async () => {
|
||||
const { store } = this.state;
|
||||
const { dispatch } = store;
|
||||
const { showAvatar, showJoinActions } = this.props;
|
||||
|
||||
super._navigate({
|
||||
component: Prejoin,
|
||||
props: {
|
||||
showAvatar,
|
||||
showJoinActions
|
||||
}
|
||||
});
|
||||
|
||||
const { startWithAudioMuted, startWithVideoMuted } = store.getState()['features/base/settings'];
|
||||
|
||||
dispatch(setConfig({
|
||||
prejoinPageEnabled: true,
|
||||
startWithAudioMuted,
|
||||
startWithVideoMuted
|
||||
}));
|
||||
|
||||
const { tryCreateLocalTracks, errors } = createPrejoinTracks();
|
||||
|
||||
const tracks = await tryCreateLocalTracks;
|
||||
|
||||
dispatch(initPrejoin(tracks, errors));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the parent method to inject {@link AtlasKitThemeProvider} as
|
||||
* the top most component.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_createMainElement(component, props) {
|
||||
return (
|
||||
<AtlasKitThemeProvider mode = 'dark'>
|
||||
{ super._createMainElement(component, props) }
|
||||
</AtlasKitThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific dialog container.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderDialogContainer() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,17 @@ export function isDeviceStatusVisible(state: Object): boolean {
|
||||
&& !state['features/base/config'].startSilent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the display name is mandatory.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isDisplayNameRequired(state: Object): boolean {
|
||||
return state['features/prejoin'].isDisplayNameRequired
|
||||
|| state['features/base/config'].requireDisplayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for the prejoin status bar.
|
||||
*
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { updateSettings } from '../base/settings';
|
||||
import { getLocalVideoTrack, replaceLocalTrack } from '../base/tracks';
|
||||
|
||||
import { PREJOIN_START_CONFERENCE } from './actionTypes';
|
||||
import { setPrejoinPageVisibility } from './actions';
|
||||
import { isPrejoinPageVisible } from './functions';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
@@ -19,18 +23,43 @@ MiddlewareRegistry.register(store => next => async action => {
|
||||
const { getState, dispatch } = store;
|
||||
const state = getState();
|
||||
const { userSelectedSkipPrejoin } = state['features/prejoin'];
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localVideoTrack = getLocalVideoTrack(state['features/base/tracks']);
|
||||
|
||||
userSelectedSkipPrejoin && dispatch(updateSettings({
|
||||
userSelectedSkipPrejoin
|
||||
}));
|
||||
|
||||
APP.conference.prejoinStart(tracks.map(t => t.jitsiTrack));
|
||||
if (localVideoTrack?.muted) {
|
||||
await dispatch(replaceLocalTrack(localVideoTrack.jitsiTrack, null));
|
||||
}
|
||||
|
||||
const jitsiTracks = getState()['features/base/tracks'].map(t => t.jitsiTrack);
|
||||
|
||||
dispatch(setPrejoinPageVisibility(false));
|
||||
APP.conference.prejoinStart(jitsiTracks);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_AUDIO_MUTED: {
|
||||
if (isPrejoinPageVisible(store.getState())) {
|
||||
store.dispatch(updateSettings({
|
||||
startWithAudioMuted: Boolean(action.muted)
|
||||
}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_VIDEO_MUTED: {
|
||||
if (isPrejoinPageVisible(store.getState())) {
|
||||
store.dispatch(updateSettings({
|
||||
startWithVideoMuted: Boolean(action.muted)
|
||||
}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
@@ -2,13 +2,14 @@ import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
SET_DEVICE_STATUS,
|
||||
SET_DIALOUT_NUMBER,
|
||||
SET_DIALOUT_COUNTRY,
|
||||
SET_DIALOUT_NUMBER,
|
||||
SET_DIALOUT_STATUS,
|
||||
SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
|
||||
SET_SKIP_PREJOIN,
|
||||
SET_PREJOIN_DEVICE_ERRORS,
|
||||
SET_PREJOIN_PAGE_VISIBILITY
|
||||
SET_PREJOIN_DISPLAY_NAME_REQUIRED,
|
||||
SET_PREJOIN_PAGE_VISIBILITY,
|
||||
SET_SKIP_PREJOIN
|
||||
} from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
@@ -22,6 +23,7 @@ const DEFAULT_STATE = {
|
||||
},
|
||||
dialOutNumber: '',
|
||||
dialOutStatus: 'prejoin.dialing',
|
||||
isDisplayNameRequired: false,
|
||||
name: '',
|
||||
rawError: '',
|
||||
showPrejoin: true,
|
||||
@@ -94,6 +96,13 @@ ReducerRegistry.register(
|
||||
};
|
||||
}
|
||||
|
||||
case SET_PREJOIN_DISPLAY_NAME_REQUIRED: {
|
||||
return {
|
||||
...state,
|
||||
isDisplayNameRequired: true
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { jitsiLocalStorage } from 'js-utils';
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
|
||||
import { APP_WILL_MOUNT } from '../base/app';
|
||||
import { getURLWithoutParamsNormalized } from '../base/connection';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { copyText } from '../../../invite';
|
||||
import { copyText } from '../../../base/util';
|
||||
|
||||
import PasswordForm from './PasswordForm';
|
||||
|
||||
|
||||
@@ -1083,7 +1083,11 @@ class Toolbox extends Component<Props, State> {
|
||||
} />
|
||||
);
|
||||
case 'closedcaptions':
|
||||
return <ClosedCaptionButton showLabel = { true } />;
|
||||
return (
|
||||
<ClosedCaptionButton
|
||||
key = 'closed-captions'
|
||||
showLabel = { true } />
|
||||
);
|
||||
case 'security':
|
||||
return (
|
||||
<SecurityDialogButton
|
||||
|
||||
@@ -32,8 +32,7 @@ StateListenerRegistry.register(
|
||||
dispatch(selectParticipant());
|
||||
|
||||
if (!displayTileView) {
|
||||
dispatch(
|
||||
setMaxReceiverVideoQuality(VIDEO_QUALITY_LEVELS.HIGH));
|
||||
dispatch(setMaxReceiverVideoQuality(VIDEO_QUALITY_LEVELS.HIGH));
|
||||
|
||||
if (_getAutoPinSetting()) {
|
||||
_updateAutoPinnedParticipant(store);
|
||||
|
||||
33
react/features/video-quality/middleware.js
Normal file
33
react/features/video-quality/middleware.js
Normal file
@@ -0,0 +1,33 @@
|
||||
// @flow
|
||||
|
||||
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||
import { setPreferredVideoQuality } from '../base/conference/actions';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Implements the middleware of the feature video-quality.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED: {
|
||||
if (navigator.product === 'ReactNative') {
|
||||
const { resolution } = getState()['features/base/config'];
|
||||
|
||||
if (typeof resolution !== 'undefined') {
|
||||
dispatch(setPreferredVideoQuality(Number.parseInt(resolution, 10)));
|
||||
logger.info(`Configured preferred receiver video frame height to: ${resolution}`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { generateRoomWithoutSeparator } from 'js-utils/random';
|
||||
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
|
||||
import { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import { getJitsiMeetTransport } from '../modules/transport';
|
||||
import { App } from './features/app/components';
|
||||
import { getLogger } from './features/base/logging/functions';
|
||||
import { Platform } from './features/base/react';
|
||||
import { getJitsiMeetGlobalNS } from './features/base/util';
|
||||
import PrejoinApp from './features/prejoin/components/PrejoinApp';
|
||||
|
||||
const logger = getLogger('index.web');
|
||||
const OS = Platform.OS;
|
||||
@@ -20,9 +22,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
APP.connectionTimes['document.ready'] = now;
|
||||
logger.log('(TIME) document ready:\t', now);
|
||||
|
||||
// Render the main/root Component.
|
||||
ReactDOM.render(<App />, document.getElementById('react'));
|
||||
});
|
||||
|
||||
// Workaround for the issue when returning to a page with the back button and
|
||||
@@ -56,3 +55,21 @@ window.addEventListener('beforeunload', () => {
|
||||
APP.API.dispose();
|
||||
getJitsiMeetTransport().dispose();
|
||||
});
|
||||
|
||||
const globalNS = getJitsiMeetGlobalNS();
|
||||
|
||||
globalNS.entryPoints = {
|
||||
APP: App,
|
||||
PREJOIN: PrejoinApp
|
||||
};
|
||||
|
||||
globalNS.renderEntryPoint = ({
|
||||
Component,
|
||||
props = {},
|
||||
elementId = 'react'
|
||||
}) => {
|
||||
ReactDOM.render(
|
||||
<Component { ...props } />,
|
||||
document.getElementById(elementId)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,15 +25,25 @@ function init_session(event)
|
||||
|
||||
if query ~= nil then
|
||||
local params = formdecode(query);
|
||||
|
||||
-- The following fields are filled in the session, by extracting them
|
||||
-- from the query and no validation is beeing done.
|
||||
-- After validating auth_token will be cleaned in case of error and few
|
||||
-- other fields will be extracted from the token and set in the session
|
||||
|
||||
session.auth_token = query and params.token or nil;
|
||||
-- previd is used together with https://modules.prosody.im/mod_smacks.html
|
||||
-- the param is used to find resumed session and re-use anonymous(random) user id
|
||||
-- (see get_username_from_token)
|
||||
session.previd = query and params.previd or nil;
|
||||
|
||||
-- The room name and optional prefix from the bosh query
|
||||
session.jitsi_bosh_query_room = params.room;
|
||||
session.jitsi_bosh_query_prefix = params.prefix or "";
|
||||
-- The room name and optional prefix from the web query
|
||||
session.jitsi_web_query_room = params.room;
|
||||
session.jitsi_web_query_prefix = params.prefix or "";
|
||||
|
||||
-- Deprecated, you should use jitsi_web_query_room and jitsi_web_query_prefix
|
||||
session.jitsi_bosh_query_room = session.jitsi_web_query_room;
|
||||
session.jitsi_bosh_query_prefix = session.jitsi_web_query_prefix;
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,6 +82,7 @@ function provider.get_sasl_handler(session)
|
||||
if (res == false) then
|
||||
log("warn",
|
||||
"Error verifying token err:%s, reason:%s", error, reason);
|
||||
session.auth_token = nil;
|
||||
return res, error, reason;
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
local st = require "util.stanza";
|
||||
local is_feature_allowed = module:require "util".is_feature_allowed;
|
||||
local token_util = module:require "token/util".new(module);
|
||||
|
||||
local accepted_rayo_iq_token_issuers = module:get_option_array("accepted_rayo_iq_token_issuers");
|
||||
|
||||
-- filters jibri iq in case of requested from jwt authenticated session that
|
||||
-- has features in the user context, but without feature for recording
|
||||
@@ -11,15 +14,37 @@ module:hook("pre-iq/full", function(event)
|
||||
local session = event.origin;
|
||||
local token = session.auth_token;
|
||||
|
||||
if jibri.attr.action == 'start'
|
||||
and (token == nil
|
||||
if jibri.attr.action == 'start' then
|
||||
local errorReason;
|
||||
if accepted_rayo_iq_token_issuers then
|
||||
local iq_token = jibri.attr.token;
|
||||
if iq_token then
|
||||
local session = {};
|
||||
session.auth_token = iq_token;
|
||||
local verified, reason = token_util:process_and_verify_token(
|
||||
session, accepted_rayo_iq_token_issuers);
|
||||
if verified then
|
||||
return nil; -- this will proceed with dispatching the stanza
|
||||
end
|
||||
errorReason = reason;
|
||||
else
|
||||
errorReason = 'No recording token provided';
|
||||
end
|
||||
|
||||
module:log("warn", "not a valid token %s", tostring(errorReason));
|
||||
session.send(st.error_reply(stanza, "auth", "forbidden"));
|
||||
return true;
|
||||
end
|
||||
|
||||
if token == nil
|
||||
or not is_feature_allowed(session,
|
||||
(jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming'))
|
||||
(jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming')
|
||||
) then
|
||||
module:log("info",
|
||||
"Filtering jibri start recording, stanza:%s", tostring(stanza));
|
||||
session.send(st.error_reply(stanza, "auth", "forbidden"));
|
||||
return true;
|
||||
module:log("info",
|
||||
"Filtering jibri start recording, stanza:%s", tostring(stanza));
|
||||
session.send(st.error_reply(stanza, "auth", "forbidden"));
|
||||
return true;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,20 +16,29 @@
|
||||
-- muc_room_default_public_jids = true
|
||||
--
|
||||
-- we use async to detect Prosody 0.10 and earlier
|
||||
local have_async = pcall(require, "util.async");
|
||||
local have_async = pcall(require, 'util.async');
|
||||
|
||||
if not have_async then
|
||||
module:log("warn", "Lobby rooms will not work with Prosody version 0.10 or less.");
|
||||
module:log('warn', 'Lobby rooms will not work with Prosody version 0.10 or less.');
|
||||
return;
|
||||
end
|
||||
|
||||
local formdecode = require "util.http".formdecode;
|
||||
local jid_split = require 'util.jid'.split;
|
||||
local jid_bare = require 'util.jid'.bare;
|
||||
local json = require 'util.json';
|
||||
local filters = require 'util.filters';
|
||||
local st = require 'util.stanza';
|
||||
local MUC_NS = 'http://jabber.org/protocol/muc';
|
||||
local DISCO_INFO_NS = 'http://jabber.org/protocol/disco#info';
|
||||
local DISPLAY_NAME_REQUIRED_FEATURE = 'http://jitsi.org/protocol/lobbyrooms#displayname_required';
|
||||
local LOBBY_IDENTITY_TYPE = 'lobbyrooms';
|
||||
local NOTIFY_JSON_MESSAGE_TYPE = 'lobby-notify';
|
||||
local NOTIFY_LOBBY_ENABLED = 'LOBBY-ENABLED';
|
||||
local NOTIFY_LOBBY_ACCESS_GRANTED = 'LOBBY-ACCESS-GRANTED';
|
||||
local NOTIFY_LOBBY_ACCESS_DENIED = 'LOBBY-ACCESS-DENIED';
|
||||
|
||||
local is_healthcheck_room = module:require "util".is_healthcheck_room;
|
||||
local is_healthcheck_room = module:require 'util'.is_healthcheck_room;
|
||||
|
||||
local main_muc_component_config = module:get_option_string('main_muc');
|
||||
if main_muc_component_config == nil then
|
||||
@@ -42,19 +51,26 @@ if lobby_muc_component_config == nil then
|
||||
return ;
|
||||
end
|
||||
|
||||
local whitelist = module:get_option_set("muc_lobby_whitelist", {});
|
||||
local whitelist;
|
||||
local check_display_name_required;
|
||||
local function load_config()
|
||||
whitelist = module:get_option_set('muc_lobby_whitelist', {});
|
||||
check_display_name_required
|
||||
= module:get_option_boolean('muc_lobby_check_display_name_required', true);
|
||||
end
|
||||
load_config();
|
||||
|
||||
local lobby_muc_service;
|
||||
local main_muc_service;
|
||||
|
||||
-- Checks whether there is self-status 110 of the <x node
|
||||
function check_self_status(muc_x)
|
||||
-- Checks whether there is status in the <x node
|
||||
function check_status(muc_x, status)
|
||||
if not muc_x then
|
||||
return false;
|
||||
end
|
||||
|
||||
for status in muc_x:childtags('status') do
|
||||
if status.attr.code == '110' then
|
||||
for statusNode in muc_x:childtags('status') do
|
||||
if statusNode.attr.code == status then
|
||||
return true;
|
||||
end
|
||||
end
|
||||
@@ -62,6 +78,42 @@ function check_self_status(muc_x)
|
||||
return false;
|
||||
end
|
||||
|
||||
function broadcast_json_msg(room, from, json_msg)
|
||||
json_msg.type = NOTIFY_JSON_MESSAGE_TYPE;
|
||||
|
||||
local occupant = room:get_occupant_by_real_jid(from);
|
||||
if occupant then
|
||||
room:broadcast_message(
|
||||
st.message({ type = 'groupchat', from = occupant.nick })
|
||||
:tag('json-message', {xmlns='http://jitsi.org/jitmeet'})
|
||||
:text(json.encode(json_msg)):up());
|
||||
end
|
||||
end
|
||||
|
||||
-- Sends a json message notifying for lobby enabled/disable
|
||||
-- the message from is the actor that did the operation
|
||||
function notify_lobby_enabled(room, actor, value)
|
||||
broadcast_json_msg(room, actor, {
|
||||
event = NOTIFY_LOBBY_ENABLED,
|
||||
value = value
|
||||
});
|
||||
end
|
||||
|
||||
-- Sends a json message notifying that the jid was granted/denied access in lobby
|
||||
-- the message from is the actor that did the operation
|
||||
function notify_lobby_access(room, actor, jid, granted)
|
||||
local notify_json = {
|
||||
value = jid
|
||||
};
|
||||
if granted then
|
||||
notify_json.event = NOTIFY_LOBBY_ACCESS_GRANTED;
|
||||
else
|
||||
notify_json.event = NOTIFY_LOBBY_ACCESS_DENIED;
|
||||
end
|
||||
|
||||
broadcast_json_msg(room, actor, notify_json);
|
||||
end
|
||||
|
||||
function filter_stanza(stanza)
|
||||
if not stanza.attr or not stanza.attr.from or not main_muc_service then
|
||||
return stanza;
|
||||
@@ -73,7 +125,7 @@ function filter_stanza(stanza)
|
||||
if stanza.name == 'presence' then
|
||||
local muc_x = stanza:get_child('x', MUC_NS..'#user');
|
||||
|
||||
if muc_x and check_self_status(muc_x) then
|
||||
if check_status(muc_x, '110') then
|
||||
return stanza;
|
||||
end
|
||||
|
||||
@@ -84,6 +136,9 @@ function filter_stanza(stanza)
|
||||
end
|
||||
|
||||
return nil;
|
||||
elseif stanza.name == 'iq' and stanza:get_child('query', DISCO_INFO_NS) then
|
||||
-- allow disco info from the lobby component
|
||||
return stanza;
|
||||
end
|
||||
|
||||
return nil;
|
||||
@@ -125,7 +180,24 @@ function process_lobby_muc_loaded(lobby_muc, host_module)
|
||||
filters.add_filter_hook(filter_session);
|
||||
|
||||
-- Advertise lobbyrooms support on main domain so client can pick up the address and use it
|
||||
module:add_identity('component', 'lobbyrooms', lobby_muc_component_config);
|
||||
module:add_identity('component', LOBBY_IDENTITY_TYPE, lobby_muc_component_config);
|
||||
|
||||
-- Tag the disco#info response with a feature that display name is required
|
||||
-- when the conference name from the web request has a lobby enabled.
|
||||
host_module:hook('host-disco-info-node', function (event)
|
||||
local session, reply, node = event.origin, event.reply, event.node;
|
||||
if node == LOBBY_IDENTITY_TYPE
|
||||
and session.jitsi_web_query_room
|
||||
and main_muc_service
|
||||
and check_display_name_required then
|
||||
local room = main_muc_service.get_room_from_jid(
|
||||
jid_bare(session.jitsi_web_query_room .. '@' .. main_muc_component_config));
|
||||
if room and room._data.lobbyroom then
|
||||
reply:tag('feature', { var = DISPLAY_NAME_REQUIRED_FEATURE }):up();
|
||||
end
|
||||
end
|
||||
event.exists = true;
|
||||
end);
|
||||
|
||||
local room_mt = lobby_muc_service.room_mt;
|
||||
-- we base affiliations (roles) in lobby muc component to be based on the roles in the main muc
|
||||
@@ -143,6 +215,15 @@ function process_lobby_muc_loaded(lobby_muc, host_module)
|
||||
|
||||
return 'none';
|
||||
end
|
||||
|
||||
-- listens for kicks in lobby room, 307 is the status for kick according to xep-0045
|
||||
host_module:hook('muc-broadcast-presence', function (event)
|
||||
local actor, occupant, room, x = event.actor, event.occupant, event.room, event.x;
|
||||
if check_status(x, '307') then
|
||||
-- we need to notify in the main room
|
||||
notify_lobby_access(room.main_room, actor, occupant.nick, false);
|
||||
end
|
||||
end);
|
||||
end
|
||||
|
||||
-- process or waits to process the lobby muc component
|
||||
@@ -169,7 +250,7 @@ process_host_module(main_muc_component_config, function(host_module, host)
|
||||
|
||||
-- hooks when lobby is enabled to create its room, only done here or by admin
|
||||
host_module:hook('muc-config-submitted', function(event)
|
||||
local room = event.room;
|
||||
local actor, room = event.actor, event.room;
|
||||
local members_only = event.fields['muc#roomconfig_membersonly'] and true or nil;
|
||||
if members_only then
|
||||
local node = jid_split(room.jid);
|
||||
@@ -179,29 +260,31 @@ process_host_module(main_muc_component_config, function(host_module, host)
|
||||
local new_room = lobby_muc_service.create_room(lobby_room_jid);
|
||||
new_room.main_room = room;
|
||||
room._data.lobbyroom = new_room;
|
||||
event.status_codes["104"] = true;
|
||||
event.status_codes['104'] = true;
|
||||
notify_lobby_enabled(room, actor, true);
|
||||
end
|
||||
elseif room._data.lobbyroom then
|
||||
room._data.lobbyroom:destroy(room.jid, 'Lobby room closed.');
|
||||
room._data.lobbyroom = nil;
|
||||
notify_lobby_enabled(room, actor, false);
|
||||
end
|
||||
end);
|
||||
host_module:hook("muc-room-destroyed",function(event)
|
||||
host_module:hook('muc-room-destroyed',function(event)
|
||||
local room = event.room;
|
||||
if room._data.lobbyroom then
|
||||
room._data.lobbyroom:destroy(nil, 'Lobby room closed.');
|
||||
room._data.lobbyroom = nil;
|
||||
end
|
||||
end);
|
||||
host_module:hook("muc-disco#info", function (event)
|
||||
host_module:hook('muc-disco#info', function (event)
|
||||
local room = event.room;
|
||||
if (room._data.lobbyroom and room:get_members_only()) then
|
||||
table.insert(event.form, {
|
||||
name = "muc#roominfo_lobbyroom";
|
||||
label = "Lobby room jid";
|
||||
value = "";
|
||||
name = 'muc#roominfo_lobbyroom';
|
||||
label = 'Lobby room jid';
|
||||
value = '';
|
||||
});
|
||||
event.formdata["muc#roominfo_lobbyroom"] = room._data.lobbyroom.jid;
|
||||
event.formdata['muc#roominfo_lobbyroom'] = room._data.lobbyroom.jid;
|
||||
end
|
||||
end);
|
||||
|
||||
@@ -212,7 +295,7 @@ process_host_module(main_muc_component_config, function(host_module, host)
|
||||
return;
|
||||
end
|
||||
|
||||
local join = stanza:get_child("x", MUC_NS);
|
||||
local join = stanza:get_child('x', MUC_NS);
|
||||
if not join then
|
||||
return;
|
||||
end
|
||||
@@ -236,7 +319,7 @@ process_host_module(main_muc_component_config, function(host_module, host)
|
||||
local affiliation = room:get_affiliation(invitee);
|
||||
if not affiliation or affiliation == 0 then
|
||||
event.occupant.role = 'participant';
|
||||
room:set_affiliation(true, invitee_bare_jid, "member");
|
||||
room:set_affiliation(true, invitee_bare_jid, 'member');
|
||||
room:save();
|
||||
|
||||
return;
|
||||
@@ -255,4 +338,41 @@ process_host_module(main_muc_component_config, function(host_module, host)
|
||||
return true;
|
||||
end
|
||||
end, -4); -- the default hook on members_only module is on -5
|
||||
|
||||
-- listens for invites for participants to join the main room
|
||||
host_module:hook('muc-invite', function(event)
|
||||
local room, stanza = event.room, event.stanza;
|
||||
local invitee = stanza.attr.to;
|
||||
local from = stanza:get_child('x', 'http://jabber.org/protocol/muc#user')
|
||||
:get_child('invite').attr.from;
|
||||
|
||||
if room._data.lobbyroom then
|
||||
local occupant = room._data.lobbyroom:get_occupant_by_real_jid(invitee);
|
||||
if occupant then
|
||||
notify_lobby_access(room, from, occupant.nick, true);
|
||||
end
|
||||
end
|
||||
end);
|
||||
end);
|
||||
|
||||
-- Extract 'room' param from URL when session is created
|
||||
function update_session(event)
|
||||
local session = event.session;
|
||||
|
||||
if session.jitsi_web_query_room then
|
||||
-- no need for an update
|
||||
return;
|
||||
end
|
||||
|
||||
local query = event.request.url.query;
|
||||
if query ~= nil then
|
||||
local params = formdecode(query);
|
||||
-- The room name and optional prefix from the web query
|
||||
session.jitsi_web_query_room = params.room;
|
||||
session.jitsi_web_query_prefix = params.prefix or '';
|
||||
end
|
||||
end
|
||||
|
||||
module:hook_global('bosh-session', update_session);
|
||||
module:hook_global('websocket-session', update_session);
|
||||
module:hook_global('config-reloaded', load_config);
|
||||
|
||||
@@ -106,8 +106,8 @@ prosody.events.add_handler("pre-jitsi-authentication", function(session)
|
||||
|
||||
if (session.jitsi_meet_context_user) then
|
||||
local room = get_room(
|
||||
session.jitsi_bosh_query_room,
|
||||
session.jitsi_bosh_query_prefix);
|
||||
session.jitsi_web_query_room,
|
||||
session.jitsi_web_query_prefix);
|
||||
|
||||
if (not room) then
|
||||
return nil;
|
||||
@@ -221,7 +221,9 @@ function handle_create_poltergeist (event)
|
||||
creator_user = session.jitsi_meet_context_user;
|
||||
creator_group = session.jitsi_meet_context_group;
|
||||
};
|
||||
|
||||
if avatar ~= nil then
|
||||
context.user.avatar = avatar
|
||||
end
|
||||
local resources = {};
|
||||
if conversation ~= nil then
|
||||
resources["conversation"] = conversation
|
||||
|
||||
@@ -137,7 +137,7 @@ function handle_get_room (event)
|
||||
local room_address
|
||||
= jid.join(room_name, muc_domain_prefix.."."..domain_name);
|
||||
|
||||
if subdomain ~= "" then
|
||||
if subdomain and subdomain ~= "" then
|
||||
room_address = "["..subdomain.."]"..room_address;
|
||||
end
|
||||
|
||||
|
||||
@@ -93,7 +93,8 @@ for event_name, method in pairs {
|
||||
return;
|
||||
end
|
||||
|
||||
if not session.auth_token then
|
||||
-- jitsi_meet_room is set after the token had been verified
|
||||
if not session.auth_token or not session.jitsi_meet_room then
|
||||
session.send(
|
||||
st.error_reply(
|
||||
stanza, "cancel", "not-allowed", "Room modification disabled for guests"));
|
||||
|
||||
@@ -159,9 +159,10 @@ end
|
||||
|
||||
--- Verifies issuer part of token
|
||||
-- @param 'iss' claim from the token to verify
|
||||
-- @param 'acceptedIssuers' list of issuers to check
|
||||
-- @return nil and error string or true for accepted claim
|
||||
function Util:verify_issuer(issClaim)
|
||||
for i, iss in ipairs(self.acceptedIssuers) do
|
||||
function Util:verify_issuer(issClaim, acceptedIssuers)
|
||||
for i, iss in ipairs(acceptedIssuers) do
|
||||
if issClaim == iss then
|
||||
--claim matches an accepted issuer so return success
|
||||
return true;
|
||||
@@ -192,8 +193,9 @@ end
|
||||
--- Verifies token
|
||||
-- @param token the token to verify
|
||||
-- @param secret the secret to use to verify token
|
||||
-- @param acceptedIssuers the list of accepted issuers to check
|
||||
-- @return nil and error or the extracted claims from the token
|
||||
function Util:verify_token(token, secret)
|
||||
function Util:verify_token(token, secret, acceptedIssuers)
|
||||
local claims, err = jwt.decode(token, secret, true);
|
||||
if claims == nil then
|
||||
return nil, err;
|
||||
@@ -209,7 +211,7 @@ function Util:verify_token(token, secret)
|
||||
return nil, "'iss' claim is missing";
|
||||
end
|
||||
--check the issuer against the accepted list
|
||||
local issCheck, issCheckErr = self:verify_issuer(issClaim);
|
||||
local issCheck, issCheckErr = self:verify_issuer(issClaim, acceptedIssuers);
|
||||
if issCheck == nil then
|
||||
return nil, issCheckErr;
|
||||
end
|
||||
@@ -241,8 +243,13 @@ end
|
||||
-- session.jitsi_meet_context_group - the group value from the token
|
||||
-- session.jitsi_meet_context_features - the features value from the token
|
||||
-- @param session the current session
|
||||
-- @param acceptedIssuers optional list of accepted issuers to check
|
||||
-- @return false and error
|
||||
function Util:process_and_verify_token(session)
|
||||
function Util:process_and_verify_token(session, acceptedIssuers)
|
||||
if not acceptedIssuers then
|
||||
acceptedIssuers = self.acceptedIssuers;
|
||||
end
|
||||
|
||||
if session.auth_token == nil then
|
||||
if self.allowEmptyToken then
|
||||
return true;
|
||||
@@ -272,9 +279,9 @@ function Util:process_and_verify_token(session)
|
||||
-- now verify the whole token
|
||||
local claims, msg;
|
||||
if self.asapKeyServer then
|
||||
claims, msg = self:verify_token(session.auth_token, pubKey);
|
||||
claims, msg = self:verify_token(session.auth_token, pubKey, acceptedIssuers);
|
||||
else
|
||||
claims, msg = self:verify_token(session.auth_token, self.appSecret);
|
||||
claims, msg = self:verify_token(session.auth_token, self.appSecret, acceptedIssuers);
|
||||
end
|
||||
if claims ~= nil then
|
||||
-- Binds room name to the session which is later checked on MUC join
|
||||
@@ -401,4 +408,4 @@ function Util:verify_room(session, room_address)
|
||||
end
|
||||
end
|
||||
|
||||
return Util;
|
||||
return Util;
|
||||
|
||||
16
resources/update-ljm.sh
Executable file
16
resources/update-ljm.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -u
|
||||
|
||||
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
|
||||
LATEST_LJM_COMMIT=$(git ls-remote https://github.com/jitsi/lib-jitsi-meet.git HEAD | awk '{ print $1 }')
|
||||
|
||||
pushd ${THIS_DIR}/..
|
||||
|
||||
npm install github:jitsi/lib-jitsi-meet#${LATEST_LJM_COMMIT}
|
||||
git add package.json package-lock.json
|
||||
git commit -m "deps: lib-jitsi-meet@latest"
|
||||
|
||||
popd
|
||||
|
||||
echo "Done! Now push your branch to GH and open a PR!"
|
||||
41
static/prejoin.html
Normal file
41
static/prejoin.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<html xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!--#include virtual="/base.html" -->
|
||||
<link rel="stylesheet" href="../css/all.css">
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!JitsiMeetJS.app) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
const params = new URLSearchParams(url.search);
|
||||
const showAvatar = params.get('showAvatar') === 'true';
|
||||
const showJoinActions = params.get('showJoinActions') === 'true';
|
||||
const css = params.get('style');
|
||||
const style = document.createElement('style');
|
||||
style.appendChild(document.createTextNode(css));
|
||||
document.head.appendChild(style);
|
||||
|
||||
JitsiMeetJS.app.renderEntryPoint({
|
||||
Component: JitsiMeetJS.app.entryPoints.PREJOIN,
|
||||
props: {
|
||||
showAvatar,
|
||||
showJoinActions
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<!--#include virtual="/title.html" -->
|
||||
<script>var config = {}</script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script>var interfaceConfig = {}</script>
|
||||
<script src="../libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||
<script src="../libs/app.bundle.min.js?v=139"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="react"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -55,7 +55,7 @@ const config = {
|
||||
// as well.
|
||||
|
||||
exclude: [
|
||||
new RegExp(`${__dirname}/node_modules/(?!js-utils)`)
|
||||
new RegExp(`${__dirname}/node_modules/(?!@jitsi/js-utils)`)
|
||||
],
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
|
||||
Reference in New Issue
Block a user