Compare commits

...

34 Commits
5237 ... 5259

Author SHA1 Message Date
Saúl Ibarra Corretgé
4acafebe5f chore(deps) lib-jitsi-meet@latest
* feat(e2ee) add support for WebRTC Encoded Transform
* fix(JitsiConference) skip AudioOutputProblemDetector on disableAudioLevels
* feat(xmpp) remove clientNode config option
* Workaround for Chrome ice connection failure detection (#1685)

c23abfa2bc...61c977f70a
2021-08-25 09:53:07 +02:00
Hristo Terezov
88a11b9f3e fix(TileView): not showing all participants. 2021-08-24 15:40:29 -05:00
Дамян Минков
bcc326c150 fix: Fixes undefined errors in MeetingParticipant - Item and ContextMenu. 2021-08-24 14:38:54 -05:00
Дамян Минков
e348270099 Revert "fix: Fixes reloads after enabling AV moderation."
This reverts commit 38f9c97f40.
2021-08-24 14:38:54 -05:00
Дамян Минков
fc59cdbdbe fix: Fixes showing awaiting AV moderation after grant moderator. 2021-08-24 14:38:54 -05:00
Saúl Ibarra Corretgé
59ef5c4789 feat(e2ee) add support for WebRTC Encoded Transform
An alternative to Insertable Streams, currently implemented in Safarii / WebKit.

https://w3c.github.io/webrtc-encoded-transform/

It's currently behind a config flag, both in Safari and here.

Fixes: https://github.com/jitsi/jitsi-meet/issues/9585
2021-08-24 17:43:20 +02:00
hmuresan
b6b943e7de feat(lobby) Add sound for participant knocking 2021-08-24 18:01:10 +03:00
Дамян Минков
48efa4ac61 feat: Updates i18n-iso-countries to latest. Fixes #9792. 2021-08-24 09:19:29 -05:00
Christoph Settgast
2f1105e6d2 chore(deps) @matrix-org/olm@latest
Bugfixes since 3.2.1, see https://gitlab.matrix.org/matrix-org/olm/-/blob/master/CHANGELOG.rst
In 3.2.2 the package name changed to @matrix-org/olm, thus
updating in Makefile & imports

Signed-off-by: Christoph Settgast <csett86@web.de>
2021-08-24 15:40:18 +02:00
Calin Chitu
9d5024cc5d fix(authentication) fixed close on cancel click 2021-08-24 15:39:56 +02:00
hmuresan
8b23265a50 fix(notifications) Add timeout for video/audio lost notifs 2021-08-24 16:30:02 +03:00
Saúl Ibarra Corretgé
5bc424459f fix(iframe) don't register service worker when in an iframe
Fixes: https://github.com/jitsi/jitsi-meet/issues/9712
2021-08-24 11:58:43 +02:00
robertpin
605ce9db62 Added default state to persistance 2021-08-24 10:07:44 +03:00
Дамян Минков
e2f760c7f1 fix: Fixes AV moderation used in tenants. 2021-08-23 09:43:53 -05:00
Дамян Минков
e63b3016c9 feat: Allow AV moderation for p2p users. 2021-08-23 09:43:53 -05:00
Дамян Минков
4d07d4ae76 feat: Always show AV moderation. 2021-08-23 09:43:53 -05:00
Дамян Минков
2616e126fb feat: Show footer context menu in p2p. 2021-08-23 09:43:53 -05:00
Дамян Минков
38f9c97f40 fix: Fixes reloads after enabling AV moderation. 2021-08-23 09:43:53 -05:00
robertpin
c7a91e1974 feat(reaction-sounds) Added sounds for reactions (#9775)
* Added sounds for reactions

* Updated reactions list

* Added reactions to sound settings

* Added support for multiple sounds

* Added feature flag for sounds

* Updated sound settings

Moved reactions toggle at the top of the list

* Added disable reaction sounds notification

* Added reaction button zoom for burst intensity

* Fixed raise hand sound

* Fixed register sounds for reactions

* Changed boo emoji

* Updated sounds

* Fixed lint errors

* Fixed reaction sounds file names

* Fix raise hand sound

Play sound only on raise hand not on lower hand

* Fixed types for sound constants

* Fixed type for raise hand sound constant
2021-08-23 12:57:56 +03:00
hmuresan
fe41eef398 fix(drawer-menu) Allow scroll on drawer menu items 2021-08-20 16:35:24 +03:00
Saúl Ibarra Corretgé
08177af182 fix(rn,amplitude) update Amplitude SDK on RN
Amplitude has gone back to having a separate package for RN, so switch to it.
2021-08-20 15:24:34 +02:00
José Luís Andrade
229520f74f fix(lang) update Portuguese translation 2021-08-20 14:39:57 +02:00
Saúl Ibarra Corretgé
0d9af05a4b chore(rn,versions) bump app and sdk versions 2021-08-20 11:51:03 +02:00
Avram Tudor
1ad9046a38 Improve premeeting screens ux (#9726)
* feat(prejoin) move invite to toolbar section

* feat(premeeting) redesign prejoin and lobby screens

* code review changes

* fix prejoin flicker and avatar id

* fix password error message and native lobby dialog close position
2021-08-20 11:53:11 +03:00
Avram Tudor
49a73ac446 fix(jaas) do not redirect to plan limit page on auth errors (#9746) 2021-08-20 11:36:09 +03:00
Avram Tudor
8e4a22bdbf fix(moderation) fix ui styles for advanced moderation context menu (#9758) 2021-08-20 11:23:37 +03:00
Saúl Ibarra Corretgé
ddbf334930 fix(e2ee) fix showing not supported warning when alone 2021-08-19 17:19:00 +02:00
Saúl Ibarra Corretgé
cd5f2b483f fix(e2ee) show entire content at all times 2021-08-19 17:19:00 +02:00
Saúl Ibarra Corretgé
cf34b0a783 fix(e2ee) update E2EE warning message 2021-08-19 17:19:00 +02:00
Saúl Ibarra Corretgé
10cc3b2b31 fix(ios) fix conference failing when proximity sensor is near
React Native links timers to the display, so they cannot run when the display is
not running. Builtin timers already take being in the background into account,
but not the proximity sensor.

Credits: https://github.com/react-native-webrtc/react-native-callkeep/issues/143

Fixes: https://github.com/jitsi/jitsi-meet/issues/9619
2021-08-19 13:55:35 +02:00
Saúl Ibarra Corretgé
27e4e862fd feat(xmpp) remove clientNode config option 2021-08-19 13:28:58 +02:00
Avram Tudor
4b3d92dcbd fix(moderation) highlight dominant speaker (#9750) 2021-08-19 14:08:30 +03:00
Saúl Ibarra Corretgé
5c1e2b4bd2 fix(AudioRoutePickerDialog) add proper margin 2021-08-19 10:47:53 +02:00
Saúl Ibarra Corretgé
a4d516ca86 fix(ios) fix deadlock when selecting audio device
In WebRTC M92 the RTCAudioSession lock changed from a recursive one to a regular
mutex one, so make sure we don't attempt to lock it  while already holding the
lock.
2021-08-19 10:47:53 +02:00
144 changed files with 1954 additions and 1368 deletions

View File

@@ -3,7 +3,7 @@ CLEANCSS = ./node_modules/.bin/cleancss
DEPLOY_DIR = libs
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
OLM_DIR = node_modules/olm
OLM_DIR = node_modules/@matrix-org/olm
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
TFLITE_WASM = react/features/stream-effects/virtual-background/vendor/tflite
MEET_MODELS_DIR = react/features/stream-effects/virtual-background/vendor/models/

View File

@@ -25,5 +25,5 @@ android.enableDexingArtifactTransform.desugaring=false
android.useAndroidX=true
android.enableJetifier=true
appVersion=21.3.0
sdkVersion=3.8.0
appVersion=21.4.0
sdkVersion=3.9.0

View File

@@ -56,6 +56,7 @@ dependencies {
exclude group: 'com.android.installreferrer'
}
} else {
implementation project(':amplitudereactnative')
implementation project(':react-native-device-info')
implementation(project(":react-native-google-signin")) {
exclude group: 'com.google.android.gms'

View File

@@ -203,6 +203,16 @@ class ReactInstanceManagerHolder {
}
}));
// AmplitudeReactNativePackage
try {
Class<?> amplitudePackageClass = Class.forName("com.amplitude.reactnative.AmplitudeReactNativePackage");
Constructor constructor = amplitudePackageClass.getConstructor();
packages.add((ReactPackage)constructor.newInstance());
} catch (Exception e) {
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
}
// RNGoogleSigninPackage
try {
Class<?> googlePackageClass = Class.forName("co.apptailor.googlesignin.RNGoogleSigninPackage");
Constructor constructor = googlePackageClass.getConstructor();

View File

@@ -1,6 +1,8 @@
rootProject.name = 'jitsi-meet'
include ':app', ':sdk'
include ':amplitudereactnative'
project(':amplitudereactnative').projectDir = new File(rootProject.projectDir, '../node_modules/@amplitude/react-native//android')
include ':react-native-async-storage'
project(':react-native-async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-async-storage/async-storage/android')
include ':react-native-background-timer'

2
app.js
View File

@@ -2,7 +2,7 @@
import 'jquery';
import 'olm';
import '@matrix-org/olm';
import 'focus-visible';

View File

@@ -27,9 +27,6 @@ var config = {
// Websocket URL
// websocket: 'wss://jitsi-meet.example.com/xmpp-websocket',
// The name of client node advertised in XEP-0115 'c' stanza
clientNode: 'http://jitsi.org/jitsimeet',
// The real JID of focus participant - can be overridden here
// Do not change username - FIXME: Make focus username configurable
// https://github.com/jitsi/jitsi-meet/issues/7376
@@ -388,6 +385,11 @@ var config = {
// bridge itself is reachable via UDP)
// useTurnUdp: false
// Enable support for encoded transform in supported browsers. This allows
// E2EE to work in Safari if the corresponding flag is enabled in the browser.
// Experimental.
// enableEncodedTransformSupport: false,
// UI
//

View File

@@ -17,6 +17,7 @@ import {
JitsiConnectionErrors,
JitsiConnectionEvents
} from './react/features/base/lib-jitsi-meet';
import { getCustomerDetails } from './react/features/jaas/actions.any';
import { isVpaasMeeting, getJaasJWT } from './react/features/jaas/functions';
import { setPrejoinDisplayNameRequired } from './react/features/prejoin/actions';
const logger = Logger.getLogger(__filename);
@@ -90,9 +91,13 @@ export async function connect(id, password, roomName) {
let { jwt } = state['features/base/jwt'];
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
if (!iAmRecorder && !iAmSipGateway && !jwt && isVpaasMeeting(state)) {
jwt = await getJaasJWT(state);
APP.store.dispatch(setJWT(jwt));
if (!iAmRecorder && !iAmSipGateway && isVpaasMeeting(state)) {
await APP.store.dispatch(getCustomerDetails());
if (!jwt) {
jwt = await getJaasJWT(state);
APP.store.dispatch(setJWT(jwt));
}
}
// Use Websocket URL for the web app if configured. Note that there is no 'isWeb' check, because there's assumption

View File

@@ -23,7 +23,7 @@
max-height: calc(80vh - 64px);
background: #242528;
border-radius: 16px 16px 0 0;
overflow-y: hidden;
overflow-y: scroll;
margin-bottom: env(safe-area-inset-bottom, 0);
width: 100%;

View File

@@ -5,14 +5,6 @@
.description {
font-size: 13px;
margin: 15px 0;
.read-more {
cursor: pointer;
opacity: .9;
color: #fff;
font-size: 0.8rem;
font-weight: bold;
}
}
.control-row {

View File

@@ -206,13 +206,3 @@
bottom: 0;
width: 35%;
}
/**
* Resizes elements width to fill the whole screen width with some margin
*/
@mixin adjust-for-max-width($width, $margin) {
@media (max-width: $width) {
margin: 0 $margin;
width: $width - 2 * $margin;
}
}

View File

@@ -48,4 +48,11 @@
.participants_pane-content {
width: 100%;
}
}
}
.jitsi-icon {
&-dominant-speaker {
background-color: #1EC26A;
border-radius: 3px;
}
}

View File

@@ -1,153 +0,0 @@
.prejoin {
&-input-area {
margin: 0 auto;
text-align: center;
&-label {
display: block;
margin-bottom: 5px;
color: #ffffff;
font-weight: 300;
font-size: 15px;
line-height: 24px;
}
}
&-title {
color: #fff;
font-size: 24px;
line-height: 32px;
margin-bottom: 16px;
}
&-text-btns {
display: flex;
justify-content: space-between;
}
&-input-label {
color: #A4B8D1;
font-size: 13px;
line-height: 20px;
margin-top: 32px 0 8px 0;
text-align: center;
width: 100%;
}
&-checkbox {
border: 0;
height: 16px;
margin-right: 8px;
padding: 0;
width: 16px;
}
&-checkbox-container {
margin-bottom: 14px;
width: 100%;
}
&-error {
color: white;
background-color: rgba(225, 45, 45, 0.6);
border-radius: 3px;
width: 100%;
padding: 2px;
box-sizing: border-box;
margin-top: 4px;
font-size: 13px;
text-align: center;
}
}
@mixin name-placeholder {
color: #fff;
font-weight: 300;
opacity: 0.6;
}
.prejoin-preview {
&-status {
align-items: center;
align-self: stretch;
bottom: 0;
color: #fff;
display: flex;
font-size: 13px;
min-height: 24px;
justify-content: center;
position: absolute;
text-align: center;
width: 100%;
z-index: 1;
&--warning {
background: rgba(241, 173, 51, 1);
}
&--ok {
background: rgba(49, 183, 106, 1);
}
}
&-icon {
background-position: center;
background-repeat: no-repeat;
display: inline-block;
height: 16px;
margin-right: 8px;
width: 16px;
}
&-error-desc {
margin-right: 4px;
color: #fff;
font-weight: bold;
}
.settings-button-container {
width: 49px;
margin: 0 8px;
}
&-dropdown-btns {
width: 320px;
padding: 8px 0;
@include adjust-for-max-width(320px, 8px);
}
&-dropdown-btn {
align-items: center;
color: #1C2025;
cursor: pointer;
display: flex;
height: 40px;
font-size: 15px;
line-height: 24px;
padding: 0 16px;
&:hover {
background-color: #DAEBFA;
}
}
&-dropdown-icon {
display: inline-block;
margin-right: 16px;
& > svg {
fill: #1C2025;
}
}
&-dropdown-container {
margin-top: 16px;
& > div:nth-child(2) {
background: #fff;
padding: 0;
}
}
}

View File

@@ -48,6 +48,13 @@
display: flex;
align-items: center;
justify-content: center;
transition: font-size ease .1s;
@for $i from 1 through 12 {
&.increase-#{$i}{
font-size: calc(20px + #{$i}px);
}
}
}
}

View File

@@ -334,7 +334,7 @@
border-radius: 0;
display: flex;
justify-content: space-evenly;
padding: 6px 0;
padding: 8px 0;
width: 100%;
}

View File

@@ -264,3 +264,9 @@ $chromeExtensionBannerRightInMeeeting: 10px;
*/
$smallScreen: 700px;
$verySmallScreen: 500px;
/**
* Prejoin / premeeting screen
*/
$prejoinDefaultContentWidth: 336px;

View File

@@ -79,7 +79,6 @@ $flagsImagePath: "../images/";
@import 'filmstrip/vertical_filmstrip';
@import 'filmstrip/vertical_filmstrip_overrides';
@import 'labels';
@import 'lobby';
@import 'unsupported-browser/main';
@import 'modals/invite/add-people';
@import 'deep-linking/main';
@@ -95,15 +94,12 @@ $flagsImagePath: "../images/";
@import 'meter';
@import 'audio-preview';
@import 'video-preview';
@import 'prejoin';
@import 'prejoin-dialog';
@import 'premeeting/main';
@import 'country-picker';
@import 'modals/invite/invite_more';
@import 'modals/security/security';
@import 'premeeting-screens';
@import 'e2ee';
@import 'responsive';
@import 'connection-status';
@import 'drawer';
@import 'participants-pane';
@import 'reactions-menu';

View File

@@ -1,32 +1,24 @@
.con-status {
border-radius: 6px;
color: #fff;
font-size: 12px;
letter-spacing: 0.16px;
line-height: 16px;
position: absolute;
top: 24px;
width: 100%;
z-index: $toolbarZ + 3;
&-container {
border-radius: 3px;
color: #fff;
font-size: 13px;
line-height: 13px;
margin: 0 auto;
width: 320px;
@include adjust-for-max-width(320px, 8px);
}
&-header {
background: rgba(28, 32, 37, .5);
background-color: rgba(0, 0, 0, 0.7);
align-items: center;
display: flex;
justify-content: space-between;
padding: 8px 12px;
}
&-circle {
border-radius: 50%;
display: inline-block;
padding: 4px;
margin: 8px;
margin-right: 16px;
}
&--good {
@@ -42,14 +34,7 @@
}
&-arrow {
height: 36px;
width: 36px;
border-radius: 3px;
margin-left: 8px;
margin-right: 2px;
display: flex;
align-items: center;
justify-content: center;
margin-left: auto;
transition: background-color 0.16s ease-out;
&--up {
@@ -70,7 +55,7 @@
}
&-details {
background: rgba(28, 32, 37, .5);
background-color: rgba(0, 0, 0, 0.7);
border-top: 1px solid #5E6D7A;
padding: 16px;
transition: opacity 0.16s ease-out;

View File

@@ -0,0 +1,35 @@
.device {
&-status {
align-items: center;
align-self: stretch;
color: #fff;
display: flex;
font-size: 14px;
font-weight: 400;
justify-content: center;
line-height: 20px;
margin-top: 8px;
padding: 6px;
text-align: center;
}
&-icon {
background-position: center;
background-repeat: no-repeat;
display: inline-block;
height: 16px;
margin-right: 10px;
width: 16px;
&--warning {
svg path {
fill: rgba(241, 173, 51, 1);
}
}
&--ok {
svg path {
fill: #189b55;
}
}
}
}

View File

@@ -1,18 +1,21 @@
#lobby-screen {
.content {
.lobby-screen {
font-size: 16px;
font-weight: 400;
line-height: 26px;
.container {
align-items: center;
display: flex;
flex-direction: column;
&-content {
align-items: center;
display: flex;
flex-direction: column;
.spinner {
margin: 30px;
}
.spinner {
margin: 8px;
}
.joining-message {
margin: 10px;
}
.joining-message {
color: white;
margin: 24px auto;
text-align: center;
}
}
}
@@ -68,7 +71,7 @@
button {
align-self: stretch;
margin: 8px 0;
margin-bottom: 8px 0;
padding: 12px;
transition: .2s transform ease;

View File

@@ -0,0 +1,7 @@
@import 'connection-status';
@import 'device-status';
@import 'lobby';
@import 'premeeting-screens';
@import 'prejoin';
@import 'prejoin-dialog';
@import 'prejoin-third-party';

View File

@@ -0,0 +1,39 @@
$sidePanelWidth: 300px;
.prejoin-third-party {
flex-direction: column-reverse;
.content {
height: auto;
margin: 0 auto;
.new-toolbox {
width: auto;
}
}
#preview {
background-color: transparent;
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
.avatar {
display: none;
}
}
&.splash {
.content {
margin-left: calc((100% - #{$prejoinDefaultContentWidth} + #{$sidePanelWidth}) / 2)
}
}
&.guest {
.content {
margin-bottom: auto;
}
}
}

View File

@@ -0,0 +1,73 @@
.prejoin {
&-input-area {
width: 100%;
}
&-checkbox-container {
margin-bottom: 16px;
width: 100%;
text-align: center;
}
&-error {
color: white;
background-color: #E04757;
border-radius: 6px;
padding: 4px;
box-sizing: border-box;
margin-bottom: 16px;
margin-top: -8px;
font-size: 12px;
text-align: center;
width: 100%;
}
}
.prejoin-preview {
&-dropdown-btns {
padding: 8px 0;
width: calc(100% - 48px);
}
&-dropdown-btn {
align-items: center;
color: #1C2025;
cursor: pointer;
display: flex;
height: 40px;
font-size: 15px;
line-height: 24px;
padding: 0 16px;
&:hover {
background-color: #DAEBFA;
}
}
&-dropdown-icon {
display: inline-block;
margin-right: 16px;
& > svg {
fill: #1C2025;
}
}
&-dropdown-container {
position: relative;
width: 100%;
/**
* Override default InlineDialog behaviour, since it does not play nicely with relative widths
*/
& > div:nth-child(2) {
background: #fff;
padding: 0;
position: absolute !important;
top: 48px !important;
transform: none !important;
width: 100%;
}
}
}

View File

@@ -1,47 +1,27 @@
/**
* Shared style for full screen local track based dialogs/modals.
*/
.premeeting-screen {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.premeeting-screen {
align-items: stretch;
background: radial-gradient(50% 50% at 50% 50%, #2A3A4B 20.83%, #1E2A36 100%);
background: #292929;
bottom: 0;
display: flex;
flex-direction: column;
font-size: 1.3em;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: $toolbarZ + 1;
&-avatar {
background-color: #A4B8D1;
margin-bottom: 24px;
text {
fill: black;
font-size: 26px;
font-weight: 400;
}
}
.action-btn {
border-radius: 3px;
border-radius: 6px;
box-sizing: border-box;
color: #fff;
cursor: pointer;
display: inline-block;
font-size: 15px;
font-size: 14px;
line-height: 24px;
margin-bottom: 16px;
padding: 7px 16px;
position: relative;
text-align: center;
width: 320px;
@include adjust-for-max-width(320px, 8px);
width: 100%;
&.primary {
background: #0376DA;
@@ -49,8 +29,8 @@
}
&.secondary {
background: transparent;
border: 1px solid #5E6D7A;
background: #3D3D3D;
border: 1px solid transparent;
}
&.text {
@@ -96,130 +76,150 @@
.content {
align-items: center;
box-sizing: border-box;
display: flex;
flex: 1;
flex-direction: column;
justify-content: flex-end;
padding-bottom: 24px;
flex-shrink: 0;
height: 100%;
margin: 0 110px;
padding: 24px 0 16px;
position: relative;
width: $prejoinDefaultContentWidth;
z-index: $toolbarZ + 2;
.title {
color: #fff;
font-size: 24px;
line-height: 32px;
margin-bottom: 16px;
}
.copy-meeting {
&-controls {
align-items: center;
cursor: pointer;
color: #fff;
display: flex;
flex-direction: column;
font-size: 15px;
font-weight: 300;
justify-content: center;
line-height: 24px;
margin-bottom: 16px;
margin: auto;
width: 100%;
.url {
background: rgba(28, 32, 37, 0.5);
border-radius: 4px;
display: flex;
padding: 8px 10px;
transition: background 0.16s ease-out;
&:hover {
background: #1C2025;
.title {
color: #fff;
font-size: 28px;
font-weight: 600;
letter-spacing: -0.015;
line-height: 36px;
margin-bottom: 32px;
text-align: center;
}
input.field {
background-color: white;
border: none;
outline: none;
border-radius: 6px;
font-size: 14px;
line-height: 20px;
margin-bottom: 16px;
color: #1C2025;
padding: 10px 16px;
text-align: center;
width: 100%;
&.error {
border: 1px solid #E04757;
}
&.done {
background: #31B76A;
}
.jitsi-icon {
margin-left: 10px;
&.focused {
box-shadow: 0px 0px 1px 1.5px black, 0px 0px 1.3px 4px white;
}
}
.copy-button{
width: 298px;
}
.copy-meeting-text {
width: 266px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#new-toolbox {
bottom: 0;
margin-bottom: 16px;
position: relative;
transition: none;
&:hover {
align-self: stretch;
}
textarea {
border-width: 0;
height: 0;
opacity: 0;
padding: 0;
width: 0;
}
}
input.field {
background-color: white;
border: none;
outline: none;
border-radius: 3px;
font-size: 15px;
line-height: 24px;
color: #1C2025;
padding: 8px 0;
text-align: center;
width: 320px;
@include adjust-for-max-width(320px, 8px);
&.error {
box-shadow: 0px 0px 4px 3px rgba(225, 45, 45, 0.4);
}
&.focused {
box-shadow: 0px 0px 1px 1.5px black, 0px 0px 1.3px 4px white;
.toolbox-content,
.toolbox-content-wrapper,
.toolbox-content-items {
box-sizing: border-box;
width: 100%;
}
}
}
}
.media-btn-container {
display: flex;
justify-content: center;
margin: 24px 0 16px 0;
width: 100%;
&> div {
margin: 0 12px;
@media (max-width: 1000px) {
flex-direction: column-reverse;
.content {
height: auto;
margin: 0 auto;
}
.con-status {
margin: 24px auto;
position: fixed;
top: 0;
width: $prejoinDefaultContentWidth;
}
}
@media (max-width: 400px) {
.content {
padding: 16px;
width: 100%;
.title {
font-size: 20px;
line-height: 28px;
letter-spacing: -0.012;
margin-bottom: 24px;
}
}
.con-status {
margin: 16px;
width: calc(100% - 32px);
}
input.field {
font-size: 16px;
padding: 14px 16px;
}
.action-btn {
font-size: 16px;
padding: 11px 16px;
}
.toolbox-content-items {
border-radius: 0;
display: flex;
justify-content: space-evenly;
padding: 8px 0;
}
}
input::placeholder {
color: #040404;
}
}
#preview {
background: #040404;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
position: absolute;
width: 100%;
&.no-video {
background: radial-gradient(50% 50% at 50% 50%, #5B6F80 0%, #365067 100%), #FFFFFF;
text-align: center;
}
.avatar {
background: #A4B8D1;
margin: 0 auto;
background: #0045B3;
text {
fill: white;
font-size: 26px;
font-weight: 400;
}
}
video {
height: 100%;
object-fit: cover;
position: absolute;
width: 100%;
}
}
@@ -241,16 +241,14 @@
}
.toggle-button {
border-radius: 3px;
border-radius: 6px;
cursor: pointer;
color: #fff;
font-size: 13px;
height: 40px;
margin: 0 auto;
transition: background 0.16s ease-out;
width: 320px;
@include adjust-for-max-width(320px, 8px);
@include flex-centered();
svg {

View File

@@ -25,8 +25,16 @@
Component: JitsiMeetJS.app.entryPoints.APP
})
const inIframe = () => {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
};
const isElectron = navigator.userAgent.includes('Electron');
const shouldRegisterWorker = !isElectron && 'serviceWorker' in navigator;
const shouldRegisterWorker = !isElectron && !inIframe() && 'serviceWorker' in navigator;
if (shouldRegisterWorker) {
navigator.serviceWorker

View File

@@ -29,7 +29,7 @@ target 'JitsiMeetSDK' do
'CoreModulesHeaders',
'DevSupport',
'RCTWebSocket'
]
], :modular_headers => true
pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
@@ -54,6 +54,7 @@ target 'JitsiMeetSDK' do
# React Native plugins
#
pod 'amplitude-react-native', :path => '../node_modules/@amplitude/react-native'
pod 'react-native-background-timer', :path => '../node_modules/react-native-background-timer'
pod 'react-native-calendar-events', :path => '../node_modules/react-native-calendar-events'
pod 'react-native-keep-awake', :path => '../node_modules/react-native-keep-awake'

View File

@@ -1,4 +1,8 @@
PODS:
- Amplitude (8.2.0)
- amplitude-react-native (2.3.3):
- Amplitude (= 8.2.0)
- React-Core
- AppAuth (1.4.0):
- AppAuth/Core (= 1.4.0)
- AppAuth/ExternalUserAgent (= 1.4.0)
@@ -372,6 +376,7 @@ PODS:
- Yoga (1.14.0)
DEPENDENCIES:
- "amplitude-react-native (from `../node_modules/@amplitude/react-native`)"
- CocoaLumberjack (~> 3.5.3)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector/`)
@@ -423,6 +428,7 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- Amplitude
- AppAuth
- boost-for-react-native
- CocoaLumberjack
@@ -444,6 +450,8 @@ SPEC REPOS:
- PromisesObjC
EXTERNAL SOURCES:
amplitude-react-native:
:path: "../node_modules/@amplitude/react-native"
DoubleConversion:
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
FBLazyVector:
@@ -528,6 +536,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
Amplitude: 263118b9e691e73a1c864b05bb08a3aff3636d16
amplitude-react-native: 833a4bd7f656f826bda1de01a7b8593b58842209
AppAuth: 31bcec809a638d7bd2f86ea8a52bd45f6e81e7c7
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
CocoaLumberjack: 2f44e60eb91c176d471fdba43b9e3eae6a721947
@@ -589,6 +599,6 @@ SPEC CHECKSUMS:
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c
PODFILE CHECKSUM: f4db44d934caeae7212dbaa33abe62ed164363e8
PODFILE CHECKSUM: 1ae1a9823f3eab0b6e735b9637ba7588e0890d08
COCOAPODS: 1.10.1

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>21.3.0</string>
<string>21.4.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>21.3.0</string>
<string>21.4.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>21.3.0</string>
<string>21.4.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UISupportedInterfaceOrientations</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>21.3.0</string>
<string>21.4.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CLKComplicationPrincipalClass</key>

View File

@@ -437,10 +437,12 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeetSDK/Pods-JitsiMeetSDK-resources.sh",
"${PODS_ROOT}/Amplitude/Sources/Amplitude/Resources/ComodoRsaDomainValidationCA.der",
"${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ComodoRsaDomainValidationCA.der",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle",
);
runOnlyForDeploymentPostprocessing = 0;

View File

@@ -125,12 +125,19 @@ RCT_EXPORT_MODULE();
return _workerQueue;
}
- (BOOL)setConfigWithoutLock:(RTCAudioSessionConfiguration *)config
error:(NSError * _Nullable *)outError {
RTCAudioSession *session = [RTCAudioSession sharedInstance];
return [session setConfiguration:config error:outError];
}
- (BOOL)setConfig:(RTCAudioSessionConfiguration *)config
error:(NSError * _Nullable *)outError {
RTCAudioSession *session = [RTCAudioSession sharedInstance];
[session lockForConfiguration];
BOOL success = [session setConfiguration:config error:outError];
BOOL success = [self setConfigWithoutLock:config error:outError];
[session unlockForConfiguration];
return success;
@@ -196,7 +203,7 @@ RCT_EXPORT_METHOD(setAudioDevice:(NSString *)device
break;
}
}
if (port != nil) {
// First remove the override if we are going to select a different device.
if (isSpeakerOn) {
@@ -206,11 +213,11 @@ RCT_EXPORT_METHOD(setAudioDevice:(NSString *)device
// Special case for the earpiece.
if ([port.portType isEqualToString:AVAudioSessionPortBuiltInMic]) {
forceEarpiece = YES;
[self setConfig:earpieceConfig error:nil];
[self setConfigWithoutLock:earpieceConfig error:nil];
} else if (isEarpieceOn) {
// Reset the config.
RTCAudioSessionConfiguration *config = [self configForMode:activeMode];
[self setConfig:config error:nil];
[self setConfigWithoutLock:config error:nil];
}
// Select our preferred input.

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.8.0</string>
<string>3.9.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -209,7 +209,6 @@
"e2eeLabel": "Ende-zu-Ende-Verschlüsselung aktivieren",
"e2eeWarning": "WARNUNG: Nicht alle Personen dieser Konferenz scheinen Ende-zu-Ende-Verschlüsselung zu unterstützen. Wenn Sie diese aktivieren, können die entsprechenden Personen nichts mehr sehen oder hören.",
"enterDisplayName": "Bitte geben Sie hier Ihren Namen ein",
"enterDisplayNameToJoin" : "Benutzername für Konferenz eingeben" ,
"embedMeeting": "Besprechung einbetten",
"error": "Fehler",
"gracefulShutdown": "Der Dienst steht momentan wegen Wartungsarbeiten nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",

View File

@@ -179,7 +179,7 @@
"e2eeLabel": "Ŝlosilo",
"e2eeTitle": "Tutvoja ĉifrado",
"e2eeWarning": "<br /><p><strong>ATENTIGO:</strong> Ne ĉiuj partoprenantoj en ĉi tiu kunveno ŝajnas havi subtenon de tutvoja ĉifrado. Se vi ŝaltos ĝin, ili ne povos vidi aŭ aŭdi vin.</p>",
"enterDisplayName": "Please enter your name here",
"enterDisplayName": "Enter your name here",
"error": "Eraro",
"externalInstallationMsg": "Vi devas instali nian ekranvidadan kromprogramon.",
"externalInstallationTitle": "Kromprogramo bezonata",

View File

@@ -203,7 +203,6 @@
"e2eeLabel": "Aktibatu puntutik punturako zifratzea",
"e2eeWarning": "OHARRA: bileraren partaide guztiek ezin dute puntutik punturako zifratzea erabili. Aukera hau aktibatzen baduzu, batzuk ezingo zaituzte ikusi eta entzun.",
"enterDisplayName": "Sartu zure izena hemen",
"enterDisplayNameToJoin": "Mesedez idatzi zure izena bileran sartzeko",
"embedMeeting": "Kapsulatu bilera",
"error": "Errorea",
"gracefulShutdown": "Zerbitzua ez dago erabilgarri mantentze-lanak direla eta. Saiatu berriro beranduago.",

View File

@@ -213,7 +213,6 @@
"e2eeLabel": "Activer le chiffrement de Bout-en-Bout",
"e2eeWarning": "ATTENTION : Tous les participants de cette réunion ne semblent pas prendre en charge le chiffrement de Bout-en-Bout. Si vous activez le chiffrement, ils ne pourront ni vous voir, ni vous entendre.",
"enterDisplayName": "Merci de saisir votre nom ici",
"enterDisplayNameToJoin": "Merci de saisir votre nom pour rejoindre",
"embedMeeting": "Intégrer la réunion",
"error": "Erreur",
"gracefulShutdown": "Notre service est actuellement en maintenance. Veuillez réessayer plus tard.",

View File

@@ -175,7 +175,7 @@
"dismiss": "Dismiss",
"displayNameRequired": "Hi! Whats your name?",
"done": "Done",
"enterDisplayName": "Please enter your name here",
"enterDisplayName": "Enter your name here",
"error": "Error",
"externalInstallationMsg": "You need to install our desktop sharing extension.",
"externalInstallationTitle": "Extension required",

View File

@@ -193,11 +193,10 @@
"dismiss": "Dispensar",
"displayNameRequired": "Olá! Qual é o seu nome?",
"done": "Feito",
"e2eeDescription": "A encriptação de ponta a ponta é actualmente EXPERIMENTAL. Tenha em mente que ligar a encriptação de ponta a ponta irá efectivamente desactivar os serviços fornecidos do lado do servidor, tais como: gravação, transmissão em directo e participação telefónica. Tenha também em mente que o encontro só funcionará para pessoas que se juntem a partir de browsers com suporte para \"insertable streams\".",
"e2eeDescription": "A encriptação de ponta a ponta é actualmente EXPERIMENTAL. Tenha em mente que ligar a encriptação de ponta a ponta irá efectivamente desactivar os serviços fornecidos do lado do servidor, tais como: gravação, transmissão em direto e participação telefónica. Tenha também em mente que o encontro só funcionará para pessoas que se juntem a partir de browsers com suporte para \"insertable streams\".",
"e2eeLabel": "Habilitar encriptação de ponta a ponta",
"e2eeWarning": "AVISO: Nem todos os participantes neste encontro parecem ter apoio para a encriptação de ponta a ponta. Se o permitir, eles não o poderão ver nem ouvir.",
"enterDisplayName": "Digite o seu nome aqui",
"enterDisplayNameToJoin": "Por favor, digite o seu nome para participar",
"embedMeeting": "Embutir reunião",
"error": "Erro",
"gracefulShutdown": "O nosso serviço está atualmente em manutenção. Por favor, tente novamente mais tarde.",
@@ -288,11 +287,11 @@
"sessTerminated": "Chamada terminada",
"sessionRestarted": "Chamada reiniciada pela ponte",
"Share": "Partilhar",
"shareVideoLinkError": "Por favor, forneça uma ligação correcta ao youtube.",
"shareVideoLinkError": "Por favor, forneça um link correcto do vídeo.",
"shareVideoTitle": "Partilhar vídeo",
"shareYourScreen": "Partilhe o seu ecrã",
"shareYourScreenDisabled": "Partilha de ecrã desactivada.",
"startLiveStreaming": "Iniciar a transmissão em directo",
"startLiveStreaming": "Iniciar a transmissão em direto",
"startRecording": "Iniciar gravação",
"startRemoteControlErrorMessage": "Ocorreu um erro ao tentar iniciar a sessão de controlo remoto!",
"stopLiveStreaming": "Parar a transmissão em direto",
@@ -310,7 +309,7 @@
"user": "Utilizador",
"userIdentifier": "Identificador do utilizador",
"userPassword": "Palavra-passe do utilizador",
"videoLink": "Ligação do vídeo",
"videoLink": "Link do vídeo",
"WaitForHostMsg": "A conferência <b>{{room}}</b> ainda não começou. Se for o anfitrião, por favor autentique. Caso contrário, por favor aguarde que o anfitrião chegue.",
"WaitForHostMsgWOk": "A conferência <b>{{room}}</b> ainda não começou. Se for o anfitrião, por favor prima Ok para autenticar. Caso contrário, por favor aguarde que o anfitrião chegue.",
"WaitingForHostTitle": "À espera do anfitrião ...",
@@ -354,13 +353,13 @@
"dialInSummaryError": "Ocorreu um erro ao buscar a informação de discagem. Tente novamente mais tarde.",
"dialInTollFree": "Chamada gratuita",
"genericError": "Oops, alguma coisa deu errado.",
"inviteLiveStream": "Para ver a transmissão ao vivo da reunião, clique no link: {{url}}",
"inviteLiveStream": "Para ver a transmissão em direto da reunião, clique no link: {{url}}",
"invitePhone": "Para participar por telefone, toque aqui: {{number}} ,, {{conferenceID}} # \\ n",
"invitePhoneAlternatives": "Procurando um número de discagem diferente?\nVeja os números de discagem da reunião: {{url}} \n\n\nSe você também estiver discando através de um telefone da sala, participe sem conectar-se ao áudio: {{silentUrl}}",
"inviteURLFirstPartGeneral": "Você foi convidado para uma reunião.",
"inviteURLFirstPartPersonal": "{{name}} está convidando você para uma reunião.\n",
"inviteURLSecondPart": "\nEntre na reunião:\n{{url}}\n",
"liveStreamURL": "Transmissão ao vivo:",
"liveStreamURL": "Transmissão em direto:",
"moreNumbers": "Mais números",
"noNumbers": "Sem números de discagem.",
"noPassword": "Nenhum",
@@ -406,31 +405,31 @@
"busy": "Estamos trabalhando para liberar os recursos de transmissão. Tente novamente em alguns minutos.",
"busyTitle": "Todas as transmissões estão atualmente ocupadas",
"changeSignIn": "Alternar contas.",
"choose": "Escolha uma transmissão ao vivo",
"choose": "Escolha uma transmissão em direto",
"chooseCTA": "Escolha uma opção de transmissão. Você está conectado atualmente como {{email}}.",
"enterStreamKey": "Insira sua chave de transmissão ao vivo do YouTube aqui.",
"error": "Falha na transmissão ao vivo. Tente de novo.",
"enterStreamKey": "Insira sua chave de transmissão em direto do YouTube aqui.",
"error": "Falha na transmissão em direto. Tente de novo.",
"errorAPI": "Ocorreu um erro ao acessar suas transmissões do YouTube. Por favor tente logar novamente.",
"errorLiveStreamNotEnabled": "Transmissão ao vivo não está ativada em {{email}}. Ative a transmissão ao vivo ou registre numa conta com transmissão ao vivo ativada.",
"expandedOff": "A transmissão ao vivo foi encerrada",
"errorLiveStreamNotEnabled": "Transmissão em direto não está ativada em {{email}}. Ative a transmissão em direto ou registre numa conta com transmissão direto ativada.",
"expandedOff": "A transmissão em direto foi encerrada",
"expandedOn": "A reunião está sendo transmitida pelo YouTube.",
"expandedPending": "Iniciando a transmissão ao vivo...",
"failedToStart": "Falha ao iniciar a transmissão ao vivo",
"getStreamKeyManually": "Não conseguimos buscar nenhuma transmissão ao vivo. Tente obter sua chave de transmissão ao vivo no YouTube.",
"invalidStreamKey": "A senha para transmissão ao vivo pode estar incorreta.",
"off": "Transmissão ao vivo encerrada",
"offBy": "{{name}} parou a transmissão ao vivo",
"on": "Transmissão ao Vivo",
"onBy": "{{name}} iniciou a transmissão ao vivo",
"pending": "Iniciando Transmissão ao Vivo...",
"serviceName": "Serviço de Transmissão ao Vivo",
"expandedPending": "Iniciando a transmissão em direto...",
"failedToStart": "Falha ao iniciar a transmissão em direto",
"getStreamKeyManually": "Não conseguimos buscar nenhuma transmissão em direto. Tente obter sua chave de transmissão em direto no YouTube.",
"invalidStreamKey": "A senha para transmissão em direto pode estar incorreta.",
"off": "Transmissão em direto encerrada",
"offBy": "{{name}} parou a transmissão em direto",
"on": "Transmissão em Direto",
"onBy": "{{name}} iniciou a transmissão em direto",
"pending": "Iniciando Transmissão em Direto...",
"serviceName": "Serviço de Transmissão em Direto",
"signedInAs": "Você está conectado como:",
"signIn": "Faça login no Google",
"signInCTA": "Faça login ou insira sua chave de transmissão ao vivo do YouTube.",
"signInCTA": "Faça login ou insira sua chave de transmissão em Direto do YouTube.",
"signOut": "Sair",
"start": "Iniciar uma transmissão ao vivo",
"start": "Iniciar uma transmissão em direto",
"streamIdHelp": "O que é isso?",
"unavailableTitle": "Transmissão ao vivo indisponível"
"unavailableTitle": "Transmissão em direto indisponível"
},
"localRecording": {
"clientState": {
@@ -581,7 +580,7 @@
"videoLowQuality": "Prevemos que o seu vídeo tenha baixa qualidade em termos de velocidade de fotogramas e resolução.",
"videoTearing": "Prevemos que o seu vídeo seja pixelizado ou que tenha artefactos visuais."
},
"copyAndShare": "Copiar e partilhar a ligação da reunião.",
"copyAndShare": "Copiar e partilhar o link da reunião.",
"dialInMeeting": "Entrar com chamada telefónica",
"dialInPin": "Entrar com chamada telefónica e introduzir o código PIN:",
"dialing": "A marcar",
@@ -598,8 +597,8 @@
"joinMeeting": "Entrar na reunião",
"joinWithoutAudio": "Entrar sem áudio",
"initiated": "Chamada iniciada",
"linkCopied": "Ligação copiada para a área de transferência",
"lookGood": "Parece que o seu microfone está a funcionar corretamente",
"linkCopied": "Link copiado para a área de transferência",
"lookGood": "O microfone está a funcionar corretamente",
"or": "ou",
"premeeting": "Pré-reunião",
"showScreen": "Ativar o ecrã de pré-reunião",
@@ -643,7 +642,7 @@
"expandedPending": "Iniciando gravação...",
"failedToStart": "Falha ao iniciar a gravação",
"fileSharingdescription": "Compartilhar gravação com participantes da reunião",
"live": "AOVIVO",
"live": "EM DIRETO",
"loggedIn": "Conectado como {{userName}}",
"off": "Gravação parada",
"offBy": "{{name}} parou a gravação",
@@ -786,7 +785,7 @@
"shareaudio": "Partilhar áudio",
"sharedvideo": "Mudar a partilha de vídeos do YouTube",
"shareRoom": "Convidar alguém",
"shareYourScreen": "Iniciar / Parar de partilhar o seu ecrã",
"shareYourScreen": "Iniciar / Parar partilha de ecrã",
"shortcuts": "Mostrar / Esconder atalhos",
"show": "Mostrar no palco",
"speakerStats": "Mostrar / Esconder estatísticas dos participantes",
@@ -929,7 +928,7 @@
"mute": "Participante está sem som",
"muted": "Sem som",
"videomute": "O participante parou a câmara",
"remoteControl": "Iniciar / Parar o controlo remoto",
"remoteControl": "Iniciar / Parar controlo remoto",
"show": "Mostrar no palco"
},
"welcomepage": {
@@ -967,7 +966,7 @@
"admitAll": "Aceitar todos",
"knockingParticipantList": "Lista de participantes a expulsar",
"allow": "Permitir",
"backToKnockModeButton": "Sem senha, peça para aderir em vez disso",
"backToKnockModeButton": "Peça para aderir",
"dialogTitle": "Modo sala de espera",
"disableDialogContent": "O modo sala de espera está actualmente activada. Esta característica assegura que os participantes indesejados não possam juntar-se à sua reunião. Quer desativá-la?",
"disableDialogSubmit": "Desativar",
@@ -977,6 +976,7 @@
"enableDialogText": "O modo sala de espera permite-lhe proteger a sua reunião apenas permitindo a entrada de pessoas após uma aprovação formal por um moderador.",
"enterPasswordButton": "Introduza a senha da reunião",
"enterPasswordTitle": "Introduzir a senha para participar na reunião",
"errorMissingPassword": "Por favor introduza a senha da reunião",
"invalidPassword": "Senha inválida",
"joiningMessage": "Participará na reunião assim que alguém aceitar o seu pedido",
"joinWithPasswordMessage": "Tentando aderir com senha, por favor aguarde...",

View File

@@ -209,7 +209,6 @@
"e2eeLabel": "Enable End-to-End Encryption",
"e2eeWarning": "WARNING: Not all participants in this meeting seem to have support for End-to-End encryption. If you enable it they won't be able to see nor hear you.",
"enterDisplayName": "Digite seu nome aqui",
"enterDisplayNameToJoin": "Digite seu nome para participar",
"embedMeeting": "Reunião em formato compacto",
"error": "Erro",
"gracefulShutdown": "Nosso serviço está em manutenção. Tente novamente mais tarde.",

View File

@@ -209,7 +209,6 @@
"e2eeLabel": "Aktivizo Fshehtëzim Skaj-më-Skaj",
"e2eeWarning": "KUJDES: Jo të gjithë pjesëmarrësit në këtë takim duket të kenë mbulim për fshehtëzim Skaj-më-Skaj. Në e aktivizofshi, ata sdo të jenë në gjendje tju shohin apo dëgjojnë.",
"enterDisplayName": "Ju lutemi, jepni këtu emrin tuaj",
"enterDisplayNameToJoin": "Që të merrni pjesë, ju lutemi, jepni emrin tuaj",
"embedMeeting": "Trupëzoni takim",
"error": "Gabim",
"gracefulShutdown": "Shërbimi ynë është aktualisht i ndërprerë, për punë mirëmbajtjeje. Ju lutemi, riprovoni më vonë.",

View File

@@ -209,7 +209,6 @@
"e2eeLabel": "啟用端對端加密",
"e2eeWarning": "警告:看來不是每位此會議的參與者都有啟用端對端加密,如果您啟用了,他們可能無法看/聽到您。",
"enterDisplayName": "請在此輸入您自己的名字",
"enterDisplayNameToJoin": "請輸入您的名字以加入",
"embedMeeting": "嵌入會議",
"error": "錯誤",
"gracefulShutdown": "我們的服務目前關閉維護中,請稍後再試。",

View File

@@ -209,11 +209,10 @@
"dismiss": "Dismiss",
"displayNameRequired": "Hi! Whats your name?",
"done": "Done",
"e2eeDescription": "End-to-End Encryption is currently EXPERIMENTAL. Please keep in mind that turning on end-to-end encryption will effectively disable server-side provided services such as: recording, live streaming and phone participation. Also keep in mind that the meeting will only work for people joining from browsers with support for insertable streams.",
"e2eeDescription": "End-to-End Encryption is currently EXPERIMENTAL. Please keep in mind that turning on end-to-end encryption will effectively disable server-side provided services such as: phone participation. Also keep in mind that the meeting will only work for people joining from browsers with support for insertable streams.",
"e2eeLabel": "Enable End-to-End Encryption",
"e2eeWarning": "WARNING: Not all participants in this meeting seem to have support for End-to-End encryption. If you enable it they won't be able to see nor hear you.",
"enterDisplayName": "Please enter your name here",
"enterDisplayNameToJoin": "Please enter your name to join",
"enterDisplayName": "Enter your name here",
"embedMeeting": "Embed meeting",
"error": "Error",
"gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
@@ -592,6 +591,7 @@
"moderationStoppedTitle": "Moderation stopped",
"moderationToggleDescription": "by {{participantDisplayName}}",
"raiseHandAction": "Raise hand",
"reactionSounds": "Disable sounds",
"groupTitle": "Notifications"
},
"participantsPane": {
@@ -694,7 +694,7 @@
"joinWithoutAudio": "Join without audio",
"initiated": "Call initiated",
"linkCopied": "Link copied to clipboard",
"lookGood": "It sounds like your microphone is working properly",
"lookGood": "Your microphone is working properly",
"or": "or",
"premeeting": "Pre meeting",
"showScreen": "Enable pre meeting screen",
@@ -795,6 +795,7 @@
"participantJoined": "Participant Joined",
"participantLeft": "Participant Left",
"playSounds": "Play sound on",
"reactions": "Meeting reactions",
"sameAsSystem": "Same as system ({{label}})",
"selectAudioOutput": "Audio output",
"selectCamera": "Camera",
@@ -885,7 +886,6 @@
"muteEveryonesVideo": "Disable everyone's camera",
"muteEveryoneElsesVideo": "Disable everyone else's camera",
"participants": "Participants",
"party": "Party Popper",
"pip": "Toggle Picture-in-Picture mode",
"privateMessage": "Send private message",
"profile": "Edit your profile",
@@ -902,6 +902,7 @@
"shareYourScreen": "Start / Stop sharing your screen",
"shortcuts": "Toggle shortcuts",
"show": "Show on stage",
"silence": "Silence",
"speakerStats": "Toggle speaker statistics",
"surprised": "Surprised",
"tileView": "Toggle tile view",
@@ -926,6 +927,7 @@
"clap": "Clap",
"closeChat": "Close chat",
"closeReactionsMenu": "Close reactions menu",
"disableReactionSounds": "You can disable reaction sounds for this meeting",
"documentClose": "Close shared document",
"documentOpen": "Open shared document",
"download": "Download our apps",
@@ -961,7 +963,6 @@
"openChat": "Open chat",
"openReactionsMenu": "Open reactions menu",
"participants": "Participants",
"party": "Celebration",
"pip": "Enter Picture-in-Picture mode",
"privateMessage": "Send private message",
"profile": "Edit your profile",
@@ -971,7 +972,7 @@
"reactionClap": "Send clap reaction",
"reactionLaugh": "Send laugh reaction",
"reactionLike": "Send thumbs up reaction",
"reactionParty": "Send party popper reaction",
"reactionSilence": "Send silence reaction",
"reactionSurprised": "Send surprised reaction",
"security": "Security options",
"Settings": "Settings",
@@ -979,6 +980,7 @@
"sharedvideo": "Share video",
"shareRoom": "Invite someone",
"shortcuts": "View shortcuts",
"silence": "Silence",
"speakerStats": "Speaker stats",
"startScreenSharing": "Start screen sharing",
"startSubtitles": "Start subtitles",
@@ -1121,7 +1123,7 @@
"admitAll": "Admit all",
"knockingParticipantList": "Knocking participant list",
"allow": "Allow",
"backToKnockModeButton": "No password, ask to join instead",
"backToKnockModeButton": "Ask to join",
"dialogTitle": "Lobby mode",
"disableDialogContent": "Lobby mode is currently enabled. This feature ensures that unwanted participants can't join your meeting. Do you want to disable it?",
"disableDialogSubmit": "Disable",
@@ -1131,6 +1133,7 @@
"enableDialogText": "Lobby mode lets you protect your meeting by only allowing people to enter after a formal approval by a moderator.",
"enterPasswordButton": "Enter meeting password",
"enterPasswordTitle": "Enter password to join meeting",
"errorMissingPassword": "Please enter the meeting password",
"invalidPassword": "Invalid password",
"joiningMessage": "You'll join the meeting as soon as someone accepts your request",
"joinWithPasswordMessage": "Trying to join with password, please wait...",

31
package-lock.json generated
View File

@@ -4,6 +4,11 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@amplitude/react-native": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/@amplitude/react-native/-/react-native-2.3.3.tgz",
"integrity": "sha512-QTpwy4lKy9kpBjB2334HCEIU7QwGFAkGRfp21aeDA87D6pkiUMAvyDYbz58CnB5HCXuqcvws3GN8d60RO9KF9A=="
},
"@amplitude/types": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@amplitude/types/-/types-1.5.5.tgz",
@@ -2898,6 +2903,10 @@
"react-is": "^16.8.0 || ^17.0.0"
}
},
"@matrix-org/olm": {
"version": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"integrity": "sha512-OhC9wwZ/ox9vputA1MR2A7QlYlvfXCV+tdbADOR7Jn7o9qoXh3HWf+AbSpXTK3daF0GIHA69Ws8XOnWqu5n53A=="
},
"@microsoft/microsoft-graph-client": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-client/-/microsoft-graph-client-1.1.0.tgz",
@@ -7353,6 +7362,11 @@
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
},
"diacritics": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz",
"integrity": "sha1-PvqHMj67hj5mls67AILUj/PW96E="
},
"didyoumean": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.1.tgz",
@@ -9868,9 +9882,12 @@
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
},
"i18n-iso-countries": {
"version": "3.7.8",
"resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-3.7.8.tgz",
"integrity": "sha512-NkT3lRiw7D4kKtSAVjVdHCvGlc2UOe0ALKa9IfEx0LkEDf0q3YgjP/veVk0d/OZ7yqUNzV8aJP4lJc6RPj++Gw=="
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-6.8.0.tgz",
"integrity": "sha512-jJs/+CA6+VUICFxqGcB0vFMERGfhfvyNk+8Vb9EagSZkl7kSpm/kT0VyhvzM/zixDWEV/+oN9L7v/GT9BwzoGg==",
"requires": {
"diacritics": "1.3.0"
}
},
"i18next": {
"version": "17.0.6",
@@ -11087,8 +11104,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#c23abfa2bcd2b04710e4180f9b878bacba33ba16",
"from": "github:jitsi/lib-jitsi-meet#c23abfa2bcd2b04710e4180f9b878bacba33ba16",
"version": "github:jitsi/lib-jitsi-meet#61c977f70ab353013a40e7daaeb5fc3713526984",
"from": "github:jitsi/lib-jitsi-meet#61c977f70ab353013a40e7daaeb5fc3713526984",
"requires": {
"@jitsi/js-utils": "1.0.2",
"@jitsi/sdp-interop": "github:jitsi/sdp-interop#5fc4af6dcf8a6e6af9fedbcd654412fd47b1b4ae",
@@ -12955,10 +12972,6 @@
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
"dev": true
},
"olm": {
"version": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"integrity": "sha512-B87bTpGIGieuV2FNauChjjQtVltwTGagQFoHm+3Dcse4amKAAGJB/I54dnP/JtbHZ+RYVoApM2OQ46Z4VH6eNg=="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",

View File

@@ -15,6 +15,7 @@
"author": "",
"readmeFilename": "README.md",
"dependencies": {
"@amplitude/react-native": "2.3.3",
"@atlaskit/button": "15.1.4",
"@atlaskit/checkbox": "12.0.0",
"@atlaskit/dropdown-menu": "10.1.2",
@@ -35,6 +36,7 @@
"@hapi/bourne": "2.0.0",
"@jitsi/js-utils": "1.0.6",
"@material-ui/core": "4.11.3",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@microsoft/microsoft-graph-client": "1.1.0",
"@react-native-async-storage/async-storage": "1.15.5",
"@react-native-community/google-signin": "3.0.1",
@@ -47,7 +49,7 @@
"clipboard-copy": "4.0.1",
"dropbox": "4.0.9",
"focus-visible": "5.1.0",
"i18n-iso-countries": "3.7.8",
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
"i18next-browser-languagedetector": "3.0.1",
"i18next-xhr-backend": "3.0.0",
@@ -56,12 +58,11 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#c23abfa2bcd2b04710e4180f9b878bacba33ba16",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#61c977f70ab353013a40e7daaeb5fc3713526984",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.21",
"moment": "2.29.1",
"moment-duration-format": "2.2.2",
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"optional-require": "1.0.3",
"pixelmatch": "5.1.0",
"promise.allsettled": "1.0.4",

View File

@@ -11,6 +11,39 @@ index bd48f44..d243ed0 100644
withDispatchGroup:(dispatch_group_t)dispatchGroup
lazilyDiscovered:(BOOL)lazilyDiscovered
{
diff --git a/node_modules/react-native/React/Modules/RCTTiming.m b/node_modules/react-native/React/Modules/RCTTiming.m
index 8a09022..265d7b6 100644
--- a/node_modules/react-native/React/Modules/RCTTiming.m
+++ b/node_modules/react-native/React/Modules/RCTTiming.m
@@ -130,6 +130,11 @@ - (void)setBridge:(RCTBridge *)bridge
object:nil];
}
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(proximityChanged)
+ name:UIDeviceProximityStateDidChangeNotification
+ object:nil];
+
_bridge = bridge;
}
@@ -276,6 +281,16 @@ - (void)didUpdateFrame:(RCTFrameUpdate *)update
}
}
+-(void)proximityChanged
+{
+ BOOL near = [UIDevice currentDevice].proximityState;
+ if (near) {
+ [self appDidMoveToBackground];
+ } else {
+ [self appDidMoveToForeground];
+ }
+}
+
- (void)scheduleSleepTimer:(NSDate *)sleepTarget
{
@synchronized (self) {
diff --git a/node_modules/react-native/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm b/node_modules/react-native/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm
index 3cb73b5..e4a14b4 100644
--- a/node_modules/react-native/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm

View File

@@ -1,9 +1,8 @@
import amplitude from 'amplitude-js';
import logger from '../logger';
import AbstractHandler from './AbstractHandler';
import { fixDeviceID } from './amplitude';
import { fixDeviceID } from './amplitude/fixDeviceID';
import amplitude from './amplitude/lib';
/**
* Analytics handler for Amplitude.
@@ -19,41 +18,39 @@ export default class AmplitudeHandler extends AbstractHandler {
constructor(options) {
super(options);
const { amplitudeAPPKey, host, user } = options;
const { amplitudeAPPKey, user } = options;
this._enabled = true;
this._host = host; // Only used on React Native.
const onError = e => {
logger.error('Error initializing Amplitude', e);
this._enabled = false;
};
const amplitudeOptions = {
domain: navigator.product === 'ReactNative' ? host : undefined,
includeReferrer: true,
onError
};
if (navigator.product === 'ReactNative') {
amplitude.getInstance().init(amplitudeAPPKey);
fixDeviceID(amplitude.getInstance()).then(() => {
amplitude.getInstance().getDeviceId()
.then(deviceId => {
this._deviceId = deviceId;
});
});
} else {
const amplitudeOptions = {
includeReferrer: true,
onError
};
this._getInstance().init(amplitudeAPPKey, undefined, amplitudeOptions);
fixDeviceID(this._getInstance());
amplitude.getInstance().init(amplitudeAPPKey, undefined, amplitudeOptions);
fixDeviceID(amplitude.getInstance());
}
if (user) {
this._getInstance().setUserId(user);
this._userId = user;
amplitude.getInstance().setUserId(user);
}
}
/**
* Returns the AmplitudeClient instance.
*
* @returns {AmplitudeClient}
*/
_getInstance() {
const name = navigator.product === 'ReactNative' ? this._host : undefined;
return amplitude.getInstance(name);
}
/**
* Sets the Amplitude user properties.
*
@@ -62,7 +59,7 @@ export default class AmplitudeHandler extends AbstractHandler {
*/
setUserProperties(userProps) {
if (this._enabled) {
this._getInstance().setUserProperties(userProps);
amplitude.getInstance().setUserProperties(userProps);
}
}
@@ -79,7 +76,7 @@ export default class AmplitudeHandler extends AbstractHandler {
return;
}
this._getInstance().logEvent(this._extractName(event), event);
amplitude.getInstance().logEvent(this._extractName(event), event);
}
/**
@@ -88,10 +85,17 @@ export default class AmplitudeHandler extends AbstractHandler {
* @returns {Object}
*/
getIdentityProps() {
if (navigator.product === 'ReactNative') {
return {
deviceId: this._deviceId,
userId: this._userId
};
}
return {
sessionId: this._getInstance().getSessionId(),
deviceId: this._getInstance().options.deviceId,
userId: this._getInstance().options.userId
sessionId: amplitude.getInstance().getSessionId(),
deviceId: amplitude.getInstance().options.deviceId,
userId: amplitude.getInstance().options.userId
};
}
}

View File

@@ -1 +0,0 @@
export * from './fixDeviceID';

View File

@@ -0,0 +1,3 @@
import { Amplitude } from '@amplitude/react-native';
export default Amplitude;

View File

@@ -0,0 +1,3 @@
import amplitude from 'amplitude-js';
export default amplitude;

View File

@@ -43,9 +43,11 @@ export class App extends AbstractApp {
*/
_renderDialogContainer() {
return (
<AtlasKitThemeProvider mode = 'dark'>
<DialogContainer />
</AtlasKitThemeProvider>
<JitsiThemeProvider>
<AtlasKitThemeProvider mode = 'dark'>
<DialogContainer />
</AtlasKitThemeProvider>
</JitsiThemeProvider>
);
}
}

View File

@@ -124,15 +124,8 @@ class LoginDialog extends Component<Props, State> {
*/
_onCancelLogin() {
const { dispatch } = this.props;
const cancelButton = document.getElementById('modal-dialog-cancel-button');
if (cancelButton) {
cancelButton.onclick = () => {
dispatch(cancelLogin());
};
}
return false;
dispatch(cancelLogin());
}
_onLogin: () => void;
@@ -252,6 +245,7 @@ class LoginDialog extends Component<Props, State> {
return (
<Dialog
disableBlanketClickDismiss = { true }
hideCloseIconButton = { true }
okDisabled = {
connecting

View File

@@ -64,15 +64,8 @@ class WaitForOwnerDialog extends PureComponent<Props> {
*/
_onCancelWaitForOwner() {
const { dispatch } = this.props;
const cancelButton = document.getElementById('modal-dialog-cancel-button');
if (cancelButton) {
cancelButton.onclick = () => {
dispatch(cancelWaitForOwner());
};
}
return false;
dispatch(cancelWaitForOwner());
}
_onIAmHost: () => void;
@@ -102,6 +95,7 @@ class WaitForOwnerDialog extends PureComponent<Props> {
return (
<Dialog
disableBlanketClickDismiss = { true }
hideCloseIconButton = { true }
okKey = { t('dialog.IamHost') }
onCancel = { this._onCancelWaitForOwner }

View File

@@ -5,8 +5,11 @@ import { getConferenceState } from '../base/conference';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../base/media';
import {
getLocalParticipant,
getParticipantDisplayName,
getRemoteParticipants,
isLocalParticipantModerator,
isParticipantModerator,
PARTICIPANT_UPDATED,
raiseHand
} from '../base/participants';
@@ -124,19 +127,29 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
case PARTICIPANT_UPDATED: {
const state = getState();
const audioModerationEnabled = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
const participant = action.participant;
// this is handled only by moderators
if (audioModerationEnabled && isLocalParticipantModerator(state)) {
const participant = action.participant;
if (participant && audioModerationEnabled) {
if (isLocalParticipantModerator(state)) {
if (participant.raisedHand) {
// if participant raises hand show notification
!isParticipantApproved(participant.id, MEDIA_TYPE.AUDIO)(state)
// this is handled only by moderators
if (participant.raisedHand) {
// if participant raises hand show notification
!isParticipantApproved(participant.id, MEDIA_TYPE.AUDIO)(state)
&& dispatch(participantPendingAudio(participant));
} else {
// if participant lowers hand hide notification
isParticipantPending(participant, MEDIA_TYPE.AUDIO)(state)
} else {
// if participant lowers hand hide notification
isParticipantPending(participant, MEDIA_TYPE.AUDIO)(state)
&& dispatch(dismissPendingAudioParticipant(participant));
}
} else if (participant.id === getLocalParticipant(state).id
&& /* the new role */ isParticipantModerator(participant)) {
// this is the granted moderator case
getRemoteParticipants(state).forEach(p => {
p.raisedHand && !isParticipantApproved(p.id, MEDIA_TYPE.AUDIO)(state)
&& dispatch(participantPendingAudio(p));
});
}
}

View File

@@ -1,21 +1,20 @@
// @flow
import { setPrejoinPageVisibility, setSkipPrejoinOnReload } from '../../prejoin';
import { PREJOIN_SCREEN_STATES } from '../../prejoin/constants';
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
import { MiddlewareRegistry } from '../redux';
import { CONFERENCE_FAILED, CONFERENCE_JOINED } from './actionTypes';
import './middleware.any';
declare var APP: Object;
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const { enableForcedReload } = getState()['features/base/config'];
switch (action.type) {
case CONFERENCE_JOINED: {
if (enableForcedReload) {
dispatch(setPrejoinPageVisibility(false));
dispatch(setPrejoinPageVisibility(PREJOIN_SCREEN_STATES.HIDDEN));
dispatch(setSkipPrejoinOnReload(false));
}

View File

@@ -111,6 +111,7 @@ export default [
'e2eping',
'enableDisplayNameInStats',
'enableEmailInStats',
'enableEncodedTransformSupport',
'enableIceRestart',
'enableInsecureRoomNameWarning',
'enableLayerSuspension',

View File

@@ -33,7 +33,6 @@ export function createFakeConfig(baseURL: string) {
muc: `conference.${url.hostname}`
},
bosh: `${baseURL}http-bind`,
clientNode: 'https://jitsi.org/jitsi-meet',
p2p: {
enabled: true
}

View File

@@ -50,6 +50,7 @@ const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
}
};
const WARNING_DISPLAY_TIMER = 4000;
/**
* A listener for device permissions changed reported from lib-jitsi-meet.
@@ -133,7 +134,7 @@ MiddlewareRegistry.register(store => next => action => {
description: additionalCameraErrorMsg,
descriptionKey: cameraErrorMsg,
titleKey
}));
}, WARNING_DISPLAY_TIMER));
if (isPrejoinPageVisible(store.getState())) {
store.dispatch(setDeviceStatusWarning(titleKey));
@@ -162,7 +163,7 @@ MiddlewareRegistry.register(store => next => action => {
description: additionalMicErrorMsg,
descriptionKey: micErrorMsg,
titleKey
}));
}, WARNING_DISPLAY_TIMER));
if (isPrejoinPageVisible(store.getState())) {
store.dispatch(setDeviceStatusWarning(titleKey));

View File

@@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.666687 9.00002C0.666687 13.6024 4.39765 17.3334 9.00002 17.3334C13.6024 17.3334 17.3334 13.6024 17.3334 9.00002C17.3334 4.39765 13.6024 0.666687 9.00002 0.666687C4.39765 0.666687 0.666687 4.39765 0.666687 9.00002ZM13.7119 5.86983C13.3639 5.56869 12.8376 5.60672 12.5365 5.95477L7.55616 11.711L5.42261 9.57743C5.09717 9.25199 4.56954 9.25199 4.2441 9.57743C3.91866 9.90287 3.91866 10.4305 4.2441 10.7559L7.01102 13.5229C7.35319 13.865 7.91386 13.8448 8.23047 13.4789L13.7969 7.04527C14.098 6.69722 14.06 6.17096 13.7119 5.86983Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 699 B

View File

@@ -26,6 +26,7 @@ export { default as IconChat } from './chat.svg';
export { default as IconChatSend } from './send.svg';
export { default as IconChatUnread } from './chat-unread.svg';
export { default as IconCheck } from './check.svg';
export { default as IconCheckSolid } from './check-solid.svg';
export { default as IconClose } from './close.svg';
export { default as IconCloseCircle } from './close-circle.svg';
export { default as IconCloseX } from './close-x.svg';

View File

@@ -458,9 +458,12 @@ export function getSortedParticipants(stateful: Object | Function) {
const remoteParticipants = getRemoteParticipants(stateful);
const items = [];
const dominantSpeaker = getDominantSpeakerParticipant(stateful);
remoteParticipants.forEach(p => {
items.push(p);
if (p !== dominantSpeaker) {
items.push(p);
}
});
items.sort((a, b) =>
@@ -469,6 +472,10 @@ export function getSortedParticipants(stateful: Object | Function) {
items.unshift(localParticipant);
if (dominantSpeaker && dominantSpeaker !== localParticipant) {
items.unshift(dominantSpeaker);
}
return items;
}

View File

@@ -79,37 +79,35 @@ function ConnectionStatus({ connectionDetails, t, connectionType }: Props) {
return (
<div className = 'con-status'>
<div className = 'con-status-container'>
<div
aria-level = { 1 }
className = 'con-status-header'
role = 'heading'>
<div className = { `con-status-circle ${connectionClass}` }>
<Icon
size = { 16 }
src = { icon } />
</div>
<span
aria-hidden = { !showDetails }
className = 'con-status-text'
id = 'connection-status-description'>{t(connectionText)}</span>
<div
aria-level = { 1 }
className = 'con-status-header'
role = 'heading'>
<div className = { `con-status-circle ${connectionClass}` }>
<Icon
ariaDescribedBy = 'connection-status-description'
ariaPressed = { showDetails }
className = { arrowClassName }
onClick = { onToggleDetails }
onKeyPress = { onKeyPressToggleDetails }
role = 'button'
size = { 24 }
src = { IconArrowDownSmall }
tabIndex = { 0 } />
size = { 16 }
src = { icon } />
</div>
<div
aria-level = '2'
className = { `con-status-details ${detailsClassName}` }
role = 'heading'>
{detailsText}</div>
<span
aria-hidden = { !showDetails }
className = 'con-status-text'
id = 'connection-status-description'>{t(connectionText)}</span>
<Icon
ariaDescribedBy = 'connection-status-description'
ariaPressed = { showDetails }
className = { arrowClassName }
onClick = { onToggleDetails }
onKeyPress = { onKeyPressToggleDetails }
role = 'button'
size = { 24 }
src = { IconArrowDownSmall }
tabIndex = { 0 } />
</div>
<div
aria-level = '2'
className = { `con-status-details ${detailsClassName}` }
role = 'heading'>
{detailsText}</div>
</div>
);
}

View File

@@ -1,67 +0,0 @@
// @flow
import React, { Component } from 'react';
import CopyMeetingLinkSection
from '../../../../invite/components/add-people-dialog/web/CopyMeetingLinkSection';
import { getCurrentConferenceUrl } from '../../../connection';
import { translate } from '../../../i18n';
import { connect } from '../../../redux';
type Props = {
/**
* The meeting url.
*/
url: string,
/**
* Used for translation.
*/
t: Function,
/**
* Used to determine if invitation link should be automatically copied
* after creating a meeting.
*/
_enableAutomaticUrlCopy: boolean,
};
/**
* Component used to copy meeting url on prejoin page.
*/
class CopyMeetingUrl extends Component<Props> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<div className = 'copy-meeting'>
<CopyMeetingLinkSection url = { this.props.url } />
</div>
);
}
}
/**
* Maps (parts of) the redux state to the React {@code Component} props.
*
* @param {Object} state - The redux state.
* @returns {Object}
*/
function mapStateToProps(state) {
const { enableAutomaticUrlCopy } = state['features/base/config'];
const { customizationReady } = state['features/dynamic-branding'];
return {
url: customizationReady ? getCurrentConferenceUrl(state) : '',
_enableAutomaticUrlCopy: enableAutomaticUrlCopy || false
};
}
export default connect(mapStateToProps)(translate(CopyMeetingUrl));

View File

@@ -2,14 +2,10 @@
import React, { PureComponent } from 'react';
import { AudioSettingsButton, VideoSettingsButton } from '../../../../toolbox/components/web';
import { VideoBackgroundButton } from '../../../../virtual-background';
import { checkBlurSupport } from '../../../../virtual-background/functions';
import { Avatar } from '../../../avatar';
import { allowUrlSharing } from '../../functions';
import DeviceStatus from '../../../../prejoin/components/preview/DeviceStatus';
import { Toolbox } from '../../../../toolbox/components/web';
import ConnectionStatus from './ConnectionStatus';
import CopyMeetingUrl from './CopyMeetingUrl';
import Preview from './Preview';
type Props = {
@@ -17,12 +13,12 @@ type Props = {
/**
* Children component(s) to be rendered on the screen.
*/
children: React$Node,
children?: React$Node,
/**
* Footer to be rendered for the page (if any).
* Additional CSS class names to set on the icon container.
*/
footer?: React$Node,
className?: string,
/**
* The name of the participant.
@@ -35,25 +31,25 @@ type Props = {
showCopyUrlButton: boolean,
/**
* Indicates whether the avatar should be shown when video is off
* Indicates whether the device status should be shown
*/
showAvatar: boolean,
/**
* Indicates whether the label and copy url action should be shown
*/
showConferenceInfo: boolean,
/**
* Title of the screen.
*/
title: string,
showDeviceStatus: boolean,
/**
* The 'Skip prejoin' button to be rendered (if any).
*/
skipPrejoinButton?: React$Node,
/**
* Title of the screen.
*/
title?: string,
/**
* Override for default toolbar buttons
*/
toolbarButtons?: Array<string>,
/**
* True if the preview overlay should be muted, false otherwise.
*/
@@ -62,14 +58,11 @@ type Props = {
/**
* The video track to render as preview (if omitted, the default local track will be rendered).
*/
videoTrack?: Object,
/**
* Array with the buttons which this Toolbox should display.
*/
visibleButtons?: Array<string>
videoTrack?: Object
}
const buttons = [ 'microphone', 'camera', 'select-background', 'invite', 'settings' ];
/**
* Implements a pre-meeting screen that can be used at various pre-meeting phases, for example
* on the prejoin screen (pre-connection) or lobby (post-connection).
@@ -81,9 +74,8 @@ export default class PreMeetingScreen extends PureComponent<Props> {
* @static
*/
static defaultProps = {
showAvatar: true,
showCopyUrlButton: true,
showConferenceInfo: true
showToolbox: true
};
/**
@@ -93,57 +85,37 @@ export default class PreMeetingScreen extends PureComponent<Props> {
*/
render() {
const {
name,
showAvatar,
showConferenceInfo,
showCopyUrlButton,
children,
className,
showDeviceStatus,
skipPrejoinButton,
title,
toolbarButtons,
videoMuted,
videoTrack,
visibleButtons
videoTrack
} = this.props;
const showSharingButton = allowUrlSharing() && showCopyUrlButton;
const containerClassName = `premeeting-screen ${className ? className : ''}`;
return (
<div
className = 'premeeting-screen'
id = 'lobby-screen'>
<ConnectionStatus />
<div className = { containerClassName }>
<div className = 'content'>
<ConnectionStatus />
<div className = 'content-controls'>
<h1 className = 'title'>
{ title }
</h1>
{ children }
<Toolbox toolbarButtons = { toolbarButtons || buttons } />
{ skipPrejoinButton }
{ showDeviceStatus && <DeviceStatus /> }
</div>
</div>
<Preview
videoMuted = { videoMuted }
videoTrack = { videoTrack } />
<div className = 'content'>
{showAvatar && videoMuted && (
<Avatar
className = 'premeeting-screen-avatar'
displayName = { name }
dynamicColor = { false }
participantId = 'local'
size = { 80 } />
)}
{showConferenceInfo && (
<>
<h1 className = 'title'>
{ title }
</h1>
{showSharingButton ? <CopyMeetingUrl /> : null}
</>
)}
{ this.props.children }
<div className = 'media-btn-container'>
<div className = 'toolbox-content'>
<div className = 'toolbox-content-items'>
<AudioSettingsButton visible = { true } />
<VideoSettingsButton visible = { true } />
{ ((visibleButtons && visibleButtons.includes('select-background'))
|| (visibleButtons && visibleButtons.includes('videobackgroundblur')))
&& <VideoBackgroundButton visible = { checkBlurSupport() } /> }
</div>
</div>
</div>
{ this.props.skipPrejoinButton }
{ this.props.footer }
</div>
</div>
);
}

View File

@@ -2,17 +2,30 @@
import React from 'react';
import { getDisplayName } from '../../../../base/settings';
import { Avatar } from '../../../avatar';
import { Video } from '../../../media';
import { getLocalParticipant } from '../../../participants';
import { connect } from '../../../redux';
import { getLocalVideoTrack } from '../../../tracks';
export type Props = {
/**
* Local participant id
*/
_participantId: string,
/**
* Flag controlling whether the video should be flipped or not.
*/
flipVideo: boolean,
/**
* The name of the user that is about to join.
*/
name: string,
/**
* Flag signaling the visibility of camera preview.
*/
@@ -31,20 +44,27 @@ export type Props = {
* @returns {ReactElement}
*/
function Preview(props: Props) {
const { videoMuted, videoTrack, flipVideo } = props;
const { _participantId, flipVideo, name, videoMuted, videoTrack } = props;
const className = flipVideo ? 'flipVideoX' : '';
if (!videoMuted && videoTrack) {
return (
<div id = 'preview'>
<Video
className = { className }
videoTrack = {{ jitsiTrack: videoTrack }} />
</div>
);
}
return null;
return (
<div id = 'preview'>
{!videoMuted && videoTrack
? (
<Video
className = { className }
videoTrack = {{ jitsiTrack: videoTrack }} />
)
: (
<Avatar
className = 'premeeting-screen-avatar'
displayName = { name }
dynamicColor = { false }
participantId = { _participantId }
size = { 180 } />
)}
</div>
);
}
/**
@@ -55,8 +75,13 @@ function Preview(props: Props) {
* @returns {Props}
*/
function _mapStateToProps(state, ownProps) {
const name = getDisplayName(state);
const { id: _participantId } = getLocalParticipant(state);
return {
_participantId,
flipVideo: state['features/base/settings'].localFlipX,
name,
videoMuted: ownProps.videoTrack ? ownProps.videoMuted : state['features/base/media'].video.muted,
videoTrack: ownProps.videoTrack || (getLocalVideoTrack(state['features/base/tracks']) || {}).jitsiTrack
};

View File

@@ -213,14 +213,3 @@ export function getConnectionData(state: Object) {
connectionDetails: []
};
}
/**
* Returns if url sharing is enabled in interface configuration.
*
* @returns {boolean}
*/
export function allowUrlSharing() {
return typeof interfaceConfig === 'undefined'
|| typeof interfaceConfig.SHARING_FEATURES === 'undefined'
|| (interfaceConfig.SHARING_FEATURES.length && interfaceConfig.SHARING_FEATURES.indexOf('url') > -1);
}

View File

@@ -31,6 +31,7 @@ const DEFAULT_STATE = {
soundsParticipantJoined: true,
soundsParticipantLeft: true,
soundsTalkWhileMuted: true,
soundsReactions: true,
startAudioOnly: false,
startWithAudioMuted: false,
startWithVideoMuted: false,
@@ -61,7 +62,7 @@ filterSubtree.audioOutputDeviceId = false;
filterSubtree.cameraDeviceId = false;
filterSubtree.micDeviceId = false;
PersistenceRegistry.register(STORE_NAME, filterSubtree);
PersistenceRegistry.register(STORE_NAME, filterSubtree, DEFAULT_STATE);
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
switch (action.type) {

View File

@@ -22,6 +22,8 @@ import {
import { AddPeopleDialog, CalleeInfoContainer } from '../../../invite';
import { LargeVideo } from '../../../large-video';
import { KnockingParticipantList } from '../../../lobby';
import { LobbyScreen } from '../../../lobby/components/native';
import { getIsLobbyVisible } from '../../../lobby/functions';
import { BackButtonRegistry } from '../../../mobile/back-button';
import { ParticipantsPane } from '../../../participants-pane/components/native';
import { Captions } from '../../../subtitles';
@@ -98,6 +100,11 @@ type Props = AbstractProps & {
*/
_toolboxVisible: boolean,
/**
* Indicates whether the lobby screen should be visible.
*/
_showLobby: boolean,
/**
* The redux {@code dispatch} function.
*/
@@ -154,7 +161,11 @@ class Conference extends AbstractConference<Props, *> {
* @returns {ReactElement}
*/
render() {
const { _fullscreenEnabled } = this.props;
const { _fullscreenEnabled, _showLobby } = this.props;
if (_showLobby) {
return <LobbyScreen />;
}
return (
<Container style = { styles.conference }>
@@ -427,6 +438,7 @@ function _mapStateToProps(state) {
_largeVideoParticipantId: state['features/large-video'].participantId,
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
_reducedUI: reducedUI,
_showLobby: getIsLobbyVisible(state),
_toolboxVisible: isToolboxVisible(state)
};
}

View File

@@ -15,9 +15,10 @@ import { Filmstrip } from '../../../filmstrip';
import { CalleeInfoContainer } from '../../../invite';
import { LargeVideo } from '../../../large-video';
import { KnockingParticipantList, LobbyScreen } from '../../../lobby';
import { getIsLobbyVisible } from '../../../lobby/functions';
import { ParticipantsPane } from '../../../participants-pane/components/web';
import { getParticipantsPaneOpen } from '../../../participants-pane/functions';
import { Prejoin, isPrejoinPageVisible } from '../../../prejoin';
import { Prejoin, isPrejoinPageVisible, isPrejoinPageLoading } from '../../../prejoin';
import { fullScreenChanged, showToolbox } from '../../../toolbox/actions.web';
import { Toolbox } from '../../../toolbox/components/web';
import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
@@ -70,11 +71,6 @@ type Props = AbstractProps & {
*/
_backgroundAlpha: number,
/**
* Returns true if the 'lobby screen' is visible.
*/
_isLobbyScreenVisible: boolean,
/**
* If participants pane is visible or not.
*/
@@ -96,6 +92,11 @@ type Props = AbstractProps & {
*/
_roomName: string,
/**
* If lobby page is visible or not.
*/
_showLobby: boolean,
/**
* If prejoin page is visible or not.
*/
@@ -207,9 +208,9 @@ class Conference extends AbstractConference<Props, *> {
*/
render() {
const {
_isLobbyScreenVisible,
_isParticipantsPaneVisible,
_layoutClassName,
_showLobby,
_showPrejoin
} = this.props;
@@ -237,7 +238,7 @@ class Conference extends AbstractConference<Props, *> {
<Filmstrip />
</div>
{ _showPrejoin || _isLobbyScreenVisible || <Toolbox /> }
{ _showPrejoin || _showLobby || <Toolbox /> }
<Chat />
{ this.renderNotificationsContainer() }
@@ -245,7 +246,7 @@ class Conference extends AbstractConference<Props, *> {
<CalleeInfoContainer />
{ _showPrejoin && <Prejoin />}
{ _showLobby && <LobbyScreen />}
</div>
<ParticipantsPane />
</div>
@@ -373,12 +374,12 @@ function _mapStateToProps(state) {
return {
...abstractMapStateToProps(state),
_backgroundAlpha: backgroundAlpha,
_isLobbyScreenVisible: state['features/base/dialog']?.component === LobbyScreen,
_isParticipantsPaneVisible: getParticipantsPaneOpen(state),
_layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)],
_mouseMoveCallbackInterval: mouseMoveCallbackInterval,
_roomName: getConferenceNameForTitle(state),
_showPrejoin: isPrejoinPageVisible(state)
_showLobby: getIsLobbyVisible(state),
_showPrejoin: isPrejoinPageVisible(state) || isPrejoinPageLoading(state)
};
}

View File

@@ -8,7 +8,7 @@ import { translate } from '../../base/i18n';
import { Switch } from '../../base/react';
import { connect } from '../../base/redux';
import { toggleE2EE } from '../actions';
import { doesEveryoneSupportE2EE } from '../functions';
type Props = {
@@ -38,12 +38,7 @@ type State = {
/**
* True if the switch is toggled on.
*/
enabled: boolean,
/**
* True if the section description should be expanded, false otherwise.
*/
expand: boolean
enabled: boolean
};
/**
@@ -78,13 +73,10 @@ class E2EESection extends Component<Props, State> {
super(props);
this.state = {
enabled: false,
expand: false
enabled: false
};
// Bind event handlers so they are only bound once for every instance.
this._onExpand = this._onExpand.bind(this);
this._onExpandKeyPress = this._onExpandKeyPress.bind(this);
this._onToggle = this._onToggle.bind(this);
}
@@ -96,7 +88,7 @@ class E2EESection extends Component<Props, State> {
*/
render() {
const { _everyoneSupportE2EE, t } = this.props;
const { enabled, expand } = this.state;
const { enabled } = this.state;
const description = t('dialog.e2eeDescription');
return (
@@ -105,25 +97,10 @@ class E2EESection extends Component<Props, State> {
aria-live = 'polite'
className = 'description'
id = 'e2ee-section-description'>
{ expand && description }
{ !expand && description.substring(0, 100) }
{ !expand && <span
aria-controls = 'e2ee-section-description'
aria-expanded = { expand }
className = 'read-more'
onClick = { this._onExpand }
onKeyPress = { this._onExpandKeyPress }
role = 'button'
tabIndex = { 0 }>
... { t('dialog.readMore') }
</span> }
{ description }
{ !_everyoneSupportE2EE && <br /> }
{ !_everyoneSupportE2EE && t('dialog.e2eeWarning') }
</p>
{
!_everyoneSupportE2EE
&& <span className = 'warning'>
{ t('dialog.e2eeWarning') }
</span>
}
<div className = 'control-row'>
<label htmlFor = 'e2ee-section-switch'>
{ t('dialog.e2eeLabel') }
@@ -137,35 +114,6 @@ class E2EESection extends Component<Props, State> {
);
}
_onExpand: () => void;
/**
* Callback to be invoked when the description is expanded.
*
* @returns {void}
*/
_onExpand() {
this.setState({
expand: true
});
}
_onExpandKeyPress: (Object) => void;
/**
* KeyPress handler for accessibility.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onExpandKeyPress(e) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this._onExpand();
}
}
_onToggle: () => void;
/**
@@ -194,11 +142,11 @@ class E2EESection extends Component<Props, State> {
* @returns {Props}
*/
function mapStateToProps(state) {
const { enabled, everyoneSupportE2EE } = state['features/e2ee'];
const { enabled } = state['features/e2ee'];
return {
_enabled: enabled,
_everyoneSupportE2EE: everyoneSupportE2EE
_everyoneSupportE2EE: doesEveryoneSupportE2EE(state)
};
}

View File

@@ -0,0 +1,29 @@
import { getParticipantCount } from '../base/participants/functions';
import { toState } from '../base/redux';
/**
* Gets the value of a specific React {@code Component} prop of the currently
* mounted {@link App}.
*
* @param {Function|Object} stateful - The redux store or {@code getState}
* function.
* @param {string} propName - The name of the React {@code Component} prop of
* the currently mounted {@code App} to get.
* @returns {*} The value of the specified React {@code Component} prop of the
* currently mounted {@code App}.
*/
export function doesEveryoneSupportE2EE(stateful) {
const state = toState(stateful);
const { everyoneSupportE2EE } = state['features/e2ee'];
const { e2eeSupported } = state['features/base/conference'];
const participantCount = getParticipantCount(state);
if (typeof everyoneSupportE2EE === 'undefined' && participantCount === 1) {
// This will happen if we are alone.
return e2eeSupported;
}
return everyoneSupportE2EE;
}

View File

@@ -0,0 +1,52 @@
// @flow
import { SET_FILMSTRIP_ENABLED, SET_FILMSTRIP_VISIBLE, SET_REMOTE_PARTICIPANTS } from './actionTypes';
/**
* Sets whether the filmstrip is enabled.
*
* @param {boolean} enabled - Whether the filmstrip is enabled.
* @returns {{
* type: SET_FILMSTRIP_ENABLED,
* enabled: boolean
* }}
*/
export function setFilmstripEnabled(enabled: boolean) {
return {
type: SET_FILMSTRIP_ENABLED,
enabled
};
}
/**
* Sets whether the filmstrip is visible.
*
* @param {boolean} visible - Whether the filmstrip is visible.
* @returns {{
* type: SET_FILMSTRIP_VISIBLE,
* visible: boolean
* }}
*/
export function setFilmstripVisible(visible: boolean) {
return {
type: SET_FILMSTRIP_VISIBLE,
visible
};
}
/**
* Sets the list of the reordered remote participants based on which the visible participants in the filmstrip will be
* determined.
*
* @param {Array<string>} participants - The list of the remote participant endpoint IDs.
* @returns {{
type: SET_REMOTE_PARTICIPANTS,
participants: Array<string>
}}
*/
export function setRemoteParticipants(participants: Array<string>) {
return {
type: SET_REMOTE_PARTICIPANTS,
participants
};
}

View File

@@ -1,42 +1,8 @@
// @flow
import {
SET_FILMSTRIP_ENABLED,
SET_FILMSTRIP_VISIBLE,
SET_TILE_VIEW_DIMENSIONS
} from './actionTypes';
import { SET_TILE_VIEW_DIMENSIONS } from './actionTypes';
/**
* Sets whether the filmstrip is enabled.
*
* @param {boolean} enabled - Whether the filmstrip is enabled.
* @returns {{
* type: SET_FILMSTRIP_ENABLED,
* enabled: boolean
* }}
*/
export function setFilmstripEnabled(enabled: boolean) {
return {
type: SET_FILMSTRIP_ENABLED,
enabled
};
}
/**
* Sets whether the filmstrip is visible.
*
* @param {boolean} visible - Whether the filmstrip is visible.
* @returns {{
* type: SET_FILMSTRIP_VISIBLE,
* visible: boolean
* }}
*/
export function setFilmstripVisible(visible: boolean) {
return {
type: SET_FILMSTRIP_VISIBLE,
visible
};
}
export * from './actions.any';
/**
* Sets the dimensions of the tile view grid. The action is only partially implemented on native as not all

View File

@@ -5,7 +5,6 @@ import { getLocalParticipant, getRemoteParticipants, pinParticipant } from '../b
import {
SET_HORIZONTAL_VIEW_DIMENSIONS,
SET_REMOTE_PARTICIPANTS,
SET_TILE_VIEW_DIMENSIONS,
SET_VERTICAL_VIEW_DIMENSIONS,
SET_VISIBLE_REMOTE_PARTICIPANTS,
@@ -26,22 +25,7 @@ import {
calculateThumbnailSizeForVerticalView
} from './functions';
/**
* Sets the list of the reordered remote participants based on which the visible participants in the filmstrip will be
* determined.
*
* @param {Array<string>} participants - The list of the remote participant endpoint IDs.
* @returns {{
type: SET_REMOTE_PARTICIPANTS,
participants: Array<string>
}}
*/
export function setRemoteParticipants(participants: Array<string>) {
return {
type: SET_REMOTE_PARTICIPANTS,
participants
};
}
export * from './actions.any';
/**
* Sets the dimensions of the tile view grid.
@@ -192,5 +176,3 @@ export function setVisibleRemoteParticipants(startIndex: number, endIndex: numbe
endIndex
};
}
export * from './actions.native';

View File

@@ -136,6 +136,13 @@ function Thumbnail(props: Props) {
tileView
} = props;
// It seems that on leave the Thumbnail for the left participant can be re-rendered.
// This will happen when mapStateToProps is executed before the remoteParticipants list in redux is updated.
if (typeof participant === 'undefined') {
return null;
}
const participantId = participant.id;
const participantInLargeVideo
= participantId === largeVideo.participantId;

View File

@@ -0,0 +1,56 @@
// @flow
import { setRemoteParticipants } from './actions';
/**
* Computes the reorderd list of the remote participants.
*
* @param {*} store - The redux store.
* @returns {void}
* @private
*/
export function updateRemoteParticipants(store: Object) {
const state = store.getState();
const { fakeParticipants, sortedRemoteParticipants, speakersList } = state['features/base/participants'];
const { remoteScreenShares } = state['features/video-layout'];
const screenShares = (remoteScreenShares || []).slice();
let speakers = (speakersList || []).slice();
const remoteParticipants = new Map(sortedRemoteParticipants);
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
for (const screenshare of screenShares) {
remoteParticipants.delete(screenshare);
speakers = speakers.filter(speaker => speaker !== screenshare);
}
for (const sharedVideo of sharedVideos) {
remoteParticipants.delete(sharedVideo);
speakers = speakers.filter(speaker => speaker !== sharedVideo);
}
for (const speaker of speakers) {
remoteParticipants.delete(speaker);
}
const reorderedParticipants
= [ ...screenShares.reverse(), ...sharedVideos, ...speakers, ...Array.from(remoteParticipants.keys()) ];
store.dispatch(setRemoteParticipants(reorderedParticipants));
}
/**
* Private helper to calculate the reordered list of remote participants when a participant leaves.
*
* @param {*} store - The redux store.
* @param {string} participantId - The endpoint id of the participant leaving the call.
* @returns {void}
* @private
*/
export function updateRemoteParticipantsOnLeave(store: Object, participantId: ?string = null) {
if (!participantId) {
return;
}
const state = store.getState();
const { remoteParticipants } = state['features/filmstrip'];
const reorderedParticipants = new Set(remoteParticipants);
reorderedParticipants.delete(participantId)
&& store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants)));
}

View File

@@ -4,6 +4,8 @@ import { getFeatureFlag, FILMSTRIP_ENABLED } from '../base/flags';
import { getParticipantCountWithFake, getPinnedParticipant } from '../base/participants';
import { toState } from '../base/redux';
export * from './functions.any';
/**
* Returns true if the filmstrip on mobile is visible, false otherwise.
*

View File

@@ -16,7 +16,6 @@ import {
isRemoteTrackMuted
} from '../base/tracks/functions';
import { setRemoteParticipants } from './actions.web';
import {
ASPECT_RATIO_BREAKPOINT,
DISPLAY_AVATAR,
@@ -33,6 +32,8 @@ import {
VERTICAL_FILMSTRIP_MIN_HORIZONTAL_MARGIN
} from './constants';
export * from './functions.any';
declare var interfaceConfig: Object;
/**
@@ -266,36 +267,3 @@ export function computeDisplayMode(input: Object) {
// check hovering and change state to avatar with name
return isHovered ? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
}
/**
* Computes the reorderd list of the remote participants.
*
* @param {*} store - The redux store.
* @returns {void}
* @private
*/
export function updateRemoteParticipants(store: Object) {
const state = store.getState();
const { fakeParticipants, sortedRemoteParticipants, speakersList } = state['features/base/participants'];
const { remoteScreenShares } = state['features/video-layout'];
const screenShares = (remoteScreenShares || []).slice();
let speakers = (speakersList || []).slice();
const remoteParticipants = new Map(sortedRemoteParticipants);
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
for (const screenshare of screenShares) {
remoteParticipants.delete(screenshare);
speakers = speakers.filter(speaker => speaker !== screenshare);
}
for (const sharedVideo of sharedVideos) {
remoteParticipants.delete(sharedVideo);
speakers = speakers.filter(speaker => speaker !== sharedVideo);
}
for (const speaker of speakers) {
remoteParticipants.delete(speaker);
}
const reorderedParticipants
= [ ...screenShares.reverse(), ...sharedVideos, ...speakers, ...Array.from(remoteParticipants.keys()) ];
store.dispatch(setRemoteParticipants(reorderedParticipants));
}

View File

@@ -0,0 +1,28 @@
// @flow
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../base/participants';
import { MiddlewareRegistry } from '../base/redux';
import { updateRemoteParticipants, updateRemoteParticipantsOnLeave } from './functions';
import './subscriber';
/**
* The middleware of the feature Filmstrip.
*/
MiddlewareRegistry.register(store => next => action => {
const result = next(action);
switch (action.type) {
case PARTICIPANT_JOINED: {
updateRemoteParticipants(store);
break;
}
case PARTICIPANT_LEFT: {
updateRemoteParticipantsOnLeave(store, action.participant?.id);
break;
}
}
return result;
});

View File

@@ -12,12 +12,11 @@ import {
import {
setHorizontalViewDimensions,
setRemoteParticipants,
setTileViewDimensions,
setVerticalViewDimensions
} from './actions.web';
import { updateRemoteParticipants } from './functions.web';
import './subscriber.web';
} from './actions';
import { updateRemoteParticipants, updateRemoteParticipantsOnLeave } from './functions';
import './subscriber';
/**
* The middleware of the feature Filmstrip.
@@ -52,7 +51,7 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case PARTICIPANT_LEFT: {
_updateRemoteParticipantsOnLeave(store, action.participant?.id);
updateRemoteParticipantsOnLeave(store, action.participant?.id);
break;
}
case SETTINGS_UPDATED: {
@@ -66,23 +65,3 @@ MiddlewareRegistry.register(store => next => action => {
return result;
});
/**
* Private helper to calculate the reordered list of remote participants when a participant leaves.
*
* @param {*} store - The redux store.
* @param {string} participantId - The endpoint id of the participant leaving the call.
* @returns {void}
* @private
*/
function _updateRemoteParticipantsOnLeave(store, participantId = null) {
if (!participantId) {
return;
}
const state = store.getState();
const { remoteParticipants } = state['features/filmstrip'];
const reorderedParticipants = new Set(remoteParticipants);
reorderedParticipants.delete(participantId)
&& store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants)));
}

View File

@@ -117,10 +117,14 @@ ReducerRegistry.register(
horizontalViewDimensions: action.dimensions
};
case SET_REMOTE_PARTICIPANTS: {
const { visibleParticipantsStartIndex: startIndex, visibleParticipantsEndIndex: endIndex } = state;
state.remoteParticipants = action.participants;
state.visibleRemoteParticipants = new Set(state.remoteParticipants.slice(startIndex, endIndex));
// TODO: implement this on mobile.
if (navigator.product !== 'ReactNative') {
const { visibleParticipantsStartIndex: startIndex, visibleParticipantsEndIndex: endIndex } = state;
state.visibleRemoteParticipants = new Set(state.remoteParticipants.slice(startIndex, endIndex));
}
return { ...state };
}

View File

@@ -0,0 +1,38 @@
// @flow
import { StateListenerRegistry } from '../base/redux';
import { updateRemoteParticipants } from './functions';
/**
* Listens for changes to the screensharing status of the remote participants to recompute the reordered list of the
* remote endpoints.
*/
StateListenerRegistry.register(
/* selector */ state => state['features/video-layout'].remoteScreenShares,
/* listener */ (remoteScreenShares, store) => updateRemoteParticipants(store));
/**
* Listens for changes to the dominant speaker to recompute the reordered list of the remote endpoints.
*/
StateListenerRegistry.register(
/* selector */ state => state['features/base/participants'].dominantSpeaker,
/* listener */ (dominantSpeaker, store) => _reorderDominantSpeakers(store));
/**
* Private helper function that reorders the remote participants based on dominant speaker changes.
*
* @param {*} store - The redux store.
* @returns {void}
* @private
*/
function _reorderDominantSpeakers(store) {
const state = store.getState();
const { dominantSpeaker, local } = state['features/base/participants'];
const { visibleRemoteParticipants } = state['features/filmstrip'];
// Reorder the participants if the new dominant speaker is currently not visible.
if (dominantSpeaker !== local?.id && !visibleRemoteParticipants.has(dominantSpeaker)) {
updateRemoteParticipants(store);
}
}

View File

@@ -0,0 +1,3 @@
// @flow
import './subscriber.any';

View File

@@ -12,14 +12,15 @@ import {
setHorizontalViewDimensions,
setTileViewDimensions,
setVerticalViewDimensions
} from './actions.web';
} from './actions';
import {
ASPECT_RATIO_BREAKPOINT,
DISPLAY_DRAWER_THRESHOLD,
SINGLE_COLUMN_BREAKPOINT,
TWO_COLUMN_BREAKPOINT
} from './constants';
import { updateRemoteParticipants } from './functions.web';
import './subscriber.any';
/**
* Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
@@ -158,36 +159,3 @@ StateListenerRegistry.register(
store.dispatch(setTileViewDimensions(gridDimensions));
}
});
/**
* Listens for changes to the screensharing status of the remote participants to recompute the reordered list of the
* remote endpoints.
*/
StateListenerRegistry.register(
/* selector */ state => state['features/video-layout'].remoteScreenShares,
/* listener */ (remoteScreenShares, store) => updateRemoteParticipants(store));
/**
* Listens for changes to the dominant speaker to recompute the reordered list of the remote endpoints.
*/
StateListenerRegistry.register(
/* selector */ state => state['features/base/participants'].dominantSpeaker,
/* listener */ (dominantSpeaker, store) => _reorderDominantSpeakers(store));
/**
* Private helper function that reorders the remote participants based on dominant speaker changes.
*
* @param {*} store - The redux store.
* @returns {void}
* @private
*/
function _reorderDominantSpeakers(store) {
const state = store.getState();
const { dominantSpeaker, local } = state['features/base/participants'];
const { visibleRemoteParticipants } = state['features/filmstrip'];
// Reorder the participants if the new dominant speaker is currently not visible.
if (dominantSpeaker !== local?.id && !visibleRemoteParticipants.has(dominantSpeaker)) {
updateRemoteParticipants(store);
}
}

View File

@@ -0,0 +1,44 @@
// @flow
import { createToolbarEvent, sendAnalytics } from '../../../../analytics';
import { translate } from '../../../../base/i18n';
import { IconAddPeople } from '../../../../base/icons';
import { connect } from '../../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../../base/toolbox/components';
import { beginAddPeople } from '../../../actions.any';
/**
* The type of the React {@code Component} props of {@link EmbedMeetingButton}.
*/
type Props = AbstractButtonProps & {
/**
* The redux {@code dispatch} function.
*/
dispatch: Function
};
/**
* Implementation of a button for opening invite people dialog.
*/
class InviteButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.invite';
icon = IconAddPeople;
label = 'toolbar.invite';
tooltip = 'toolbar.invite';
/**
* Handles clicking / pressing the button, and opens the appropriate dialog.
*
* @protected
* @returns {void}
*/
_handleClick() {
const { dispatch } = this.props;
sendAnalytics(createToolbarEvent('invite'));
dispatch(beginAddPeople());
}
}
export default translate(connect()(InviteButton));

View File

@@ -1,3 +1,4 @@
// @flow
export { default as AddPeopleDialog } from './AddPeopleDialog';
export { default as InviteButton } from './InviteButton';

View File

@@ -0,0 +1,46 @@
// @flow
import { SET_DETAILS } from './actionTypes';
import { getVpaasTenant, sendGetDetailsRequest } from './functions';
import logger from './logger';
/**
* Action used to set the jaas customer details in store.
*
* @param {Object} details - The customer details object.
* @returns {Object}
*/
function setCustomerDetails(details) {
return {
type: SET_DETAILS,
payload: details
};
}
/**
* Sends a request for retrieving jaas customer details.
*
* @returns {Function}
*/
export function getCustomerDetails() {
return async function(dispatch: Function, getState: Function) {
const state = getState();
const baseUrl = state['features/base/config'].jaasActuatorUrl || 'https://api-vo-pilot.jitsi.net/jaas-actuator';
const appId = getVpaasTenant(state);
const shouldSendRequest = Boolean(baseUrl && appId);
if (shouldSendRequest) {
try {
const details = await sendGetDetailsRequest({
appId,
baseUrl
});
dispatch(setCustomerDetails(details));
} catch (err) {
logger.error('Could not send request', err);
}
}
};
}

View File

@@ -2,55 +2,8 @@
import { openDialog } from '../base/dialog';
import { SET_DETAILS } from './actionTypes';
import { PremiumFeatureDialog } from './components';
import { VPAAS_TENANT_PREFIX } from './constants';
import { getVpaasTenant, isFeatureDisabled, sendGetDetailsRequest } from './functions';
import logger from './logger';
/**
* Action used to set the jaas customer details in store.
*
* @param {Object} details - The customer details object.
* @returns {Object}
*/
function setCustomerDetails(details) {
return {
type: SET_DETAILS,
payload: details
};
}
/**
* Sends a request for retrieving jaas customer details.
*
* @returns {Function}
*/
export function getCustomerDetails() {
return async function(dispatch: Function, getState: Function) {
const state = getState();
const baseUrl = state['features/base/config'].jaasActuatorUrl;
const jwt = state['features/base/jwt'].jwt;
const appId = getVpaasTenant(state).replace(VPAAS_TENANT_PREFIX, '');
const shouldSendRequest = Boolean(baseUrl && jwt && appId);
if (shouldSendRequest) {
try {
const details = await sendGetDetailsRequest({
baseUrl,
jwt,
appId
});
dispatch(setCustomerDetails(details));
} catch (err) {
logger.error('Could not send request', err);
}
}
};
}
import { isFeatureDisabled } from './functions';
/**
* Shows a dialog prompting users to upgrade, if requested feature is disabled.

View File

@@ -54,24 +54,16 @@ export function isVpaasMeeting(state: Object) {
* @param {Object} reqData - The request info.
* @param {string} reqData.appId - The client appId.
* @param {string} reqData.baseUrl - The base url for the request.
* @param {string} reqData.jwt - The JWT token.
* @returns {void}
*/
export async function sendGetDetailsRequest({ appId, baseUrl, jwt }: {
export async function sendGetDetailsRequest({ appId, baseUrl }: {
appId: string,
baseUrl: string,
jwt: string,
}) {
const fullUrl = `${baseUrl}/v1/customers/${encodeURIComponent(appId)}`;
const headers = {
'Authorization': `Bearer ${jwt}`
};
const fullUrl = `${baseUrl}/v1/public/tenants/${encodeURIComponent(appId)}`;
try {
const res = await fetch(fullUrl, {
method: 'GET',
headers
});
const res = await fetch(fullUrl);
if (res.ok) {
return res.json();

View File

@@ -1,14 +1,9 @@
import { redirectToStaticPage } from '../app/actions';
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
import { CONNECTION_FAILED } from '../base/connection';
import { JitsiConnectionErrors } from '../base/lib-jitsi-meet';
import { MiddlewareRegistry } from '../base/redux';
import { SET_DETAILS } from './actionTypes';
import { getCustomerDetails } from './actions';
import { STATUSES } from './constants';
import { isVpaasMeeting } from './functions';
/**
* The redux middleware for jaas.
@@ -18,27 +13,6 @@ import { isVpaasMeeting } from './functions';
*/
MiddlewareRegistry.register(store => next => async action => {
switch (action.type) {
case CONFERENCE_JOINED: {
store.dispatch(getCustomerDetails());
break;
}
case CONNECTION_FAILED: {
const { error } = action;
if (!isVpaasMeeting(store.getState()) || !error) {
break;
}
if (error.name === JitsiConnectionErrors.PASSWORD_REQUIRED) {
if (error.message !== 'could not obtain public key') {
break;
}
store.dispatch(redirectToStaticPage('/static/planLimit.html'));
}
break;
}
case SET_DETAILS: {
const { status } = action.payload;

View File

@@ -20,6 +20,11 @@ export const SET_LOBBY_MODE_ENABLED = 'SET_LOBBY_MODE_ENABLED';
*/
export const SET_KNOCKING_STATE = 'SET_KNOCKING_STATE';
/**
* Action type to set the lobby visibility.
*/
export const SET_LOBBY_VISIBILITY = 'TOGGLE_LOBBY_VISIBILITY';
/**
* Action type to set the password join failed status.
*/

View File

@@ -6,6 +6,8 @@ import {
getCurrentConference
} from '../base/conference';
import { SET_LOBBY_VISIBILITY } from './actionTypes';
/**
* Action to toggle lobby mode on or off.
*
@@ -23,3 +25,27 @@ export function toggleLobbyMode(enabled: boolean) {
}
};
}
/**
* Action to open the lobby screen.
*
* @returns {openDialog}
*/
export function openLobbyScreen() {
return {
type: SET_LOBBY_VISIBILITY,
visible: true
};
}
/**
* Action to hide the lobby screen.
*
* @returns {hideDialog}
*/
export function hideLobbyScreen() {
return {
type: SET_LOBBY_VISIBILITY,
visible: false
};
}

View File

@@ -9,7 +9,6 @@ import {
sendLocalParticipant,
setPassword
} from '../base/conference';
import { hideDialog, openDialog } from '../base/dialog';
import { getLocalParticipant } from '../base/participants';
export * from './actions.any';
@@ -20,7 +19,6 @@ import {
SET_LOBBY_MODE_ENABLED,
SET_PASSWORD_JOIN_FAILED
} from './actionTypes';
import { LobbyScreen } from './components';
declare var APP: Object;
@@ -44,15 +42,6 @@ export function cancelKnocking() {
};
}
/**
* Action to hide the lobby screen.
*
* @returns {hideDialog}
*/
export function hideLobbyScreen() {
return hideDialog(LobbyScreen);
}
/**
* Tries to join with a preset password.
*
@@ -83,15 +72,6 @@ export function knockingParticipantLeft(id: string) {
};
}
/**
* Action to open the lobby screen.
*
* @returns {openDialog}
*/
export function openLobbyScreen() {
return openDialog(LobbyScreen, {}, true);
}
/**
* Action to be executed when a participant starts knocking or an already knocking participant gets updated.
*

View File

@@ -7,6 +7,7 @@ import { getFeatureFlag, INVITE_ENABLED } from '../../base/flags';
import { getLocalParticipant } from '../../base/participants';
import { getFieldValue } from '../../base/react';
import { updateSettings } from '../../base/settings';
import { isDeviceStatusVisible } from '../../prejoin/functions';
import { cancelKnocking, joinWithPassword, setPasswordJoinFailed, startKnocking } from '../actions';
export const SCREEN_STATES = {
@@ -17,6 +18,11 @@ export const SCREEN_STATES = {
export type Props = {
/**
* Indicates whether the device status should be visible.
*/
_deviceStatusVisible: boolean,
/**
* True if knocking is already happening, so we're waiting for a response.
*/
@@ -380,8 +386,10 @@ export function _mapStateToProps(state: Object): $Shape<Props> {
const { knocking, passwordJoinFailed } = state['features/lobby'];
const { iAmSipGateway } = state['features/base/config'];
const showCopyUrlButton = inviteEnabledFlag || !disableInviteFunctions;
const deviceStatusVisible = isDeviceStatusVisible(state);
return {
_deviceStatusVisible: deviceStatusVisible,
_knocking: knocking,
_meetingName: getConferenceName(state),
_participantEmail: localParticipant?.email,

View File

@@ -27,15 +27,16 @@ class LobbyScreen extends AbstractLobbyScreen {
return (
<CustomDialog
onCancel = { this._onCancel }
style = { styles.contentWrapper }>
<Text style = { styles.dialogTitle }>
{ t(this._getScreenTitleKey()) }
</Text>
<Text style = { styles.secondaryText }>
{ _meetingName }
</Text>
{ this._renderContent() }
onCancel = { this._onCancel }>
<View style = { styles.contentWrapper }>
<Text style = { styles.dialogTitle }>
{ t(this._getScreenTitleKey()) }
</Text>
<Text style = { styles.secondaryText }>
{ _meetingName }
</Text>
{ this._renderContent() }
</View>
</CustomDialog>
);
}

View File

@@ -20,11 +20,13 @@ class LobbyScreen extends AbstractLobbyScreen {
* @inheritdoc
*/
render() {
const { showCopyUrlButton, t } = this.props;
const { _deviceStatusVisible, showCopyUrlButton, t } = this.props;
return (
<PreMeetingScreen
className = 'lobby-screen'
showCopyUrlButton = { showCopyUrlButton }
showDeviceStatus = { _deviceStatusVisible }
title = { t(this._getScreenTitleKey()) }>
{ this._renderContent() }
</PreMeetingScreen>
@@ -62,7 +64,7 @@ class LobbyScreen extends AbstractLobbyScreen {
*/
_renderJoining() {
return (
<div className = 'container'>
<div className = 'lobby-screen-content'>
<div className = 'spinner'>
<LoadingIndicator size = 'large' />
</div>
@@ -113,13 +115,19 @@ class LobbyScreen extends AbstractLobbyScreen {
const { _passwordJoinFailed, t } = this.props;
return (
<InputField
className = { _passwordJoinFailed ? 'error' : '' }
onChange = { this._onChangePassword }
placeHolder = { _passwordJoinFailed ? t('lobby.invalidPassword') : t('lobby.passwordField') }
testId = 'lobby.password'
type = 'password'
value = { this.state.password } />
<>
<InputField
className = { _passwordJoinFailed ? 'error' : '' }
onChange = { this._onChangePassword }
placeHolder = { t('lobby.passwordField') }
testId = 'lobby.password'
type = 'password'
value = { this.state.password } />
{_passwordJoinFailed && <div
className = 'prejoin-error'
data-testid = 'lobby.errorMessage'>{t('lobby.invalidPassword')}</div>}
</>
);
}
@@ -134,11 +142,10 @@ class LobbyScreen extends AbstractLobbyScreen {
return (
<>
<ActionButton
disabled = { !this.state.password }
onClick = { this._onJoinWithPassword }
testId = 'lobby.passwordJoinButton'
type = 'primary'>
{ t('lobby.passwordJoinButton') }
{ t('prejoin.joinMeeting') }
</ActionButton>
<ActionButton
onClick = { this._onSwitchToKnockMode }

View File

@@ -2,3 +2,9 @@
* Hide these emails when trying to join a lobby
*/
export const HIDDEN_EMAILS = [ 'inbound-sip-jibri@jitsi.net', 'outbound-sip-jibri@jitsi.net' ];
/**
* The identifier of the sound to be played when a participant joins lobby.
* @type {string}
*/
export const KNOCKING_PARTICIPANT_SOUND_ID = 'KNOCKING_PARTICIPANT_SOUND';

View File

@@ -20,6 +20,16 @@ export function getKnockingParticipants(state: any) {
return state['features/lobby'].knockingParticipants;
}
/**
* Selector to return lobby visibility.
*
* @param {any} state - State object.
* @returns {any}
*/
export function getIsLobbyVisible(state: any) {
return state['features/lobby'].lobbyVisible;
}
/**
* Selector to return array with knocking participant ids.
*

Some files were not shown because too many files have changed in this diff Show More