mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-24 02:47:50 +00:00
Compare commits
58 Commits
6070
...
saghul-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe9fd1c0bc | ||
|
|
e4704ae032 | ||
|
|
132b44a8b6 | ||
|
|
72111114b6 | ||
|
|
550c730ed4 | ||
|
|
2ac2138982 | ||
|
|
a84d7c17fa | ||
|
|
586ad30ed4 | ||
|
|
f1c5f314e5 | ||
|
|
64d7305598 | ||
|
|
c03d86e0e3 | ||
|
|
0ae2693116 | ||
|
|
20f6ba1736 | ||
|
|
eb64ea6aba | ||
|
|
3e004811e0 | ||
|
|
037b9202a6 | ||
|
|
8b8a42e0d1 | ||
|
|
4315e19780 | ||
|
|
4d2bd932a7 | ||
|
|
6e1f56fad1 | ||
|
|
7d2f62a614 | ||
|
|
97b958e9ea | ||
|
|
b00fc92ee6 | ||
|
|
f9d1003527 | ||
|
|
0abefa87aa | ||
|
|
dde8c586da | ||
|
|
aa944e76ad | ||
|
|
4153097cc9 | ||
|
|
2a5be074d0 | ||
|
|
2e0ae75774 | ||
|
|
a8017149a0 | ||
|
|
e99fc4394d | ||
|
|
46dd88c91b | ||
|
|
dbc8f21b01 | ||
|
|
5ebe308953 | ||
|
|
7420113079 | ||
|
|
221ecac12d | ||
|
|
744607a5cc | ||
|
|
8f641b7bb1 | ||
|
|
045bd44407 | ||
|
|
13cfc3ba66 | ||
|
|
bbfe7b4f32 | ||
|
|
4a375aa2a4 | ||
|
|
a6ad592d25 | ||
|
|
00bb013373 | ||
|
|
95baf34ba6 | ||
|
|
64385d48e9 | ||
|
|
5d8c87eb76 | ||
|
|
8bf42e79a0 | ||
|
|
930852cd88 | ||
|
|
fcc8e98aad | ||
|
|
c633929c58 | ||
|
|
6085220bfc | ||
|
|
ed6759c6cf | ||
|
|
0259d1c260 | ||
|
|
537d3ae53a | ||
|
|
bf463e37ca | ||
|
|
c246174555 |
@@ -54,7 +54,11 @@ import {
|
||||
sendLocalParticipant,
|
||||
nonParticipantMessageReceived
|
||||
} from './react/features/base/conference';
|
||||
import { getReplaceParticipant, getMultipleVideoSupportFeatureFlag } from './react/features/base/config/functions';
|
||||
import {
|
||||
getReplaceParticipant,
|
||||
getMultipleVideoSupportFeatureFlag,
|
||||
getSourceNameSignalingFeatureFlag
|
||||
} from './react/features/base/config/functions';
|
||||
import {
|
||||
checkAndNotifyForNewDevice,
|
||||
getAvailableDevices,
|
||||
@@ -93,6 +97,7 @@ import {
|
||||
dominantSpeakerChanged,
|
||||
getLocalParticipant,
|
||||
getNormalizedDisplayName,
|
||||
getScreenshareParticipantByOwnerId,
|
||||
localParticipantAudioLevelChanged,
|
||||
localParticipantConnectionStatusChanged,
|
||||
localParticipantRoleChanged,
|
||||
@@ -102,6 +107,7 @@ import {
|
||||
participantPresenceChanged,
|
||||
participantRoleChanged,
|
||||
participantUpdated,
|
||||
screenshareParticipantDisplayNameChanged,
|
||||
updateRemoteParticipantFeatures
|
||||
} from './react/features/base/participants';
|
||||
import {
|
||||
@@ -2258,6 +2264,17 @@ export default {
|
||||
id,
|
||||
name: formattedDisplayName
|
||||
}));
|
||||
|
||||
if (getSourceNameSignalingFeatureFlag(state)) {
|
||||
const screenshareParticipantId = getScreenshareParticipantByOwnerId(state, id)?.id;
|
||||
|
||||
if (screenshareParticipantId) {
|
||||
APP.store.dispatch(
|
||||
screenshareParticipantDisplayNameChanged(screenshareParticipantId, formattedDisplayName)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
APP.API.notifyDisplayNameChanged(id, {
|
||||
displayName: formattedDisplayName,
|
||||
formattedDisplayName:
|
||||
|
||||
10
config.js
10
config.js
@@ -1,6 +1,11 @@
|
||||
|
||||
/* eslint-disable no-unused-vars, no-var */
|
||||
|
||||
/*
|
||||
* NOTE: If you add a new option please remember to document it here:
|
||||
* https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-configuration
|
||||
*/
|
||||
|
||||
var config = {
|
||||
// Connection
|
||||
//
|
||||
@@ -69,6 +74,11 @@ var config = {
|
||||
// or disabled for the screenshare.
|
||||
// capScreenshareBitrate: 1 // 0 to disable - deprecated.
|
||||
|
||||
// Whether to use fake constraints (height: 99999, width: 99999) when calling getDisplayMedia on
|
||||
// Chromium based browsers. This is intended as a workaround for
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1056311
|
||||
// setScreenSharingResolutionConstraints: true
|
||||
|
||||
// Enable callstats only for a percentage of users.
|
||||
// This takes a value between 0 and 100 which determines the probability for
|
||||
// the callstats to be enabled.
|
||||
|
||||
@@ -46,18 +46,12 @@
|
||||
}
|
||||
|
||||
.audio-preview > div:nth-child(2),
|
||||
.video-preview > div:nth-child(2),
|
||||
.reactions-menu-popup > div:nth-child(2) {
|
||||
.video-preview > div:nth-child(2) {
|
||||
margin-bottom: 4px;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.reactions-menu-popup > div:nth-child(2) {
|
||||
margin-bottom: 6px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following selectors keep the chat modal full-size anywhere between 100px
|
||||
* and 580px for desktop or 680px for mobile.
|
||||
|
||||
@@ -104,6 +104,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.reactions-menu-container {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.reactions-animations-container {
|
||||
position: absolute;
|
||||
width: 20%;
|
||||
@@ -112,8 +116,7 @@
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.reactions-menu-popup-container,
|
||||
.reactions-menu-popup {
|
||||
.reactions-menu-popup-container {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -60,3 +60,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-button-small-icon-container {
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: -3px;
|
||||
|
||||
& .settings-button-small-icon {
|
||||
position: relative;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,175 +1,177 @@
|
||||
.vertical-filmstrip span:not(.tile-view) .filmstrip {
|
||||
&.hide-videos {
|
||||
.remote-videos {
|
||||
& > div {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
.vertical-filmstrip, .stage-filmstrip {
|
||||
span:not(.tile-view) .filmstrip {
|
||||
&.hide-videos {
|
||||
.remote-videos {
|
||||
& > div {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Firefox sets flex items to min-height: auto and min-width: auto,
|
||||
* preventing flex children from shrinking like they do on other browsers.
|
||||
* Setting min-height and min-width 0 is a workaround for the issue so
|
||||
* Firefox behaves like other browsers.
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1043520
|
||||
*/
|
||||
@mixin minHWAutoFix() {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
/*
|
||||
* Firefox sets flex items to min-height: auto and min-width: auto,
|
||||
* preventing flex children from shrinking like they do on other browsers.
|
||||
* Setting min-height and min-width 0 is a workaround for the issue so
|
||||
* Firefox behaves like other browsers.
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1043520
|
||||
*/
|
||||
@mixin minHWAutoFix() {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@extend %align-right;
|
||||
align-items: flex-end;
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
/**
|
||||
* fixed positioning is necessary for remote menus and tooltips to pop
|
||||
* out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
|
||||
* a library called popper which will position its elements fixed if
|
||||
* any parent is also fixed.
|
||||
*/
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
&.no-vertical-padding {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide videos by making them slight to the right.
|
||||
*/
|
||||
.filmstrip__videos {
|
||||
@extend %align-right;
|
||||
align-items: flex-end;
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
position:relative;
|
||||
/**
|
||||
* fixed positioning is necessary for remote menus and tooltips to pop
|
||||
* out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
|
||||
* a library called popper which will position its elements fixed if
|
||||
* any parent is also fixed.
|
||||
*/
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: auto;
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
&.no-vertical-padding {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* An id selector is used to match id specificity with existing
|
||||
* filmstrip styles.
|
||||
* Hide videos by making them slight to the right.
|
||||
*/
|
||||
&#remoteVideos {
|
||||
border: $thumbnailsBorder solid transparent;
|
||||
padding-left: 0;
|
||||
border-left: 0;
|
||||
.filmstrip__videos {
|
||||
@extend %align-right;
|
||||
bottom: 0;
|
||||
padding: 0;
|
||||
position:relative;
|
||||
right: 0;
|
||||
width: auto;
|
||||
|
||||
/**
|
||||
* An id selector is used to match id specificity with existing
|
||||
* filmstrip styles.
|
||||
*/
|
||||
&#remoteVideos {
|
||||
border: $thumbnailsBorder solid transparent;
|
||||
padding-left: 0;
|
||||
border-left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-styles the local Video to better fit vertical filmstrip layout.
|
||||
*/
|
||||
#filmstripLocalVideo {
|
||||
align-self: initial;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: auto;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-styles the local Video to better fit vertical filmstrip layout.
|
||||
*/
|
||||
#filmstripLocalVideo {
|
||||
align-self: initial;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: auto;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
#filmstripLocalVideoThumbnail {
|
||||
width: calc(100% - 15px);
|
||||
|
||||
#filmstripLocalVideoThumbnail {
|
||||
width: calc(100% - 15px);
|
||||
|
||||
.videocontainer {
|
||||
height: 0px;
|
||||
width: 100%;
|
||||
.videocontainer {
|
||||
height: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#filmstripLocalScreenShare {
|
||||
align-self: initial;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: auto;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
#filmstripLocalScreenShare {
|
||||
align-self: initial;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: auto;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
|
||||
#filmstripLocalScreenShareThumbnail {
|
||||
width: calc(100% - 15px);
|
||||
#filmstripLocalScreenShareThumbnail {
|
||||
width: calc(100% - 15px);
|
||||
|
||||
.videocontainer {
|
||||
height: 0px;
|
||||
width: 100%;
|
||||
.videocontainer {
|
||||
height: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unnecssary padding that is normally used to prevent horizontal
|
||||
* filmstrip from overlapping the left edge of the screen.
|
||||
*/
|
||||
#filmstripLocalVideo,
|
||||
#filmstripLocalScreenShare,
|
||||
.remote-videos {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#remoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.resizable-filmstrip #remoteVideos .videocontainer {
|
||||
border-left: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.reduce-height {
|
||||
height: calc(100% - calc(#{$newToolbarSizeWithPadding} + #{$scrollHeight}));
|
||||
}
|
||||
|
||||
.filmstrip__videos.vertical-view-grid#remoteVideos {
|
||||
align-items: 'center';
|
||||
border: 0px;
|
||||
padding-right: 7px;
|
||||
|
||||
&.has-scroll {
|
||||
padding-right: 0px;
|
||||
/**
|
||||
* Remove unnecssary padding that is normally used to prevent horizontal
|
||||
* filmstrip from overlapping the left edge of the screen.
|
||||
*/
|
||||
#filmstripLocalVideo,
|
||||
#filmstripLocalScreenShare,
|
||||
.remote-videos {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.remote-videos > div {
|
||||
left: 0px; // fixes an issue on FF - the div is aligned to the right by default for some reason
|
||||
#remoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.videocontainer {
|
||||
.resizable-filmstrip #remoteVideos .videocontainer {
|
||||
border-left: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.reduce-height {
|
||||
height: calc(100% - calc(#{$newToolbarSizeWithPadding} + #{$scrollHeight}));
|
||||
}
|
||||
|
||||
.filmstrip__videos.vertical-view-grid#remoteVideos {
|
||||
align-items: 'center';
|
||||
border: 0px;
|
||||
margin: 2px;
|
||||
}
|
||||
}
|
||||
padding-right: 7px;
|
||||
|
||||
.remote-videos {
|
||||
display: flex;
|
||||
overscroll-behavior: contain;
|
||||
&.has-scroll {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
&.height-transition {
|
||||
transition: height .3s ease-in;
|
||||
.remote-videos > div {
|
||||
left: 0px; // fixes an issue on FF - the div is aligned to the right by default for some reason
|
||||
}
|
||||
|
||||
.videocontainer {
|
||||
border: 0px;
|
||||
margin: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
& > div {
|
||||
position: absolute;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
.remote-videos {
|
||||
display: flex;
|
||||
overscroll-behavior: contain;
|
||||
|
||||
&.is-not-overflowing > div {
|
||||
bottom: 0px;
|
||||
&.height-transition {
|
||||
transition: height .3s ease-in;
|
||||
}
|
||||
|
||||
& > div {
|
||||
position: absolute;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
&.is-not-overflowing > div {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
* clashing with the filmstrip.
|
||||
*/
|
||||
.vertical-filmstrip #etherpad,
|
||||
.vertical-filmstrip #sharedvideo {
|
||||
.stage-filmstrip #etherpad,
|
||||
.vertical-filmstrip #sharedvideo,
|
||||
.stage-filmstrip #sharedvideo {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides for small videos in vertical filmstrip mode.
|
||||
*/
|
||||
.vertical-filmstrip .filmstrip__videos .videocontainer {
|
||||
.vertical-filmstrip .filmstrip__videos .videocontainer,
|
||||
.stage-filmstrip .filmstrip__videos .videocontainer {
|
||||
.self-view-mobile-portrait video {
|
||||
object-fit: contain;
|
||||
}
|
||||
@@ -27,7 +30,8 @@
|
||||
* The class opening is for when the filmstrip is transitioning from hidden
|
||||
* to visible.
|
||||
*/
|
||||
.vertical-filmstrip .large-video-labels {
|
||||
.vertical-filmstrip .large-video-labels,
|
||||
.stage-filmstrip .large-video-labels {
|
||||
&.with-filmstrip {
|
||||
right: 150px;
|
||||
}
|
||||
@@ -47,6 +51,7 @@
|
||||
* Overrides for self view when in portrait mode on mobile.
|
||||
* This is done in order to keep the aspect ratio.
|
||||
*/
|
||||
.vertical-filmstrip .self-view-mobile-portrait #localVideo_container {
|
||||
.vertical-filmstrip .self-view-mobile-portrait #localVideo_container,
|
||||
.stage-filmstrip .self-view-mobile-portrait #localVideo_container {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
4
debian/control
vendored
4
debian/control
vendored
@@ -47,12 +47,12 @@ Description: Prosody configuration for Jitsi Meet
|
||||
|
||||
Package: jitsi-meet-tokens
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, prosody-trunk (>= 1nightly747) | prosody-0.11 | prosody (>= 0.11.2), libssl1.0-dev | libssl-dev, luarocks, jitsi-meet-prosody, git
|
||||
Depends: ${misc:Depends}, prosody-trunk | prosody-0.11 | prosody-0.12 | prosody (>= 0.11.2), libssl-dev, luarocks, jitsi-meet-prosody, git, lua-basexx
|
||||
Description: Prosody token authentication plugin for Jitsi Meet
|
||||
|
||||
Package: jitsi-meet-turnserver
|
||||
Architecture: all
|
||||
Breaks: apache2
|
||||
Pre-Depends: jitsi-meet-web-config
|
||||
Depends: ${misc:Depends}, nginx (>= 1.13.10) | nginx-full (>= 1.13.10) | nginx-extras (>= 1.13.10), jitsi-meet-prosody, coturn, dnsutils
|
||||
Depends: ${misc:Depends}, jitsi-meet-prosody, coturn, dnsutils
|
||||
Description: Configures coturn to be used with Jitsi Meet
|
||||
|
||||
15
debian/jitsi-meet-tokens.postinst
vendored
15
debian/jitsi-meet-tokens.postinst
vendored
@@ -48,6 +48,11 @@ case "$1" in
|
||||
db_stop
|
||||
|
||||
if [ -f "$PROSODY_HOST_CONFIG" ] ; then
|
||||
# Install luajwt (also on update, to make sure we get the latest version).
|
||||
if ! luarocks install luajwtjitsi 3.0-0; then
|
||||
echo "Failed to install luajwtjitsi - try installing it manually"
|
||||
fi
|
||||
|
||||
# search for the token auth, if this is not enabled this is the
|
||||
# first time we install tokens package and needs a config change
|
||||
if ! egrep -q '^\s*authentication\s*=\s*"token"' "$PROSODY_HOST_CONFIG"; then
|
||||
@@ -60,16 +65,6 @@ case "$1" in
|
||||
sed -i 's/ --modules_enabled = { "token_verification" }/ modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
|
||||
sed -i '/^\s*--\s*"token_verification"/ s/--\s*//' $PROSODY_HOST_CONFIG
|
||||
|
||||
# Install luajwt
|
||||
if ! luarocks install luajwtjitsi 2.0-0; then
|
||||
echo "Failed to install luajwtjitsi - try installing it manually"
|
||||
fi
|
||||
|
||||
# Install basexx
|
||||
if ! luarocks install basexx; then
|
||||
echo "Failed to install basexx - try installing it manually"
|
||||
fi
|
||||
|
||||
PR10_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'prosody-0.10' 2>/dev/null | awk '{print $3}' || true)"
|
||||
PRTRUNK_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'prosody-trunk' 2>/dev/null | awk '{print $3}' || true)"
|
||||
PR_VER_INSTALLED=$(dpkg-query -f='${Version}\n' --show prosody 2>/dev/null || true)
|
||||
|
||||
16
debian/jitsi-meet-turnserver.postinst
vendored
16
debian/jitsi-meet-turnserver.postinst
vendored
@@ -33,7 +33,6 @@ case "$1" in
|
||||
JVB_HOSTNAME=$(echo "$RET" | xargs echo -n)
|
||||
|
||||
TURN_CONFIG="/etc/turnserver.conf"
|
||||
NGINX_CONFIG="/etc/nginx/sites-available/$JVB_HOSTNAME.conf"
|
||||
JITSI_MEET_CONFIG="/etc/jitsi/meet/$JVB_HOSTNAME-config.js"
|
||||
|
||||
# if there was a turn config backup it so we can configure
|
||||
@@ -51,19 +50,6 @@ case "$1" in
|
||||
fi
|
||||
fi
|
||||
|
||||
# this detect only old installations with no nginx
|
||||
db_get jitsi-meet/jvb-serve || true
|
||||
if [ ! -f $NGINX_CONFIG -o "$RET" = "true" ] ; then
|
||||
# nothing to do
|
||||
echo "------------------------------------------------"
|
||||
echo ""
|
||||
echo "turnserver not configured"
|
||||
echo ""
|
||||
echo "------------------------------------------------"
|
||||
db_stop
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -f $TURN_CONFIG ]] ; then
|
||||
echo "------------------------------------------------"
|
||||
echo ""
|
||||
@@ -117,7 +103,7 @@ denied-peer-ip=240.0.0.0-255.255.255.255" >> $TURN_CONFIG
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" $TURN_CONFIG
|
||||
sed -i "s/__turnSecret__/$TURN_SECRET/g" $TURN_CONFIG
|
||||
|
||||
# SSL for nginx
|
||||
# SSL settings
|
||||
db_get jitsi-meet/cert-choice
|
||||
CERT_CHOICE="$RET"
|
||||
|
||||
|
||||
16
debian/jitsi-meet-turnserver.postrm
vendored
16
debian/jitsi-meet-turnserver.postrm
vendored
@@ -23,26 +23,12 @@ set -e
|
||||
|
||||
|
||||
case "$1" in
|
||||
remove)
|
||||
if [ -x "/etc/init.d/nginx" ]; then
|
||||
invoke-rc.d nginx reload || true
|
||||
fi
|
||||
if [ -x "/etc/init.d/apache2" ]; then
|
||||
invoke-rc.d apache2 reload || true
|
||||
fi
|
||||
;;
|
||||
purge)
|
||||
rm -rf /etc/turnserver.conf
|
||||
if [ -x "/etc/init.d/nginx" ]; then
|
||||
invoke-rc.d nginx reload || true
|
||||
fi
|
||||
if [ -x "/etc/init.d/apache2" ]; then
|
||||
invoke-rc.d apache2 reload || true
|
||||
fi
|
||||
# Clear the debconf variable
|
||||
db_purge
|
||||
;;
|
||||
upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
|
||||
remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
|
||||
;;
|
||||
|
||||
*)
|
||||
|
||||
@@ -421,7 +421,7 @@ PODS:
|
||||
- React-Core
|
||||
- RNCMaskedView (0.2.6):
|
||||
- React-Core
|
||||
- RNDefaultPreference (1.4.3):
|
||||
- RNDefaultPreference (1.4.4):
|
||||
- React-Core
|
||||
- RNDeviceInfo (8.4.8):
|
||||
- React-Core
|
||||
@@ -723,7 +723,7 @@ SPEC CHECKSUMS:
|
||||
RNCAsyncStorage: ea6b5c280997b2b32a587793163b1f10e580c4f7
|
||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
||||
RNCMaskedView: c298b644a10c0c142055b3ae24d83879ecb13ccd
|
||||
RNDefaultPreference: 326860d42a681bfd7338c8f6d061cf58745bd860
|
||||
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
|
||||
RNDeviceInfo: 0400a6d0c94186d1120c3cbd97b23abc022187a9
|
||||
RNGestureHandler: e5c7cab5f214503dcefd6b2b0cefb050e1f51c4a
|
||||
RNGoogleSignin: c4381751eefd73c552b923ba347a9bfc6f18771c
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "بلوتوث",
|
||||
"car": "مسجل السيارة",
|
||||
"headphones": "سماعات رأس",
|
||||
"none": "لا يوجد أي أجهزة صوت",
|
||||
"phone": "هاتف",
|
||||
@@ -919,6 +920,7 @@
|
||||
"incomingMessage": "رسالة واردة",
|
||||
"language": "اللغة",
|
||||
"loggedIn": "الدخول باسم {{name}}",
|
||||
"maxStageParticipants": "الحد الأقصى لعدد المشاركين الذين يمكن تثبيتهم في المرحلة الرئيسية",
|
||||
"microphones": "المجهار (المايكروفون)",
|
||||
"moderator": "رئيس الجلسة",
|
||||
"more": "المزيد",
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "Bluetooth",
|
||||
"car": "Auto",
|
||||
"headphones": "Kopfhörer",
|
||||
"none": "Keine Audiogeräte verfügbar",
|
||||
"phone": "Hörer",
|
||||
@@ -216,6 +217,8 @@
|
||||
"liveStreaming": "Livestream"
|
||||
},
|
||||
"add": "Hinzufügen",
|
||||
"addMeetingNote": "Notiz zu dieser Konferenz hinzufügen",
|
||||
"addOptionalNote": "Notiz hinzufügen (optional):",
|
||||
"allow": "Erlauben",
|
||||
"alreadySharedVideoMsg": "Eine andere Person gibt bereits ein Video weiter. Bei dieser Konferenz ist jeweils nur ein geteiltes Video möglich.",
|
||||
"alreadySharedVideoTitle": "Nur ein geteiltes Video gleichzeitig",
|
||||
@@ -267,6 +270,8 @@
|
||||
"kickParticipantDialog": "Wollen Sie diese Person wirklich entfernen?",
|
||||
"kickParticipantTitle": "Person entfernen?",
|
||||
"kickTitle": "Autsch! {{participantDisplayName}} hat Sie aus dem Meeting geworfen",
|
||||
"linkMeeting": "Konferenz verlinken",
|
||||
"linkMeetingTitle": "Konferenz mit Salesforce verlinken",
|
||||
"liveStreaming": "Livestreaming",
|
||||
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Während einer Aufnahme nicht möglich",
|
||||
"liveStreamingDisabledTooltip": "Starten des Livestreams deaktiviert.",
|
||||
@@ -303,11 +308,11 @@
|
||||
"muteEveryonesVideoTitle": "Die Kamera von allen anderen ausschalten?",
|
||||
"muteParticipantBody": "Sie können die Stummschaltung anderer Personen nicht aufheben, aber eine Person kann ihre eigene Stummschaltung jederzeit beenden.",
|
||||
"muteParticipantButton": "Stummschalten",
|
||||
"muteParticipantDialog": "Wollen Sie diese Person wirklich stummschalten? Sie können die Stummschaltung nicht wieder aufheben, die Person kann dies aber jederzeit selbst tun.",
|
||||
"muteParticipantTitle": "Person stummschalten?",
|
||||
"muteParticipantsVideoBody": "Sie können die Kamera nicht wieder aktivieren, die Teilnehmer können dies aber jederzeit wieder ändern.",
|
||||
"muteParticipantsVideoBodyModerationOn": "Sie können die Kamera nicht wieder aktivieren und die Person selbst auch nicht.",
|
||||
"muteParticipantsVideoButton": "Kamera ausschalten",
|
||||
"muteParticipantsVideoDialog": "Wollen Sie die Kamera dieser Person wirklich deaktivieren? Sie können die Kamera nicht wieder aktivieren, die Person kann dies aber jederzeit selbst tun.",
|
||||
"muteParticipantsVideoDialogModerationOn": "Wollen Sie die Kamera dieser Person wirklich deaktivieren? Sie können die Kamea nicht wieder aktivieren und die Person selbst auch nicht.",
|
||||
"muteParticipantsVideoTitle": "Die Kamera von dieser Person ausschalten?",
|
||||
"noDropboxToken": "Kein gültiges Dropbox-Token",
|
||||
"password": "Passwort",
|
||||
@@ -321,6 +326,7 @@
|
||||
"popupError": "Ihr Browser blockiert Pop-ups von dieser Website. Bitte aktivieren Sie Pop-ups in den Sicherheitseinstellungen des Browsers und versuchen Sie es erneut.",
|
||||
"popupErrorTitle": "Pop-up blockiert",
|
||||
"readMore": "mehr",
|
||||
"recentlyUsedObjects": "Ihre zuletzt verwendeten Objekte",
|
||||
"recording": "Aufnahme",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Während eines Livestreams nicht möglich",
|
||||
"recordingDisabledTooltip": "Start der Aufzeichnung deaktiviert.",
|
||||
@@ -343,6 +349,12 @@
|
||||
"screenSharingFailed": "Ups! Beim Teilen des Bildschirms ist etwas schiefgegangen!",
|
||||
"screenSharingFailedTitle": "Bildschirmfreigabe fehlgeschlagen!",
|
||||
"screenSharingPermissionDeniedError": "Ups! Etwas stimmt nicht mit Ihren Berechtigungen zur Bildschirmfreigabe. Bitte neu laden und erneut versuchen.",
|
||||
"searchInSalesforce": "In Salesforce suchen",
|
||||
"searchResults": "Suchergebnisse({{count}})",
|
||||
"searchResultsDetailsError": "Beim Abrufen der Daten des Besitzers ist ein Fehler aufgetreten.",
|
||||
"searchResultsError": "Beim Abrufen der Daten ist ein Fehler aufgetreten.",
|
||||
"searchResultsNotFound": "Keine Suchergebnisse.",
|
||||
"searchResultsTryAgain": "Versuchen Sie es mit anderen Stichwörtern.",
|
||||
"sendPrivateMessage": "Sie haben kürzlich eine private Nachricht erhalten. Hatten Sie die Absicht, darauf privat zu antworten, oder wollen Sie Ihre Nachricht an die Gruppe senden?",
|
||||
"sendPrivateMessageCancel": "An die Gruppe senden",
|
||||
"sendPrivateMessageOk": "Privat antworten",
|
||||
@@ -410,6 +422,10 @@
|
||||
"veryBad": "Sehr schlecht",
|
||||
"veryGood": "Sehr gut"
|
||||
},
|
||||
"giphy": {
|
||||
"noResults": "Keine Ergebnisse :(",
|
||||
"search": "GIPHY durchsuchen"
|
||||
},
|
||||
"helpView": {
|
||||
"header": "Hilfecenter"
|
||||
},
|
||||
@@ -476,6 +492,7 @@
|
||||
"focusLocal": "Lokales Video fokussieren",
|
||||
"focusRemote": "Auf das Video einer anderen Person fokussieren",
|
||||
"fullScreen": "Vollbildmodus aktivieren oder deaktivieren",
|
||||
"giphyMenu": "GIPHY ein- oder ausblenden",
|
||||
"keyboardShortcuts": "Tastenkürzel",
|
||||
"localRecording": "Lokale Aufzeichnungssteuerelemente ein- oder ausblenden",
|
||||
"mute": "Stummschaltung aktivieren oder deaktivieren",
|
||||
@@ -543,6 +560,7 @@
|
||||
"errorMissingPassword": "Bitte das Konferenzpasswort eingeben",
|
||||
"invalidPassword": "Ungültiges Passwort",
|
||||
"joinRejectedMessage": "Ihre Beitrittsanfrage wurde von der Moderation abgelehnt.",
|
||||
"joinRejectedTitle": "Beitrittsanfrage abgelehnt.",
|
||||
"joinTitle": "Konferenz beitreten",
|
||||
"joinWithPasswordMessage": "Beitrittsversuch mit Passwort, bitte warten …",
|
||||
"joiningMessage": "Sie treten der Konferenz bei, sobald jemand Ihre Anfrage annimmt.",
|
||||
@@ -615,6 +633,7 @@
|
||||
"displayNotifications": "Benachrichtigungen anzeigen für",
|
||||
"focus": "Konferenzleitung",
|
||||
"focusFail": "{{component}} ist im Moment nicht verfügbar – wiederholen in {{ms}} Sekunden",
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "Benachrichtigungen",
|
||||
"hostAskedUnmute": "Die Moderation bittet Sie, das Mikrofon zu aktivieren",
|
||||
"invitedOneMember": "{{name}} wurde eingeladen",
|
||||
@@ -624,6 +643,12 @@
|
||||
"leftOneMember": "{{name}} hat die Konferenz verlassen",
|
||||
"leftThreePlusMembers": "{{name}} und Weitere haben die Konferenz verlassen",
|
||||
"leftTwoMembers": "{{first}} und {{second}} haben die Konferenz verlassen",
|
||||
"linkToSalesforce": "Mit Salesforce verlinken",
|
||||
"linkToSalesforceDescription": "Sie können die Zusammenfassung der Konferenz mit einem Objekt bei Salesforce verlinken.",
|
||||
"linkToSalesforceError": "Konferenz konnte nicht mit Salesforce verlinkt werden",
|
||||
"linkToSalesforceKey": "Konferenz verlinken",
|
||||
"linkToSalesforceProgress": "Konferenz wird mit Salesforce verlinkt...",
|
||||
"linkToSalesforceSuccess": "Die Konferenz wurde mit Salesforce verlinkt",
|
||||
"me": "Ich",
|
||||
"moderationInEffectCSDescription": "Bitte melden um ein Video zu teilen",
|
||||
"moderationInEffectCSTitle": "Die Videofreigabe ist von der Moderation gesperrt",
|
||||
@@ -702,6 +727,7 @@
|
||||
},
|
||||
"passwordDigitsOnly": "Bis zu {{number}} Ziffern",
|
||||
"passwordSetRemotely": "von einer anderen Person gesetzt",
|
||||
"pinnedParticipant": "Die Person ist angeheftet",
|
||||
"polls": {
|
||||
"answer": {
|
||||
"skip": "Überspringen",
|
||||
@@ -817,6 +843,18 @@
|
||||
},
|
||||
"raisedHand": "Ich möchte sprechen",
|
||||
"raisedHandsLabel": "Anzahl gehobener Hände",
|
||||
"record": {
|
||||
"already": {
|
||||
"linked": "Diese Konferenz ist bereits mit einem Objekt bei Salesforce verlinkt."
|
||||
},
|
||||
"type": {
|
||||
"account": "Account",
|
||||
"contact": "Contact",
|
||||
"lead": "Lead",
|
||||
"opportunity": "Opportunity",
|
||||
"owner": "Owner"
|
||||
}
|
||||
},
|
||||
"recording": {
|
||||
"authDropboxText": "In Dropbox hochladen",
|
||||
"availableSpace": "Verfügbarer Speicherplatz: {{spaceLeft}} MB (ca. {{duration}} Minuten Aufzeichnung)",
|
||||
@@ -831,6 +869,11 @@
|
||||
"expandedPending": "Aufzeichnung wird gestartet…",
|
||||
"failedToStart": "Die Aufnahme konnte nicht gestartet werden",
|
||||
"fileSharingdescription": "Aufzeichnung mit den Personen der Konferenz teilen",
|
||||
"highlight": "Highlight",
|
||||
"highlightMoment": "Moment als Highlight festhalten",
|
||||
"highlightMomentDisabled": "Sie können Momente als Highlights festhalten, sobald die Aufnahme startet",
|
||||
"highlightMomentSuccess": "Highlight festgehalten",
|
||||
"highlightMomentSucessDescription": "Ihr festgehaltener Moment wird zur Zusammenfassung des Meeting hinzugefügt.",
|
||||
"inProgress": "Aufzeichnung gestartet",
|
||||
"limitNotificationDescriptionNative": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} Min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <3>{{app}}</3>.",
|
||||
"limitNotificationDescriptionWeb": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} Min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
@@ -845,6 +888,7 @@
|
||||
"rec": "AUFZ",
|
||||
"serviceDescription": "Ihre Aufzeichnung wird vom Aufzeichnungsdienst gespeichert",
|
||||
"serviceDescriptionCloud": "Cloud-Aufzeichnung",
|
||||
"serviceDescriptionCloudInfo": "Aufzeichnungen werden 24 Stunden nach Aufzeichnungsende automatisch gelöscht.",
|
||||
"serviceName": "Aufnahmedienst",
|
||||
"sessionAlreadyActive": "Diese Konferenz wird bereits aufgezeichnet.",
|
||||
"signIn": "Anmelden",
|
||||
@@ -879,6 +923,7 @@
|
||||
"incomingMessage": "Eingehende Nachricht",
|
||||
"language": "Sprache",
|
||||
"loggedIn": "Als {{name}} angemeldet",
|
||||
"maxStageParticipants": "Maximale Anzahl an Personen, die zur Hauptansicht angeheftet werden können",
|
||||
"microphones": "Mikrofon",
|
||||
"moderator": "Moderation",
|
||||
"more": "Mehr",
|
||||
@@ -977,6 +1022,7 @@
|
||||
"expand": "Ausklappen",
|
||||
"feedback": "Feedback hinterlassen",
|
||||
"fullScreen": "Vollbildmodus ein-/ausschalten",
|
||||
"giphy": "GIPHY ein-/ausschalten",
|
||||
"grantModerator": "Moderationsrechte vergeben",
|
||||
"hangup": "Konferenz verlassen",
|
||||
"help": "Hilfe",
|
||||
@@ -984,6 +1030,7 @@
|
||||
"kick": "Person entfernen",
|
||||
"laugh": "Lachen",
|
||||
"like": "Daumen nach oben",
|
||||
"linkToSalesforce": "Mit Salesforce verlinken",
|
||||
"lobbyButton": "Lobbymodus ein-/ausschalten",
|
||||
"localRecording": "Lokale Aufzeichnungssteuerelemente ein-/ausschalten",
|
||||
"lockRoom": "Konferenzpasswort ein-/ausschalten",
|
||||
@@ -993,8 +1040,8 @@
|
||||
"mute": "Mikrofon aktivieren / deaktivieren",
|
||||
"muteEveryone": "Alle stummschalten",
|
||||
"muteEveryoneElse": "Alle anderen stummschalten",
|
||||
"muteEveryoneElsesVideo": "Alle anderen Kameras ausschalten",
|
||||
"muteEveryonesVideo": "Alle Kameras ausschalten",
|
||||
"muteEveryoneElsesVideoStream": "Alle anderen Kameras ausschalten",
|
||||
"muteEveryonesVideoStream": "Alle Kameras ausschalten",
|
||||
"participants": "Anwesende",
|
||||
"pip": "Bild-in-Bild-Modus ein-/ausschalten",
|
||||
"privateMessage": "Private Nachricht senden",
|
||||
@@ -1045,6 +1092,7 @@
|
||||
"exitFullScreen": "Vollbildmodus verlassen",
|
||||
"exitTileView": "Kachelansicht ausschalten",
|
||||
"feedback": "Feedback hinterlassen",
|
||||
"giphy": "GIPHY ein-/ausschalten",
|
||||
"hangup": "Konferenz verlassen",
|
||||
"help": "Hilfe",
|
||||
"invite": "Personen einladen",
|
||||
@@ -1052,6 +1100,7 @@
|
||||
"laugh": "Lachen",
|
||||
"leaveBreakoutRoom": "Breakout-Raum verlassen",
|
||||
"like": "Daumen hoch",
|
||||
"linkToSalesforce": "Mit Salesforce verknüpfen",
|
||||
"lobbyButtonDisable": "Lobbymodus deaktivieren",
|
||||
"lobbyButtonEnable": "Lobbymodus aktivieren",
|
||||
"login": "Anmelden",
|
||||
@@ -1171,10 +1220,12 @@
|
||||
"moderator": "Moderation",
|
||||
"mute": "Person ist stumm geschaltet",
|
||||
"muted": "Stummgeschaltet",
|
||||
"pinToStage": "Anheften",
|
||||
"remoteControl": "Fernsteuerung",
|
||||
"screenSharing": "Person teilt den Bildschirm",
|
||||
"show": "Im Vordergrund anzeigen",
|
||||
"showSelfView": "Eigene Ansicht anzeigen",
|
||||
"unpinFromStage": "Lösen",
|
||||
"videoMuted": "Kamera ausgeschaltet",
|
||||
"videomute": "Person hat die Kamera angehalten"
|
||||
},
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "Bluetooth",
|
||||
"car": "Vivavoce Auto",
|
||||
"headphones": "Cuffie",
|
||||
"none": "Nessun dispositivo audio esistente",
|
||||
"phone": "Telefono",
|
||||
@@ -39,6 +40,25 @@
|
||||
"audioOnly": {
|
||||
"audioOnly": "Utilizzo di minore banda"
|
||||
},
|
||||
"breakoutRooms": {
|
||||
"actions": {
|
||||
"add": "Crea sottogruppo",
|
||||
"autoAssign": "Assegna automaticamente a sottogruppi",
|
||||
"close": "Chiudi",
|
||||
"join": "Entra",
|
||||
"leaveBreakoutRoom": "Esci",
|
||||
"more": "Mostra di più",
|
||||
"remove": "Elimina",
|
||||
"sendToBreakoutRoom": "Invia partecipante a:"
|
||||
},
|
||||
"defaultName": "Sottogruppo {{index}}",
|
||||
"mainRoom": "Riunione principale",
|
||||
"notifications": {
|
||||
"joined": "Entrato nel sottogruppo \"{{name}}\"",
|
||||
"joinedMainRoom": "Entrato nella riunione principale",
|
||||
"joinedTitle": "Sottogruppo"
|
||||
}
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Aggiungi un collegamento alla riunione",
|
||||
"confirmAddLink": "Vuoi aggiungere un collegamento Jitsi a questo evento?",
|
||||
@@ -61,10 +81,11 @@
|
||||
"enter": "Entra nella conversazione",
|
||||
"error": "Errore: il tuo messaggio non è stato inviato. Motivo: {{error}}",
|
||||
"fieldPlaceHolder": "Scrivi qui il tuo messaggio",
|
||||
"lobbyChatMessageTo": "Messaggio a {{recipient}} in sala d'attesa",
|
||||
"message": "Messaggio",
|
||||
"messageAccessibleTitle": "{{user}} dice:",
|
||||
"messageAccessibleTitleMe": "io dico:",
|
||||
"messageTo": "Messaggio privato per {{recipient}}",
|
||||
"messageTo": "Messaggio privato a {{recipient}}",
|
||||
"messagebox": "Digitare un messaggio",
|
||||
"nickname": {
|
||||
"popover": "Scegli un nickname",
|
||||
@@ -72,10 +93,10 @@
|
||||
"titleWithPolls": "Inserire un nickname per utilizzare la conversazione"
|
||||
},
|
||||
"noMessagesMessage": "Non ci sono ancora messaggi nella riunione. Comincia una conversazione, qui!",
|
||||
"privateNotice": "Messaggio privato per {{recipient}}",
|
||||
"privateNotice": "Messaggio privato a {{recipient}}",
|
||||
"smileysPanel": "Pannello emoji",
|
||||
"tabs": {
|
||||
"chat": "Chat",
|
||||
"chat": "Conversazione",
|
||||
"polls": "Sondaggi"
|
||||
},
|
||||
"title": "Conversazione",
|
||||
@@ -158,7 +179,8 @@
|
||||
"joinInApp": "Entra in riunione usando l'app",
|
||||
"launchWebButton": "Avvia sul web",
|
||||
"title": "Sto avviando la riunione su {{app}}...",
|
||||
"tryAgainButton": "Prova di nuovo sul desktop"
|
||||
"tryAgainButton": "Prova di nuovo sul desktop",
|
||||
"unsupportedBrowser": "Sembra tu stia usando un browser che non supportiamo."
|
||||
},
|
||||
"defaultLink": "es. {{url}}",
|
||||
"defaultNickname": "es. Anna Rossi",
|
||||
@@ -186,12 +208,14 @@
|
||||
"Share": "Condividi",
|
||||
"Submit": "Invia",
|
||||
"WaitForHostMsg": "La riunione non è ancora cominciata. Se sei l'organizzatore, per favore autenticati. Altrimenti, aspetta l'arrivo dell'organizzatore.",
|
||||
"WaitingForHost": "In attesa dell'organizzatore...",
|
||||
"WaitingForHostTitle": "In attesa dell'organizzatore...",
|
||||
"Yes": "Sì",
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "Diretta streaming"
|
||||
},
|
||||
"add": "Aggiungi",
|
||||
"addMeetingNote": "Aggiungi una a questa riunione",
|
||||
"addOptionalNote": "Aggiungi una nota (facoltativo):",
|
||||
"allow": "Consenti",
|
||||
"alreadySharedVideoMsg": "Un altro utente sta condividendo un video. Questa riunione permette di condividere un solo video alla volta.",
|
||||
"alreadySharedVideoTitle": "È permesso un solo video alla volta",
|
||||
@@ -243,6 +267,8 @@
|
||||
"kickParticipantDialog": "Sei sicuro di voler escludere questo partecipante?",
|
||||
"kickParticipantTitle": "Escludi questo partecipante?",
|
||||
"kickTitle": "Escluso dalla riunione",
|
||||
"linkMeeting": "Link meeting",
|
||||
"linkMeetingTitle": "Link meeting to Salesforce",
|
||||
"liveStreaming": "Diretta",
|
||||
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Impossibile durante la registrazione.",
|
||||
"liveStreamingDisabledTooltip": "Trasmissioni in diretta disabilitate.",
|
||||
@@ -262,9 +288,9 @@
|
||||
"micPermissionDeniedError": "Non hai concesso il permesso di usare il microfono. Puoi comunque partecipare alla riunione ma gli altri non potranno sentirti. Usa il bottone a forma di telecamera nella barra degli indirizzi per cambiare impostazioni.",
|
||||
"micTimeoutError": "Impossibile avviare la fonte audio. Tempo di attesa scaduto.",
|
||||
"micUnknownError": "Impossibile usare il microfono per un motivo sconosciuto.",
|
||||
"moderationAudioLabel": "Permetti ai partecipenti di accendere il microfono",
|
||||
"moderationVideoLabel": "Permetti ai partecipanti di arrivare la videocamera",
|
||||
"muteEveryoneDialog": "Sei sicuro di voler spegnere il microfono a tutti? Non potrai riattivarli, ma loro potranno farlo in qualsiasi momento.",
|
||||
"moderationAudioLabel": "Permetti ai partecipenti di riaccendere il microfono",
|
||||
"moderationVideoLabel": "Permetti ai partecipanti di riattivare la videocamera",
|
||||
"muteEveryoneDialog": "I partecipanti possono riaccenderli in quasiasi momento.",
|
||||
"muteEveryoneDialogModerationOn": "I partecipanti possono fare richiesta di parlare in ogni momento.",
|
||||
"muteEveryoneElseDialog": "Una volta spenti i microfoni non potrai riattivarli, ma loro potranno farlo in qualsiasi momento.",
|
||||
"muteEveryoneElseTitle": "Spengo il microfono a tutti, eccetto a {{whom}}?",
|
||||
@@ -279,11 +305,11 @@
|
||||
"muteEveryonesVideoTitle": "Vuoi spegnere le videocamere di tutti?",
|
||||
"muteParticipantBody": "Non sarai in grado di riattivare il loro microfono, ma loro potranno riattivarlo in qualsiasi momento.",
|
||||
"muteParticipantButton": "Spegni microfono",
|
||||
"muteParticipantDialog": "Sei sicuro di voler spegnere il microfono di questo partecipante? Lui potrà riattivarlo in ogni momento.",
|
||||
"muteParticipantTitle": "Spengo il microfono a questo partecipante?",
|
||||
"muteParticipantsVideoBody": "Una volta spenta la videocamera non potrai riaccenderla, ma lui potrà riattivarla in qualsiasi momento.",
|
||||
"muteParticipantsVideoBodyModerationOn": "Non potrai riaccendere le videocamere, né potranno loro.",
|
||||
"muteParticipantsVideoButton": "Spegni videocamera",
|
||||
"muteParticipantsVideoDialog": "Sei sicuro di voler spegnere la videocamera di questo partecipante? Lui potrà riattivarla in ogni momento.",
|
||||
"muteParticipantsVideoDialogModerationOn": "Are you sure you want to turn off this participant's camera? You won't be able to turn the camera back on and neither will they.",
|
||||
"muteParticipantsVideoTitle": "Vuoi spegnere la videocamera di questo partecipante?",
|
||||
"noDropboxToken": "Token Dropbox non valido",
|
||||
"password": "Password",
|
||||
@@ -297,6 +323,7 @@
|
||||
"popupError": "Il tuo browser sta bloccando i pop-up da questo sito. Per favore abilita i pop-up dalle impostazioni di sicurezza del browser e riprova.",
|
||||
"popupErrorTitle": "Pop-up bloccato",
|
||||
"readMore": "continua",
|
||||
"recentlyUsedObjects": "Gli oggetti che hai usato di recente",
|
||||
"recording": "Registrazione",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Impossibile durante una diretta.",
|
||||
"recordingDisabledTooltip": "Registrazione disabilitata.",
|
||||
@@ -319,6 +346,12 @@
|
||||
"screenSharingFailed": "Ops! Non è stato possibile avviare la condivisione dello schermo!",
|
||||
"screenSharingFailedTitle": "Condivisione dello schermo fallita!",
|
||||
"screenSharingPermissionDeniedError": "Qualcosa non funziona nei permessi di condivisione dello schermo. Ricarica e riprova.",
|
||||
"searchInSalesforce": "Cerca in Salesforce",
|
||||
"searchResults": "Risultati ricerca({{count}})",
|
||||
"searchResultsDetailsError": "Qualcosa non ha funzionato nella ricezione dei dati del proprietario.",
|
||||
"searchResultsError": "Qualcosa non ha funzionato nella ricezione dei dati.",
|
||||
"searchResultsNotFound": "Nessun risultato.",
|
||||
"searchResultsTryAgain": "Prova altre parole di ricerca.",
|
||||
"sendPrivateMessage": "Hai ricevuto un messaggio privato poco fa. Vorresti rispondergli privatamente o vuoi mandare la risposta al gruppo?",
|
||||
"sendPrivateMessageCancel": "Invia al gruppo",
|
||||
"sendPrivateMessageOk": "Invia privatamente",
|
||||
@@ -341,8 +374,10 @@
|
||||
"shareVideoTitle": "Condividi un video",
|
||||
"shareYourScreen": "Condividi schermo",
|
||||
"shareYourScreenDisabled": "Condivisione schermo disabilitata.",
|
||||
"sharedVideoDialogError": "Errore: URL non valido",
|
||||
"sharedVideoLinkPlaceholder": "Link YouTube o link video diretto",
|
||||
"startLiveStreaming": "Inizia una diretta",
|
||||
"start": "Avvia ",
|
||||
"startLiveStreaming": "Avvia diretta",
|
||||
"startRecording": "Inizia a registrare",
|
||||
"startRemoteControlErrorMessage": "Si è verificato un errore nel tentativo di avviare la sessione di controllo remoto!",
|
||||
"stopLiveStreaming": "Ferma la diretta streaming",
|
||||
@@ -384,6 +419,10 @@
|
||||
"veryBad": "Pessima",
|
||||
"veryGood": "Ottima"
|
||||
},
|
||||
"giphy": {
|
||||
"noResults": "Trovato niente :(",
|
||||
"search": "Cerca in GIPHY"
|
||||
},
|
||||
"helpView": {
|
||||
"header": "Aiuto"
|
||||
},
|
||||
@@ -450,6 +489,7 @@
|
||||
"focusLocal": "Sposta il focus sul tuo video",
|
||||
"focusRemote": "Sposta il focus sul video di un altro partecipante",
|
||||
"fullScreen": "Attiva o disattiva schermo intero",
|
||||
"giphyMenu": "Mostra menù GIPHY",
|
||||
"keyboardShortcuts": "Scorciatoie da tastiera",
|
||||
"localRecording": "Mostra o nascondi i controlli per la registrazione",
|
||||
"mute": "Attiva o disattiva il microfono",
|
||||
@@ -479,6 +519,7 @@
|
||||
"failedToStart": "Avvio trasmissione in diretta fallito",
|
||||
"getStreamKeyManually": "Non siamo stati in grado di trovare nessuna trasmissione dal vivo. Prova ad ottenere una chiave stream da Youtube",
|
||||
"googlePrivacyPolicy": "Norme sulla riservatezza di Google",
|
||||
"inProgress": "Diretta o registrazione in corso",
|
||||
"invalidStreamKey": "La chiave per le dirette potrebbe non essere corretta.",
|
||||
"limitNotificationDescriptionNative": "La tua diretta sarà limitata a {{limit}} minuti. Per dirette illimitate, prova {{app}}.",
|
||||
"limitNotificationDescriptionWeb": "Data l'alta domanda la tua diretta sarà limitata a {{limit}} minuti. Per dirette illimitate, prova <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
@@ -488,6 +529,7 @@
|
||||
"onBy": "{{name}} ha iniziato la diretta",
|
||||
"pending": "Avvio diretta...",
|
||||
"serviceName": "Servizio dirette",
|
||||
"sessionAlreadyActive": "Questa sessione è in già in fase di registrazione o trasmessione in diretta.",
|
||||
"signIn": "Collegati con Google",
|
||||
"signInCTA": "Collegati o inserisci la tua chiave YouTube per la trasmissione in diretta.",
|
||||
"signOut": "Scollegati",
|
||||
@@ -502,6 +544,7 @@
|
||||
"admitAll": "Ammetti tutti",
|
||||
"allow": "Autorizza",
|
||||
"backToKnockModeButton": "Nessuna password, richiedi l'accesso",
|
||||
"chat": "Conversazione",
|
||||
"dialogTitle": "Sala d'attesa",
|
||||
"disableDialogContent": "Sala d'attesa attiva. Questa funzione ti permette di non dare accesso alla riunione a partecipanti indesiderati. Vuoi disattivarla?",
|
||||
"disableDialogSubmit": "Disattiva",
|
||||
@@ -514,6 +557,7 @@
|
||||
"errorMissingPassword": "Per favore, mettere la password della riunione",
|
||||
"invalidPassword": "Password errata",
|
||||
"joinRejectedMessage": "La tua richiesta d'accesso è stata respinta da un moderatore.",
|
||||
"joinRejectedTitle": "Richiesta d'ingresso respinta.",
|
||||
"joinTitle": "Entra nella riunione",
|
||||
"joinWithPasswordMessage": "Ho inviato la password per entrare, attendi...",
|
||||
"joiningMessage": "Entrerai nella riunione non appena qualcuno approva la tua richiesta",
|
||||
@@ -522,6 +566,8 @@
|
||||
"knockButton": "Chiedi d'entrare",
|
||||
"knockTitle": "Qualcuno vuole entrare nella riunione",
|
||||
"knockingParticipantList": "Lista dei partecipanti in attesa",
|
||||
"lobbyChatStartedNotification": "{{moderator}} sta parlando con {{attendee}} in sala d'attesa",
|
||||
"lobbyChatStartedTitle": "{{moderator}} sta parlando con te in sala d'attesa.",
|
||||
"nameField": "Scrivi il tuo nome",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} è stato respinto da {{originParticipantName}}",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} è stato autorizzato ad entrare da {{originParticipantName}}",
|
||||
@@ -574,18 +620,32 @@
|
||||
"OldElectronAPPTitle": "Falla di sicurezza!",
|
||||
"allowAction": "Permetti",
|
||||
"allowedUnmute": "Puoi accendere il microfono, avviare la videocamera, o condividere il tuo schermo.",
|
||||
"audioUnmuteBlockedDescription": "Lo sblocco dei microfoni è stato temporaneament bloccato per limiti del sistema.",
|
||||
"audioUnmuteBlockedTitle": "Riattivazione dei microfoni bloccata!",
|
||||
"chatMessages": "Messaggi delle conversazioni",
|
||||
"connectedOneMember": "{{name}} si è connesso",
|
||||
"connectedThreePlusMembers": "{{name}} e altri {{count}} si sono connessi",
|
||||
"connectedTwoMembers": "{{first}} e {{second}} si sono connessi",
|
||||
"disconnected": "disconnesso",
|
||||
"displayNotifications": "Mostra le notifiche per",
|
||||
"focus": "Focus su riunione",
|
||||
"focusFail": "{{component}} non disponibile - riprova in {{ms}} sec",
|
||||
"grantedTo": "Permessi di moderatore accordati a {{to}}!",
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "Notifiche",
|
||||
"hostAskedUnmute": "Il moderatore vorrebbe che tu parlassi",
|
||||
"invitedOneMember": "{{displayName}} è stato invitato",
|
||||
"invitedThreePlusMembers": "Hai invitato {{name}} e altri {{count}}",
|
||||
"invitedTwoMembers": "Hai invitato {{first}} e {{second}}",
|
||||
"kickParticipant": "{{kicked}} è stato espulso da {{kicker}}",
|
||||
"leftOneMember": "{{name}} ha lasciato la riunione",
|
||||
"leftThreePlusMembers": "{{name}} e molti altri hanno lasciato la riunione",
|
||||
"leftTwoMembers": "{{first}} e {{second}} hanno lasciato la riunione",
|
||||
"linkToSalesforce": "Collega a Salesforce",
|
||||
"linkToSalesforceDescription": "Puoi collegare il sommario della riunione ad un oggetto Salesforce.",
|
||||
"linkToSalesforceError": "Failed to link meeting to Salesforce",
|
||||
"linkToSalesforceKey": "Link this meeting",
|
||||
"linkToSalesforceProgress": "Linking meeting to Salesforce...",
|
||||
"linkToSalesforceSuccess": "The meeting was linked to Salesforce",
|
||||
"me": "Io",
|
||||
"moderationInEffectCSDescription": "Alza la mano, se vuoi condividere lo schermo, per favore.",
|
||||
"moderationInEffectCSTitle": "La condivisione schermo è stata bloccata dal moderatore",
|
||||
@@ -609,13 +669,18 @@
|
||||
"oldElectronClientDescription1": "Sembri stare usando una versione obsoleta del client Jitsi Meet che ha dei problemi di sicurezza noti. Assicurati di aggiornarla presso il nostro ",
|
||||
"oldElectronClientDescription2": "ultima build",
|
||||
"oldElectronClientDescription3": " ora!",
|
||||
"participantWantsToJoin": "Vuole unirsi alla riunione",
|
||||
"participantsWantToJoin": "Vogliono unirsi alla riunione",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) è stata tolta da un altro partecipante",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) è stata messa da un altro partecipante",
|
||||
"raiseHandAction": "Alza la mano",
|
||||
"raisedHand": "{{name}} vorrebbe intervenire.",
|
||||
"raisedHand": "Vorrebbe intervenire.",
|
||||
"raisedHands": "{{participantName}} e {{raisedHands}} altre persone",
|
||||
"reactionSounds": "Disattiva suoni",
|
||||
"screenShareNoAudio": " L'opzione di condivisione audio non era selezionata nella schermata di selezione della finestra da condividere.",
|
||||
"reactionSoundsForAll": "Disattiva suoni a tutti",
|
||||
"screenShareNoAudio": "L'opzione di condivisione audio non era selezionata nella schermata di selezione della finestra da condividere.",
|
||||
"screenShareNoAudioTitle": "Condividi audio non è stato selezionato",
|
||||
"selfViewTitle": "Puoi sempre ripristinare la tua immagine nelle impostazioni",
|
||||
"somebody": "Qualcuno",
|
||||
"startSilentDescription": "Entra di nuovo nella riunione, per attivare l'audio",
|
||||
"startSilentTitle": "Sei entrato nella riunione senza aver scelto un dispositivo audio per sentire!",
|
||||
@@ -623,23 +688,30 @@
|
||||
"suboptimalExperienceTitle": "Avviso sul browser",
|
||||
"unmute": "Accendi microfono",
|
||||
"videoMutedRemotelyDescription": "Puoi riaccenderla in qualsiasi momento.",
|
||||
"videoMutedRemotelyTitle": "La videocamera ti è stata spenta da {{participantDisplayName}}!"
|
||||
"videoMutedRemotelyTitle": "La videocamera ti è stata spenta da {{participantDisplayName}}!",
|
||||
"videoUnmuteBlockedDescription": "Riattivazione video e condivisione schermo sono state momentaneamente bloccate per limiti di sistema.",
|
||||
"videoUnmuteBlockedTitle": "Riattivazione video e condivisione schermo bloccate!",
|
||||
"viewLobby": "Vedi sala d'attesa",
|
||||
"waitingParticipants": "{{waitingParticipants}} persone"
|
||||
},
|
||||
"participantsPane": {
|
||||
"actions": {
|
||||
"allow": "Permetti ai partecipanti di:",
|
||||
"allowVideo": "Autorizza video",
|
||||
"askUnmute": "Chiedi di attivare audio",
|
||||
"audioModeration": "Possono attivare audio",
|
||||
"askUnmute": "Chiedi di riattivare audio",
|
||||
"audioModeration": "Riattivare audio",
|
||||
"blockEveryoneMicCamera": "Blocca audio e video a tutti",
|
||||
"invite": "Invita persone",
|
||||
"moreModerationActions": "Altre opzioni di moderazione",
|
||||
"moreModerationControls": "Altri controlli di moderazione",
|
||||
"moreParticipantOptions": "Altre opzioni partecipanti",
|
||||
"mute": "Silenzia",
|
||||
"muteAll": "Silenzia tutti",
|
||||
"muteEveryoneElse": "Silenzia tutti gli altri",
|
||||
"stopEveryonesVideo": "Ferma il video di tutti",
|
||||
"stopVideo": "Ferma il video",
|
||||
"unblockEveryoneMicCamera": "Sblocca audio e video a tutti",
|
||||
"videoModeration": "Avvia il loro video"
|
||||
"videoModeration": "Riavviare videocamera"
|
||||
},
|
||||
"close": "Chiudi",
|
||||
"header": "Partecipanti",
|
||||
@@ -647,15 +719,18 @@
|
||||
"lobby": "Sala d'attesa ({{count}})",
|
||||
"participantsList": "Partecipanti alla riunione ({{count}})",
|
||||
"waitingLobby": "In attesa ({{count}})"
|
||||
}
|
||||
},
|
||||
"search": "Cerca partecipanti"
|
||||
},
|
||||
"passwordDigitsOnly": "Fino a {{number}} cifre",
|
||||
"passwordSetRemotely": "definita da altro utente",
|
||||
"pinnedParticipant": "Il partecipante è in evidenza",
|
||||
"polls": {
|
||||
"answer": {
|
||||
"skip": "Salta",
|
||||
"submit": "Invia"
|
||||
},
|
||||
"by": "Da {{ name }}",
|
||||
"create": {
|
||||
"addOption": "Aggiungi risposta",
|
||||
"answerPlaceholder": "Risposta {{index}}",
|
||||
@@ -731,9 +806,9 @@
|
||||
"linkCopied": "Collegamento copiato negli appunti",
|
||||
"lookGood": "Sembra che il tuo microfono funzioni correttamente",
|
||||
"or": "o",
|
||||
"premeeting": "Attesa riunione",
|
||||
"premeeting": "Pre-riunione",
|
||||
"screenSharingError": "Errore di condivisione dello schermo:",
|
||||
"showScreen": "Avvia la schermata d'attesa della riunione",
|
||||
"showScreen": "Attiva schermata pre-riunione",
|
||||
"startWithPhone": "Avvia usando il telefono, per parlare",
|
||||
"videoOnlyError": "Errore video:",
|
||||
"videoTrackError": "Impossibile creare la traccia video.",
|
||||
@@ -753,6 +828,9 @@
|
||||
"rejected": "Rifiutato",
|
||||
"ringing": "Sta suonando..."
|
||||
},
|
||||
"privacyView": {
|
||||
"header": "Privacy"
|
||||
},
|
||||
"profile": {
|
||||
"avatar": "avatar",
|
||||
"setDisplayNameLabel": "Imposta il nome da visualizzare",
|
||||
@@ -761,6 +839,19 @@
|
||||
"title": "Profilo"
|
||||
},
|
||||
"raisedHand": "Vorrebbe parlare",
|
||||
"raisedHandsLabel": "Numero di mani alzate",
|
||||
"record": {
|
||||
"already": {
|
||||
"linked": "La riunione è già collegata a questo oggetto Salesforce."
|
||||
},
|
||||
"type": {
|
||||
"account": "Account",
|
||||
"contact": "Contact",
|
||||
"lead": "Lead",
|
||||
"opportunity": "Opportunity",
|
||||
"owner": "Owner"
|
||||
}
|
||||
},
|
||||
"recording": {
|
||||
"authDropboxText": "Carica su Dropbox",
|
||||
"availableSpace": "Spazio disponibile: {{spaceLeft}} MB (rimangono approssimativamente {{duration}} minuti di registrazione)",
|
||||
@@ -775,6 +866,12 @@
|
||||
"expandedPending": "La registrazione è in fase di avvio...",
|
||||
"failedToStart": "Non è stato possibile avviare la registrazione",
|
||||
"fileSharingdescription": "Condividi la registrazione con i partecipanti alla riunione",
|
||||
"highlight": "Evidenzia",
|
||||
"highlightMoment": "Evidenzia momento",
|
||||
"highlightMomentDisabled": "Puoi evidenziare dei momenti quando parte la registrazione",
|
||||
"highlightMomentSuccess": "Momento evidenziato",
|
||||
"highlightMomentSucessDescription": "Il tuo momento evidenziato sarà aggiunto al riepilogo della riunione.",
|
||||
"inProgress": "Registrazione o diretta in corso",
|
||||
"limitNotificationDescriptionNative": "La tua registrazione sarà limitata a {{limit}} minuti. Per registrazioni illimitate, prova <3>{{app}}</3>.",
|
||||
"limitNotificationDescriptionWeb": "Data l'alta domanda la tua registrazione sarà limitata a {{limit}} minuti. Per registrazioni illimitate, prova <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"linkGenerated": "Abbiamo generato un collegamento alla tua registrazione.",
|
||||
@@ -788,7 +885,9 @@
|
||||
"rec": "REC",
|
||||
"serviceDescription": "La tua registrazione verrà salvata dal servizio di registrazione che hai scelto",
|
||||
"serviceDescriptionCloud": "Registrazione in rete",
|
||||
"serviceDescriptionCloudInfo": "Le riunioni registrate vengono automaticamente cancellate 24 ore dopo la registrazione.",
|
||||
"serviceName": "Servizio di registrazione",
|
||||
"sessionAlreadyActive": "Questa sessione è già in corso di registrazione o trasmissione in diretta.",
|
||||
"signIn": "Entra",
|
||||
"signOut": "Esci",
|
||||
"unavailable": "Ops! Il {{serviceName}} non è al momento disponibile. Stiamo lavorando per risolvere il problema. Riprova più tardi.",
|
||||
@@ -806,7 +905,7 @@
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "L’integrazione del calendario con {{appName}} è usata per accedere in sicurezza al proprio calendario per poter leggere i prossimi appuntamenti ",
|
||||
"about": "L’integrazione del calendario con {{appName}} è usata per accedere in sicurezza al proprio calendario e poter leggere i prossimi appuntamenti ",
|
||||
"disconnect": "Disconnetti",
|
||||
"microsoftSignIn": "Connettiti con un account Microsoft",
|
||||
"signedIn": "Sto accedendo agli eventi del calendario per {{email}}. Fai click su «Disconnetti» per interrompere l’accesso agli eventi del calendario.",
|
||||
@@ -821,9 +920,10 @@
|
||||
"incomingMessage": "Messaggio in arrivo",
|
||||
"language": "Lingua",
|
||||
"loggedIn": "Connesso come {{name}}",
|
||||
"maxStageParticipants": "Numero massimo di partecipanti che possono essere aggiunti come oratori",
|
||||
"microphones": "Microfoni",
|
||||
"moderator": "Moderatore",
|
||||
"more": "Altro",
|
||||
"more": "Mostra di più",
|
||||
"name": "Nome",
|
||||
"noDevice": "Nessuno",
|
||||
"participantJoined": "Partecipante Entrato",
|
||||
@@ -834,10 +934,12 @@
|
||||
"selectAudioOutput": "Uscita audio",
|
||||
"selectCamera": "Videocamera",
|
||||
"selectMic": "Microfono",
|
||||
"selfView": "Tua immagine",
|
||||
"sounds": "Suoni",
|
||||
"speakers": "Altoparlanti",
|
||||
"startAudioMuted": "Tutti cominciano a microfono spento",
|
||||
"startVideoMuted": "Tutti cominciano a video disattivato",
|
||||
"startReactionsMuted": "Spegni i suoni delle reazioni a tutti",
|
||||
"startVideoMuted": "Tutti cominciano a videocamera disattivata",
|
||||
"talkWhileMuted": "Parla senza microfono",
|
||||
"title": "Impostazioni"
|
||||
},
|
||||
@@ -869,13 +971,21 @@
|
||||
},
|
||||
"speaker": "Relatore",
|
||||
"speakerStats": {
|
||||
"angry": "Arrabbiato",
|
||||
"disgusted": "Disgustato",
|
||||
"displayEmotions": "Mostra Emozioni",
|
||||
"fearful": "Spaventato",
|
||||
"happy": "Contento",
|
||||
"hours": "{{count}}h",
|
||||
"minutes": "{{count}}m",
|
||||
"name": "Nome",
|
||||
"neutral": "Neutro",
|
||||
"sad": "Triste",
|
||||
"search": "Cerca",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "Statistiche",
|
||||
"speakerTime": "Tempo"
|
||||
"speakerTime": "Tempo",
|
||||
"surprised": "Sorpreso"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"genericTitle": "Per la riunione devono essere usati il tuo microfono e la tua videocamera.",
|
||||
@@ -887,6 +997,9 @@
|
||||
"text": "Premi il pulsante <i>Ricollegati</i> per ricollegarti.",
|
||||
"title": "La video chiamata si è interrotta perché il computer è stato sospeso."
|
||||
},
|
||||
"termsView": {
|
||||
"header": "Termini"
|
||||
},
|
||||
"toolbar": {
|
||||
"Settings": "Impostazioni",
|
||||
"accessibilityLabel": {
|
||||
@@ -894,6 +1007,7 @@
|
||||
"audioOnly": "Spegni/Accendi audio",
|
||||
"audioRoute": "Scegli l'uscita audio",
|
||||
"boo": "Boo",
|
||||
"breakoutRoom": "Entra/Lascia sottogruppo",
|
||||
"callQuality": "Imposta qualità della chiamata",
|
||||
"cc": "Avvia/Ferma sottotitoli",
|
||||
"chat": "Entra/Esci da conversazione",
|
||||
@@ -905,6 +1019,7 @@
|
||||
"expand": "Espandi",
|
||||
"feedback": "Lascia un feedback",
|
||||
"fullScreen": "Apri/Chiudi schermo intero",
|
||||
"giphy": "Menù GIPHY",
|
||||
"grantModerator": "Autorizza moderatore",
|
||||
"hangup": "Lascia la riunione",
|
||||
"help": "Aiuto",
|
||||
@@ -912,6 +1027,7 @@
|
||||
"kick": "Espelli partecipante",
|
||||
"laugh": "Ridi",
|
||||
"like": "Mi piace",
|
||||
"linkToSalesforce": "Collega a Salesforce",
|
||||
"lobbyButton": "Attiva/disattiva sala d'attesa",
|
||||
"localRecording": "Abilita/disattiva controlli di registrazione locale",
|
||||
"lockRoom": "Attiva o disattiva password",
|
||||
@@ -934,6 +1050,7 @@
|
||||
"remoteVideoMute": "Spegni videocamera del partecipante",
|
||||
"security": "Impostazioni di sicurezza",
|
||||
"selectBackground": "Scegli sfondo",
|
||||
"selfView": "Mostra tua immagine",
|
||||
"shareRoom": "Invita qualcuno",
|
||||
"shareYourScreen": "Attiva/disattiva condivisione schermo",
|
||||
"shareaudio": "Condividi audio",
|
||||
@@ -967,16 +1084,20 @@
|
||||
"download": "Scarica le nostre app",
|
||||
"e2ee": "Crittografia punto-punto",
|
||||
"embedMeeting": "Incorpora riunione altrove",
|
||||
"enterFullScreen": "Visualizza a schermo intero",
|
||||
"enterFullScreen": "Schermo intero",
|
||||
"enterTileView": "Vedi tutti i partecipanti",
|
||||
"exitFullScreen": "Esci da schermo intero",
|
||||
"exitTileView": "Vedi una persona sola",
|
||||
"feedback": "Lascia un feedback",
|
||||
"giphy": "Menù GIPHY",
|
||||
"hangup": "Butta giù",
|
||||
"help": "Aiuto",
|
||||
"invite": "Invita persone",
|
||||
"joinBreakoutRoom": "Entra in sottogruppo",
|
||||
"laugh": "Ridi",
|
||||
"leaveBreakoutRoom": "Lascia breakout room",
|
||||
"like": "Mi piace",
|
||||
"linkToSalesforce": "Collega a Salesforce",
|
||||
"lobbyButtonDisable": "Disabilita sala d'attesa",
|
||||
"lobbyButtonEnable": "Abilita sala d'attesa",
|
||||
"login": "Accedi",
|
||||
@@ -1018,6 +1139,7 @@
|
||||
"speakerStats": "Statistiche",
|
||||
"startScreenSharing": "Inizia la condivisione dello schermo",
|
||||
"startSubtitles": "Avvia sottotitoli",
|
||||
"stopAudioSharing": "Ferma condivisione audio",
|
||||
"stopScreenSharing": "Ferma la condivisione dello schermo",
|
||||
"stopSharedVideo": "Ferma video",
|
||||
"stopSubtitles": "Ferma sottotitoli",
|
||||
@@ -1063,17 +1185,21 @@
|
||||
"pending": "{{displayName}} è stato invitato"
|
||||
},
|
||||
"videoStatus": {
|
||||
"adjustFor": "Adjust for:",
|
||||
"audioOnly": "AUD",
|
||||
"audioOnlyExpanded": "Hai attivato la modalità con banda limitata. Questa modalità permette di risparmiare banda, ma non vedrai gli altri partecipanti.",
|
||||
"audioOnlyExpanded": "Hai attivato la modalità per banda limitata. Questa modalità permette di risparmiare banda, ma non vedrai gli altri partecipanti.",
|
||||
"bestPerformance": "Massime prestazioni",
|
||||
"callQuality": "Qualità video",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "Stai vedendo in alta definizione",
|
||||
"highDefinition": "Alta definizione",
|
||||
"highestQuality": "Massima definizione",
|
||||
"labelTooiltipNoVideo": "Nessun video",
|
||||
"labelTooltipAudioOnly": "Hai attivato la modalità con banda limitata",
|
||||
"labelTooltipAudioOnly": "Hai attivato la modalità per banda limitata",
|
||||
"ld": "LD",
|
||||
"ldTooltip": "Stai vedendo a bassa definizione",
|
||||
"lowDefinition": "Bassa definizione",
|
||||
"performanceSettings": "Impostazione prestazioni",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Stai vedendo a definizione standard",
|
||||
"standardDefinition": "Definizione standard"
|
||||
@@ -1086,12 +1212,17 @@
|
||||
"domuteVideoOfOthers": "Disattiva video di tutti gli altri",
|
||||
"flip": "Rifletti",
|
||||
"grantModerator": "Autorizza moderatore",
|
||||
"hideSelfView": "Nascondi tua immagine",
|
||||
"kick": "Espelli",
|
||||
"moderator": "Moderatore",
|
||||
"mute": "Il partecipante ha il microfono spento",
|
||||
"muted": "Audio disattivato",
|
||||
"pinToStage": "Aggiungi agli oratori",
|
||||
"remoteControl": "Avvia/ferma il controllo remoto",
|
||||
"show": "Mostra in primo piano",
|
||||
"screenSharing": "Il partecipante sta condividendo lo schermo",
|
||||
"show": "Mostra tra gli oratori",
|
||||
"showSelfView": "Mostra tua immagine",
|
||||
"unpinFromStage": "Togli",
|
||||
"videoMuted": "Video disattivato",
|
||||
"videomute": "Il partecipante ha la videocamera spenta"
|
||||
},
|
||||
@@ -1116,7 +1247,8 @@
|
||||
"slightBlur": "Sfuoca leggermente",
|
||||
"title": "Sfondi",
|
||||
"uploadedImage": "Carica immagine {{index}}",
|
||||
"webAssemblyWarning": "Il WebAssembly not è supportato"
|
||||
"webAssemblyWarning": "Il WebAssembly non è supportato",
|
||||
"webAssemblyWarningDescription": "Il WebAssembly è disabilitato o non è supportat da questo browser"
|
||||
},
|
||||
"volumeSlider": "Sbarra volume",
|
||||
"welcomepage": {
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Добавить ссылку конференции",
|
||||
"confirmAddLink": "Вы хотите добавить ссылку Jitsi к этому календарному событию?",
|
||||
"confirmAddLink": "Вы хотите добавить ссылку {{app}} к этому календарному событию?",
|
||||
"error": {
|
||||
"appConfiguration": "Неправильно настроена интеграция календаря.",
|
||||
"generic": "Произошла ошибка. Проверьте настройки календаря или попробуйте обновить его.",
|
||||
@@ -429,7 +429,7 @@
|
||||
"answer": "Ответ",
|
||||
"audioCallTitle": "Входящий звонок",
|
||||
"decline": "Отклонить",
|
||||
"productLabel": "из Jitsi Meet",
|
||||
"productLabel": "из {{app}}",
|
||||
"videoCallTitle": "Входящий видеозвонок"
|
||||
},
|
||||
"info": {
|
||||
@@ -665,7 +665,7 @@
|
||||
"newDeviceAction": "Использовать",
|
||||
"newDeviceAudioTitle": "Обнаружено новое аудиоустройство",
|
||||
"newDeviceCameraTitle": "Обнаружена новая камера",
|
||||
"oldElectronClientDescription1": "Похоже, вы используете старую версию клиента Jitsi Meet, которая имеет известные уязвимости в системе безопасности. Убедитесь, что вы обновили до нашей ",
|
||||
"oldElectronClientDescription1": "Похоже, вы используете старую версию клиента {{app}}, которая имеет известные уязвимости в системе безопасности. Убедитесь, что вы обновили до нашей ",
|
||||
"oldElectronClientDescription2": "последней версии",
|
||||
"oldElectronClientDescription3": " сейчас!",
|
||||
"participantWantsToJoin": "Хочет присоединиться к митингу",
|
||||
@@ -1268,9 +1268,9 @@
|
||||
"go": "ОК",
|
||||
"goSmall": "ОК",
|
||||
"headerSubtitle": "Защищенная высококачественная видеосвязь",
|
||||
"headerTitle": "Сервер видеоконференцсвязи Jitsi Meet",
|
||||
"headerTitle": "Сервер видеоконференцсвязи {{app}}",
|
||||
"info": "Инфо",
|
||||
"jitsiOnMobile": "Jitsy для мобильных устройств — загрузите наши приложения и начните встречу из любого места",
|
||||
"jitsiOnMobile": "{{app}} для мобильных устройств — загрузите наши приложения и начните встречу из любого места",
|
||||
"join": "СОЗДАТЬ / ПРИСОЕДИНИТЬСЯ",
|
||||
"logo": {
|
||||
"calendar": "Calendar логотип",
|
||||
|
||||
@@ -602,10 +602,12 @@
|
||||
"OldElectronAPPTitle": "Güvenlik açığı!",
|
||||
"allowAction": "İzin ver",
|
||||
"allowedUnmute": "Mikrofonunuzu sessizden çıkarabilir, kameranızı başlatabilir veya ekranınızı paylaşabilirsiniz.",
|
||||
"chatMessages": "Sohbet mesajları",
|
||||
"connectedOneMember": "{{name}} toplantıya katıldı",
|
||||
"connectedThreePlusMembers": "{{name}} ve {{count}} kişi daha toplantıya katıldı",
|
||||
"connectedTwoMembers": "{{first}} ve {{second}} toplantıya katıldı",
|
||||
"disconnected": "bağlantı kesildi",
|
||||
"displayNotifications": "Bildirimleri görüntüle",
|
||||
"focus": "Toplantı odağı",
|
||||
"focusFail": "{{component}} uygun değil - {{ms}} saniye içinde tekrar deneyin",
|
||||
"grantedTo": "{{to}} kişisine yönetici hakları verildi!",
|
||||
@@ -662,6 +664,9 @@
|
||||
"audioModeration": "Seslerini aç",
|
||||
"blockEveryoneMicCamera": "Herkesin mikrofonunu ve kamerasını blokla",
|
||||
"invite": "Birini davet et",
|
||||
"moreModerationActions": "Daha fazla denetleme seçeneği",
|
||||
"moreModerationControls": "Daha fazla denetleme kontrolü",
|
||||
"moreParticipantOptions": "Daha fazla katılımcı seçeneği",
|
||||
"mute": "Sustur",
|
||||
"muteAll": "Herkesi sustur",
|
||||
"muteEveryoneElse": "Diğer herkesi sessize al",
|
||||
@@ -676,7 +681,8 @@
|
||||
"lobby": "Lobi ({{count}})",
|
||||
"participantsList": "Toplantı Katılımcıları ({{count}})",
|
||||
"waitingLobby": "Lobide bekleyen ({{count}})"
|
||||
}
|
||||
},
|
||||
"search": "Katılımcıları ara"
|
||||
},
|
||||
"passwordDigitsOnly": "{{number}} rakama kadar",
|
||||
"passwordSetRemotely": "başka katılımcı tarafından ayarlandı",
|
||||
@@ -850,6 +856,7 @@
|
||||
"incomingMessage": "Gelen mesaj",
|
||||
"language": "Dil",
|
||||
"loggedIn": "{{name}} olarak giriş yapıldı",
|
||||
"maxStageParticipants": "Ana ekrana sabitlenecek maksimum katılımcı sayısı",
|
||||
"microphones": "Mikrofonlar",
|
||||
"moderator": "Yönetici",
|
||||
"more": "Daha fazla",
|
||||
@@ -1109,6 +1116,7 @@
|
||||
"lowDefinition": "Düşük çözünürlük",
|
||||
"onlyAudioAvailable": "Yalnızca ses kullanılabilir",
|
||||
"onlyAudioSupported": "Bu tarayıcıda yalnızca sesi destekliyoruz.",
|
||||
"performanceSettings": "Performans ayarları",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Standart çözünürlüklü video görüntüleme",
|
||||
"standardDefinition": "Standart çözünürlük"
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "藍牙",
|
||||
"car": "汽車音響",
|
||||
"headphones": "耳機",
|
||||
"none": "沒有可用的音效裝置",
|
||||
"phone": "電話",
|
||||
@@ -39,9 +40,6 @@
|
||||
"audioOnly": {
|
||||
"audioOnly": "低頻寬"
|
||||
},
|
||||
"blankPage": {
|
||||
"meetingEnded": "會議已結束。"
|
||||
},
|
||||
"breakoutRooms": {
|
||||
"actions": {
|
||||
"add": "新增討論室",
|
||||
@@ -83,6 +81,7 @@
|
||||
"enter": "加入聊天室",
|
||||
"error": "錯誤:您的訊息未被傳送。原因:{{error}}",
|
||||
"fieldPlaceHolder": "在此輸入您的訊息",
|
||||
"lobbyChatMessageTo": "Lobby chat message to {{recipient}}",
|
||||
"message": "訊息",
|
||||
"messageAccessibleTitle": "{{user}} 說:",
|
||||
"messageAccessibleTitleMe": "您說:",
|
||||
@@ -130,11 +129,11 @@
|
||||
},
|
||||
"connectionindicator": {
|
||||
"address": "位址:",
|
||||
"audio_ssrc": "Audio SSRC:",
|
||||
"audio_ssrc": "音訊 SSRC:",
|
||||
"bandwidth": "估計頻寬:",
|
||||
"bitrate": "位元率:",
|
||||
"bridgeCount": "伺服器數量:",
|
||||
"codecs": "Codecs (A/V):",
|
||||
"codecs": "編碼 (A/V):",
|
||||
"connectedTo": "已連接至:",
|
||||
"e2e_rtt": "E2E RTT:",
|
||||
"framerate": "影格率:",
|
||||
@@ -163,7 +162,7 @@
|
||||
"status": "連接:",
|
||||
"transport": "傳輸:",
|
||||
"transport_plural": "傳輸:",
|
||||
"video_ssrc": "Video SSRC:"
|
||||
"video_ssrc": "視訊 SSRC:"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "稍早",
|
||||
@@ -180,7 +179,8 @@
|
||||
"joinInApp": "使用 App 加入會議",
|
||||
"launchWebButton": "在瀏覽器開啟",
|
||||
"title": "正在 {{app}} 發起您的會議...",
|
||||
"tryAgainButton": "在桌面上再試一次"
|
||||
"tryAgainButton": "在桌面上再試一次",
|
||||
"unsupportedBrowser": "您似乎正在使用我們不支援的瀏覽器。"
|
||||
},
|
||||
"defaultLink": "例如 {{url}}",
|
||||
"defaultNickname": "例如 春嬌 志明",
|
||||
@@ -207,15 +207,17 @@
|
||||
"Remove": "移除",
|
||||
"Share": "分享",
|
||||
"Submit": "提交",
|
||||
"WaitForHostMsg": "此會議 尚未啟動。如果您是會議主人,請進行認證;否則,請等待會議主人到達。",
|
||||
"WaitForHostMsg": "此會議尚未開始。如果您是主辦人,請進行認證;否則,請等待主辦人到達。",
|
||||
"WaitingForHost": "等侯主辦人...",
|
||||
"Yes": "是的",
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "直播串流"
|
||||
},
|
||||
"add": "新增",
|
||||
"addMeetingNote": "新增此會議的備註",
|
||||
"addOptionalNote": "新增備註 (選填):",
|
||||
"allow": "允許",
|
||||
"alreadySharedVideoMsg": "另一位參與者已經進行分享影像了。此會議同個時間只能允許一人分享影像畫面。",
|
||||
"alreadySharedVideoMsg": "另一位參與者已經正在分享影像了。此會議同時僅允許一人分享影像畫面。",
|
||||
"alreadySharedVideoTitle": "一次只允許一位影像分享",
|
||||
"applicationWindow": "應用程式視窗",
|
||||
"authenticationRequired": "需要驗證",
|
||||
@@ -265,11 +267,13 @@
|
||||
"kickParticipantDialog": "您確定要將這位參與者踢出會議嗎?",
|
||||
"kickParticipantTitle": "踢出這位參與者?",
|
||||
"kickTitle": "噢!{{participantDisplayName}} 已將您踢出會議",
|
||||
"linkMeeting": "連結會議",
|
||||
"linkMeetingTitle": "將會議連結至 Salesforce",
|
||||
"liveStreaming": "直播串流中",
|
||||
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "正在錄影,無法使用",
|
||||
"liveStreamingDisabledTooltip": "啟動直播串流已停用。",
|
||||
"localUserControls": "本機使用者控制",
|
||||
"lockMessage": "鎖定會議失敗。",
|
||||
"lockMessage": "無法鎖定會議。",
|
||||
"lockRoom": "增加會議 $t(lockRoomPasswordUppercase)",
|
||||
"lockTitle": "鎖定失敗",
|
||||
"login": "登入",
|
||||
@@ -319,6 +323,7 @@
|
||||
"popupError": "您的瀏覽器在此網站上阻擋彈出視窗。請在瀏覽器的安全設定中啟用並再試一次。",
|
||||
"popupErrorTitle": "彈出視窗遭到阻擋",
|
||||
"readMore": "更多",
|
||||
"recentlyUsedObjects": "您近期使用過的物件",
|
||||
"recording": "錄影中",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "正在直播時無法使用",
|
||||
"recordingDisabledTooltip": "啟動錄影已停用。",
|
||||
@@ -335,19 +340,25 @@
|
||||
"removeSharedVideoMsg": "您確定要移除自己分享的影像嗎?",
|
||||
"removeSharedVideoTitle": "移除分享的影像",
|
||||
"reservationError": "預約系統錯誤",
|
||||
"reservationErrorMsg": "錯誤碼: {{code}} , 訊息: {{msg}}",
|
||||
"reservationErrorMsg": "錯誤碼:{{code}}, 訊息:{{msg}}",
|
||||
"retry": "重試",
|
||||
"screenSharingAudio": "分享音訊",
|
||||
"screenSharingFailed": "噢喔!發生錯誤,我們無法啟動螢幕分享!",
|
||||
"screenSharingFailedTitle": "螢幕分享失敗!",
|
||||
"screenSharingPermissionDeniedError": "噢喔!您的影像分享權限發生問題。請重新載入,再試一次。",
|
||||
"sendPrivateMessage": "您最近有收到私人訊息。您要進行私人回覆,或是要將自己的訊息發佈至群組?",
|
||||
"searchInSalesforce": "在 Salesforce 中搜尋",
|
||||
"searchResults": "搜尋結果 ({{count}})",
|
||||
"searchResultsDetailsError": "取得擁有者資料時發生錯誤。",
|
||||
"searchResultsError": "取得資料時發生錯誤。",
|
||||
"searchResultsNotFound": "找不到任何結果。",
|
||||
"searchResultsTryAgain": "請嘗試使用其他關鍵字。",
|
||||
"sendPrivateMessage": "您最近有收到私人訊息。您要進行私人回覆,還是要將自己的訊息發佈至群組?",
|
||||
"sendPrivateMessageCancel": "發佈至群組",
|
||||
"sendPrivateMessageOk": "私人回覆",
|
||||
"sendPrivateMessageTitle": "私人傳訊?",
|
||||
"sendPrivateMessageTitle": "私人回覆?",
|
||||
"serviceUnavailable": "服務無法使用",
|
||||
"sessTerminated": "通話已經終止",
|
||||
"sessionRestarted": "通話被橋接器重新啟動",
|
||||
"sessionRestarted": "通話因連線問題重新啟動。",
|
||||
"shareAudio": "繼續",
|
||||
"shareAudioTitle": "如何分享音訊",
|
||||
"shareAudioWarningD1": "您必須先停止分享畫面才能分享音訊。",
|
||||
@@ -359,11 +370,13 @@
|
||||
"shareScreenWarningD2": "您必須先停止分享音訊,啟動畫面分享,然後勾選 \"分享音訊\" 選項。",
|
||||
"shareScreenWarningH1": "如果您只要分享畫面:",
|
||||
"shareScreenWarningTitle": "您必須先停止分享音訊才能分享畫面",
|
||||
"shareVideoLinkError": "請提供正確的 YouTube 連結。",
|
||||
"shareVideoLinkError": "請提供正確的影片網址。",
|
||||
"shareVideoTitle": "分享影像",
|
||||
"shareYourScreen": "分享自己的螢幕",
|
||||
"shareYourScreenDisabled": "螢幕分享已停用。",
|
||||
"sharedVideoLinkPlaceholder": "YouTube 或影片連結",
|
||||
"sharedVideoDialogError": "錯誤:網址無效",
|
||||
"sharedVideoLinkPlaceholder": "YouTube 或影片網址",
|
||||
"start": "開始 ",
|
||||
"startLiveStreaming": "啟動直播串流",
|
||||
"startRecording": "啟動錄影作業",
|
||||
"startRemoteControlErrorMessage": "嘗試啟動遠端控制階段時發生錯誤!",
|
||||
@@ -388,7 +401,7 @@
|
||||
"yourEntireScreen": "您的畫面"
|
||||
},
|
||||
"documentSharing": {
|
||||
"title": "分享的文件"
|
||||
"title": "分享的檔案"
|
||||
},
|
||||
"e2ee": {
|
||||
"labelToolTip": "此通話的音訊及視訊皆已使用端對端加密"
|
||||
@@ -406,6 +419,10 @@
|
||||
"veryBad": "極差",
|
||||
"veryGood": "極好"
|
||||
},
|
||||
"giphy": {
|
||||
"noResults": "找不到任何結果 :(",
|
||||
"search": "搜尋 GIPHY"
|
||||
},
|
||||
"helpView": {
|
||||
"header": "說明中心"
|
||||
},
|
||||
@@ -423,8 +440,8 @@
|
||||
"conferenceURL": "連結:",
|
||||
"copyNumber": "複製號碼",
|
||||
"country": "國家",
|
||||
"dialANumber": "要參加您的會議,撥打以下其中一支號碼,然後輸入 PIN 碼。",
|
||||
"dialInConferenceID": "PIN 號碼:",
|
||||
"dialANumber": "若要參加您的會議,請撥打以下其中一支號碼,然後輸入 PIN 碼。",
|
||||
"dialInConferenceID": "PIN 碼:",
|
||||
"dialInNotSupported": "抱歉,目前不支援電話撥入。",
|
||||
"dialInNumber": "撥入:",
|
||||
"dialInSummaryError": "目前解析撥入資訊錯誤。請稍後再試一次。",
|
||||
@@ -472,6 +489,7 @@
|
||||
"focusLocal": "聚焦於自己的影像",
|
||||
"focusRemote": "聚焦於另一人的影像",
|
||||
"fullScreen": "觀看或離開全螢幕",
|
||||
"giphyMenu": "切換 GIPHY 選單",
|
||||
"keyboardShortcuts": "快捷鍵",
|
||||
"localRecording": "顯示或隱藏本機端錄影操控",
|
||||
"mute": "靜音或解除靜音",
|
||||
@@ -525,7 +543,8 @@
|
||||
"admit": "准許",
|
||||
"admitAll": "准許所有人",
|
||||
"allow": "允許",
|
||||
"backToKnockModeButton": "沒有密碼,請要求加入",
|
||||
"backToKnockModeButton": "請求加入",
|
||||
"chat": "聊天",
|
||||
"dialogTitle": "大廳模式",
|
||||
"disableDialogContent": "已開啟大廳模式。此功能能夠確保閒雜人等無法加入您的會議。您確定要停用嗎?",
|
||||
"disableDialogSubmit": "停用",
|
||||
@@ -538,6 +557,7 @@
|
||||
"errorMissingPassword": "請輸入會議密碼",
|
||||
"invalidPassword": "密碼錯誤",
|
||||
"joinRejectedMessage": "您的加入請求遭到管理員拒絕。",
|
||||
"joinRejectedTitle": "加入請求遭拒。",
|
||||
"joinTitle": "加入會議",
|
||||
"joinWithPasswordMessage": "正在嘗試透過密碼加入,請稍候...",
|
||||
"joiningMessage": "一旦他人接受您的請求,即可加入會議",
|
||||
@@ -546,6 +566,8 @@
|
||||
"knockButton": "請求加入",
|
||||
"knockTitle": "有人想要加入會議",
|
||||
"knockingParticipantList": "要求加入的參與者名單",
|
||||
"lobbyChatStartedNotification": "{{moderator}} 與 {{attendee}} 開始了大廳聊天",
|
||||
"lobbyChatStartedTitle": "{{moderator}} 與您開始了大廳聊天。",
|
||||
"nameField": "輸入您的名字",
|
||||
"notificationLobbyAccessDenied": "{{originParticipantName}} 拒絕了 {{targetParticipantName}} 的加入請求",
|
||||
"notificationLobbyAccessGranted": "{{originParticipantName}} 同意了 {{targetParticipantName}} 的加入請求",
|
||||
@@ -600,13 +622,15 @@
|
||||
"allowedUnmute": "您可以將麥克風解除靜音、開啟視訊,或是分享您的畫面。",
|
||||
"audioUnmuteBlockedDescription": "麥克風解除靜音操作由於系統限制而被暫時封鎖。",
|
||||
"audioUnmuteBlockedTitle": "麥克風解除靜音遭封鎖!",
|
||||
"chatMessages": "聊天訊息",
|
||||
"connectedOneMember": "{{name}} 加入了會議",
|
||||
"connectedThreePlusMembers": "{{name}} 及 {{count}} 位人員加入了會議",
|
||||
"connectedTwoMembers": "{{first}} 及 {{second}} 加入了會議",
|
||||
"disconnected": "已經中斷連接",
|
||||
"displayNotifications": "顯示通知",
|
||||
"focus": "會議焦點",
|
||||
"focusFail": "{{component}} 無法使用 - 請在 {{ms}} 秒後重試",
|
||||
"grantedTo": "主持人權限已授予 {{to}}!",
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "通知",
|
||||
"hostAskedUnmute": "主持人希望您能解除靜音",
|
||||
"invitedOneMember": "{{name}} 已受邀請",
|
||||
@@ -616,6 +640,12 @@
|
||||
"leftOneMember": "{{name}} 已離開會議",
|
||||
"leftThreePlusMembers": "{{name}} 和其他人已離開會議",
|
||||
"leftTwoMembers": "{{first}} 和 {{second}} 已離開會議",
|
||||
"linkToSalesforce": "連結至 Salesforce",
|
||||
"linkToSalesforceDescription": "您可以將會議摘要連結至 Salesforce 物件。",
|
||||
"linkToSalesforceError": "無法將會議連結至 Salesforce",
|
||||
"linkToSalesforceKey": "連結此會議",
|
||||
"linkToSalesforceProgress": "正在將會議連結至 Salesforce...",
|
||||
"linkToSalesforceSuccess": "會議已連結至 Salesforce",
|
||||
"me": "自己",
|
||||
"moderationInEffectCSDescription": "若要分享視訊,請舉手",
|
||||
"moderationInEffectCSTitle": "內容分享已被管理員停用",
|
||||
@@ -639,14 +669,18 @@
|
||||
"oldElectronClientDescription1": "您似乎正在使用 Jitsi Meet 客戶端的舊版本,其有已知的安全漏洞。請更新到",
|
||||
"oldElectronClientDescription2": "最新版本",
|
||||
"oldElectronClientDescription3": "!",
|
||||
"participantWantsToJoin": "想要加入會議",
|
||||
"participantsWantToJoin": "想要加入會議",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) 已被其他參與者移除",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) 已被其他參與者設定",
|
||||
"raiseHandAction": "舉手",
|
||||
"raisedHand": "{{name}} 想要發言。",
|
||||
"raisedHands": "{{participantName}} 和其他 {{raisedHands}} 人",
|
||||
"reactionSounds": "停用音效",
|
||||
"reactionSoundsForAll": "為所有人停用音效",
|
||||
"screenShareNoAudio": "您未在選擇視窗時勾選分享音訊",
|
||||
"screenShareNoAudioTitle": "未勾選分享音訊",
|
||||
"selfViewTitle": "您隨時可以在設定中取消隱藏自己的畫面",
|
||||
"somebody": "某人",
|
||||
"startSilentDescription": "重新加入會議以啟用語音",
|
||||
"startSilentTitle": "您加入了會議而無聲音輸出!",
|
||||
@@ -656,7 +690,9 @@
|
||||
"videoMutedRemotelyDescription": "您隨時可以再次啟用。",
|
||||
"videoMutedRemotelyTitle": "您的攝影機已被 {{participantDisplayName}} 停用!",
|
||||
"videoUnmuteBlockedDescription": "視訊鏡頭解除靜音操作由於系統限制而被暫時封鎖。",
|
||||
"videoUnmuteBlockedTitle": "視訊鏡頭解除靜音遭封鎖!"
|
||||
"videoUnmuteBlockedTitle": "視訊鏡頭解除靜音遭封鎖!",
|
||||
"viewLobby": "檢視大廳",
|
||||
"waitingParticipants": "{{waitingParticipants}} 人"
|
||||
},
|
||||
"participantsPane": {
|
||||
"actions": {
|
||||
@@ -688,6 +724,7 @@
|
||||
},
|
||||
"passwordDigitsOnly": "上限為 {{number}} 位數",
|
||||
"passwordSetRemotely": "由其他參與者設定",
|
||||
"pinnedParticipant": "參與者被釘選",
|
||||
"polls": {
|
||||
"answer": {
|
||||
"skip": "跳過",
|
||||
@@ -801,7 +838,20 @@
|
||||
"setEmailLabel": "設定您的 Gravatar 電子信箱",
|
||||
"title": "簡介"
|
||||
},
|
||||
"raisedHand": "請求發言",
|
||||
"raisedHand": "想要發言",
|
||||
"raisedHandsLabel": "舉手人數",
|
||||
"record": {
|
||||
"already": {
|
||||
"linked": "會議已連結至此 Salesforce 物件。"
|
||||
},
|
||||
"type": {
|
||||
"account": "帳號",
|
||||
"contact": "聯絡",
|
||||
"lead": "淺在客戶",
|
||||
"opportunity": "機會",
|
||||
"owner": "擁有者"
|
||||
}
|
||||
},
|
||||
"recording": {
|
||||
"authDropboxText": "上傳至 Dropbox",
|
||||
"availableSpace": "可用空間:{{spaceLeft}} MB (錄影時間大約 {{duration}} 分鐘)",
|
||||
@@ -816,6 +866,11 @@
|
||||
"expandedPending": "錄影正在啟動...",
|
||||
"failedToStart": "錄影啟動失敗",
|
||||
"fileSharingdescription": "分享錄影給會議參與者",
|
||||
"highlight": "精選",
|
||||
"highlightMoment": "精選時刻",
|
||||
"highlightMomentDisabled": "您可以在錄製開始後精選時刻",
|
||||
"highlightMomentSuccess": "已精選的時刻",
|
||||
"highlightMomentSucessDescription": "您的精選時刻將新增至會議摘要。",
|
||||
"inProgress": "正在錄製或直播",
|
||||
"limitNotificationDescriptionNative": "由於目前流量過大,您的錄影時間被限制在 {{limit}} 分鐘。若要無限制的錄影,請試試 <3>{{app}}</3>。",
|
||||
"limitNotificationDescriptionWeb": "由於目前流量過大,您的錄影時間被限制在 {{limit}} 分鐘。若要無限制的錄影,請試試 <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>。",
|
||||
@@ -830,6 +885,7 @@
|
||||
"rec": "錄影",
|
||||
"serviceDescription": "您的錄影會由錄影服務儲存",
|
||||
"serviceDescriptionCloud": "雲端錄製",
|
||||
"serviceDescriptionCloudInfo": "已錄製的會議將在 24 小時後自動清除。",
|
||||
"serviceName": "錄影服務",
|
||||
"sessionAlreadyActive": "已在錄製或直播此工作階段。",
|
||||
"signIn": "登入",
|
||||
@@ -838,8 +894,9 @@
|
||||
"unavailableTitle": "錄影無法使用",
|
||||
"uploadToCloud": "上傳至雲端"
|
||||
},
|
||||
"screenshareDisplayName": "{{name}} 的畫面",
|
||||
"sectionList": {
|
||||
"pullToRefresh": "拉下以重新整理"
|
||||
"pullToRefresh": "下拉以重新整理"
|
||||
},
|
||||
"security": {
|
||||
"about": "您可以新增 $t(lockRoomPassword) 至您的會議。參與者在加入會議前必須先輸入 $t(lockRoomPassword)。",
|
||||
@@ -864,6 +921,7 @@
|
||||
"incomingMessage": "新訊息",
|
||||
"language": "語言",
|
||||
"loggedIn": "以 {{name}} 登入",
|
||||
"maxStageParticipants": "能夠被釘選至主舞台的參與者最大人數",
|
||||
"microphones": "麥克風",
|
||||
"moderator": "主持人",
|
||||
"more": "更多",
|
||||
@@ -877,6 +935,7 @@
|
||||
"selectAudioOutput": "音訊輸出",
|
||||
"selectCamera": "攝影裝置",
|
||||
"selectMic": "麥克風",
|
||||
"selfView": "自我檢視",
|
||||
"sounds": "音效",
|
||||
"speakers": "喇叭",
|
||||
"startAudioMuted": "全部人啟動時處於靜音",
|
||||
@@ -915,6 +974,7 @@
|
||||
"speakerStats": {
|
||||
"angry": "憤怒",
|
||||
"disgusted": "作嘔",
|
||||
"displayEmotions": "顯示表情",
|
||||
"fearful": "害怕",
|
||||
"happy": "開心",
|
||||
"hours": "{{count}}時",
|
||||
@@ -948,6 +1008,7 @@
|
||||
"audioOnly": "切換僅聲音",
|
||||
"audioRoute": "選擇音訊裝置",
|
||||
"boo": "喝倒彩",
|
||||
"breakoutRoom": "加入/離開分組討論室",
|
||||
"callQuality": "管理影像品質",
|
||||
"cc": "切換字幕",
|
||||
"chat": "切換聊天視窗",
|
||||
@@ -965,7 +1026,9 @@
|
||||
"invite": "邀請人員",
|
||||
"joy": "笑到流淚",
|
||||
"kick": "踢出參與者",
|
||||
"laugh": "大笑",
|
||||
"like": "比讚",
|
||||
"linkToSalesforce": "連結至 Salesforce",
|
||||
"lobbyButton": "啟用/停用大廳模式",
|
||||
"localRecording": "切換本地端錄影控制",
|
||||
"lockRoom": "切換會議密碼",
|
||||
@@ -1027,6 +1090,7 @@
|
||||
"exitFullScreen": "離開全螢幕",
|
||||
"exitTileView": "跳出格狀檢視",
|
||||
"feedback": "回饋",
|
||||
"giphy": "切換 GIPHY 選單",
|
||||
"hangup": "離開",
|
||||
"help": "說明",
|
||||
"invite": "邀請人員",
|
||||
@@ -1034,6 +1098,7 @@
|
||||
"laugh": "大笑",
|
||||
"leaveBreakoutRoom": "離開分組討論室",
|
||||
"like": "比讚",
|
||||
"linkToSalesforce": "連結至 Salesforce",
|
||||
"lobbyButtonDisable": "停用大廳模式",
|
||||
"lobbyButtonEnable": "啟用大廳模式",
|
||||
"login": "登入",
|
||||
@@ -1148,13 +1213,18 @@
|
||||
"domuteVideoOfOthers": "停用其他人的攝影機",
|
||||
"flip": "翻轉",
|
||||
"grantModerator": "授予管理員",
|
||||
"hideSelfView": "隱藏自我檢視",
|
||||
"kick": "踢出",
|
||||
"moderator": "主持人",
|
||||
"mute": "參與者處於靜音",
|
||||
"muted": "處於靜音",
|
||||
"pinToStage": "釘選至舞台",
|
||||
"remoteControl": "開始/停止遠端控制",
|
||||
"screenSharing": "參與者正在分享他們的畫面",
|
||||
"show": "顯示在台上",
|
||||
"videoMuted": "已停用攝影機",
|
||||
"showSelfView": "顯示自我檢視",
|
||||
"unpinFromStage": "取消釘選",
|
||||
"videoMuted": "已停用攝影裝置",
|
||||
"videomute": "參與者已經停止攝影裝置"
|
||||
},
|
||||
"virtualBackground": {
|
||||
@@ -1178,7 +1248,8 @@
|
||||
"slightBlur": "稍微模糊",
|
||||
"title": "虛擬背景",
|
||||
"uploadedImage": "上傳圖片 {{index}}",
|
||||
"webAssemblyWarning": "不支援 WebAssembly"
|
||||
"webAssemblyWarning": "不支援 WebAssembly",
|
||||
"webAssemblyWarningDescription": "WebAssembly 遭停用或不被此連覽器支援"
|
||||
},
|
||||
"volumeSlider": "音量滑條",
|
||||
"welcomepage": {
|
||||
|
||||
@@ -894,6 +894,7 @@
|
||||
"unavailableTitle": "Recording unavailable",
|
||||
"uploadToCloud": "Upload to the cloud"
|
||||
},
|
||||
"screenshareDisplayName": "{{name}}'s screen",
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Pull to refresh"
|
||||
},
|
||||
|
||||
@@ -87,6 +87,8 @@ import { toggleScreenshotCaptureSummary } from '../../react/features/screenshot-
|
||||
import { isScreenshotCaptureEnabled } from '../../react/features/screenshot-capture/functions';
|
||||
import { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions.any';
|
||||
import { extractYoutubeIdOrURL } from '../../react/features/shared-video/functions';
|
||||
import { toggleRequestingSubtitles, setRequestingSubtitles } from '../../react/features/subtitles/actions';
|
||||
import { isAudioMuteButtonDisabled } from '../../react/features/toolbox/functions';
|
||||
import { toggleTileView, setTileView } from '../../react/features/video-layout';
|
||||
import { muteAllParticipants } from '../../react/features/video-menu/actions';
|
||||
import { setVideoQuality } from '../../react/features/video-quality';
|
||||
@@ -371,6 +373,12 @@ function initCommands() {
|
||||
sendAnalytics(createApiEvent('screen.sharing.toggled'));
|
||||
toggleScreenSharing(options.enable);
|
||||
},
|
||||
'toggle-subtitles': () => {
|
||||
APP.store.dispatch(toggleRequestingSubtitles());
|
||||
},
|
||||
'set-subtitles': enabled => {
|
||||
APP.store.dispatch(setRequestingSubtitles(enabled));
|
||||
},
|
||||
'toggle-tile-view': () => {
|
||||
sendAnalytics(createApiEvent('tile-view.toggled'));
|
||||
|
||||
@@ -693,6 +701,9 @@ function initCommands() {
|
||||
case 'is-audio-muted':
|
||||
callback(APP.conference.isLocalAudioMuted());
|
||||
break;
|
||||
case 'is-audio-disabled':
|
||||
callback(isAudioMuteButtonDisabled(APP.store.getState()));
|
||||
break;
|
||||
case 'is-moderation-on': {
|
||||
const { mediaType } = request;
|
||||
const type = mediaType || MEDIA_TYPE.AUDIO;
|
||||
@@ -725,6 +736,9 @@ function initCommands() {
|
||||
case 'is-sharing-screen':
|
||||
callback(Boolean(APP.conference.isSharingScreen));
|
||||
break;
|
||||
case 'is-start-silent':
|
||||
callback(Boolean(APP.store.getState()['features/base/config'].startSilent));
|
||||
break;
|
||||
case 'get-content-sharing-participants': {
|
||||
const tracks = getState()['features/base/tracks'];
|
||||
const sharingParticipantIds = tracks.filter(tr => tr.videoType === 'desktop').map(t => t.participantId);
|
||||
|
||||
25
modules/API/external/external_api.js
vendored
25
modules/API/external/external_api.js
vendored
@@ -59,6 +59,7 @@ const commands = {
|
||||
setLargeVideoParticipant: 'set-large-video-participant',
|
||||
setMediaEncryptionKey: 'set-media-encryption-key',
|
||||
setParticipantVolume: 'set-participant-volume',
|
||||
setSubtitles: 'set-subtitles',
|
||||
setTileView: 'set-tile-view',
|
||||
setVideoQuality: 'set-video-quality',
|
||||
startRecording: 'start-recording',
|
||||
@@ -79,6 +80,7 @@ const commands = {
|
||||
toggleRaiseHand: 'toggle-raise-hand',
|
||||
toggleShareAudio: 'toggle-share-audio',
|
||||
toggleShareScreen: 'toggle-share-screen',
|
||||
toggleSubtitles: 'toggle-subtitles',
|
||||
toggleTileView: 'toggle-tile-view',
|
||||
toggleVirtualBackgroundDialog: 'toggle-virtual-background',
|
||||
toggleVideo: 'toggle-video'
|
||||
@@ -925,6 +927,18 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the audio disabled status.
|
||||
*
|
||||
* @returns {Promise} - Resolves with the audio disabled status and rejects on
|
||||
* failure.
|
||||
*/
|
||||
isAudioDisabled() {
|
||||
return this._transport.sendRequest({
|
||||
name: 'is-audio-disabled'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the moderation on status on the given mediaType.
|
||||
*
|
||||
@@ -978,6 +992,17 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns wether meeting is started silent.
|
||||
*
|
||||
* @returns {Promise} - Resolves with start silent status.
|
||||
*/
|
||||
isStartSilent() {
|
||||
return this._transport.sendRequest({
|
||||
name: 'is-start-silent'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the avatar URL of a participant.
|
||||
*
|
||||
|
||||
@@ -11,13 +11,15 @@ import { Avatar } from '../../../react/features/base/avatar';
|
||||
import theme from '../../../react/features/base/components/themes/participantsPaneTheme.json';
|
||||
import { getSourceNameSignalingFeatureFlag } from '../../../react/features/base/config';
|
||||
import { i18next } from '../../../react/features/base/i18n';
|
||||
import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
|
||||
import { VIDEO_TYPE } from '../../../react/features/base/media';
|
||||
import {
|
||||
getParticipantById,
|
||||
getParticipantDisplayName
|
||||
} from '../../../react/features/base/participants';
|
||||
import {
|
||||
getVideoTrackByParticipant
|
||||
getVideoTrackByParticipant,
|
||||
trackStreamingStatusChanged
|
||||
} from '../../../react/features/base/tracks';
|
||||
import { CHAT_SIZE } from '../../../react/features/chat';
|
||||
import {
|
||||
@@ -116,6 +118,14 @@ export default class LargeVideoManager {
|
||||
*/
|
||||
this._videoAspectRatio = 0;
|
||||
|
||||
/**
|
||||
* The video track in effect.
|
||||
* This is used to add and remove listeners on track streaming status change.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
this.videoTrack = undefined;
|
||||
|
||||
this.$container = $('#largeVideoContainer');
|
||||
|
||||
this.$container.css({
|
||||
@@ -242,6 +252,26 @@ export default class LargeVideoManager {
|
||||
const tracks = state['features/base/tracks'];
|
||||
const videoTrack = getVideoTrackByParticipant(tracks, participant);
|
||||
|
||||
// Remove track streaming status listener from the old track and add it to the new track,
|
||||
// in order to stop updating track streaming status for the old track and start it for the new track.
|
||||
// TODO: when this class is converted to a function react component,
|
||||
// use a custom hook to update a local track streaming status.
|
||||
if (this.videoTrack?.jitsiTrack?.getSourceName() !== videoTrack?.jitsiTrack?.getSourceName()) {
|
||||
if (this.videoTrack) {
|
||||
this.videoTrack.jitsiTrack.off(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED,
|
||||
this.handleTrackStreamingStatusChanged);
|
||||
APP.store.dispatch(trackStreamingStatusChanged(this.videoTrack.jitsiTrack,
|
||||
this.videoTrack.jitsiTrack.getTrackStreamingStatus()));
|
||||
}
|
||||
if (videoTrack && !videoTrack.local) {
|
||||
this.videoTrack = videoTrack;
|
||||
this.videoTrack.jitsiTrack.on(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED,
|
||||
this.handleTrackStreamingStatusChanged);
|
||||
APP.store.dispatch(trackStreamingStatusChanged(this.videoTrack.jitsiTrack,
|
||||
this.videoTrack.jitsiTrack.getTrackStreamingStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
isVideoRenderable = !isVideoMuted && (
|
||||
APP.conference.isLocalId(id)
|
||||
|| participant?.isLocalScreenShare
|
||||
@@ -340,6 +370,19 @@ export default class LargeVideoManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle track streaming status change event by
|
||||
* by dispatching an action to update track streaming status for the given track in app state.
|
||||
*
|
||||
* @param {JitsiTrack} jitsiTrack the track with streaming status updated
|
||||
* @param {JitsiTrackStreamingStatus} streamingStatus the updated track streaming status
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
handleTrackStreamingStatusChanged(jitsiTrack, streamingStatus) {
|
||||
APP.store.dispatch(trackStreamingStatusChanged(jitsiTrack, streamingStatus));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides notification about participant's connectivity issues to be
|
||||
* shown on the large video area.
|
||||
|
||||
@@ -6,7 +6,7 @@ import ReactDOM from 'react-dom';
|
||||
|
||||
import { browser } from '../../../react/features/base/lib-jitsi-meet';
|
||||
import { isTestModeEnabled } from '../../../react/features/base/testing';
|
||||
import { FILMSTRIP_BREAKPOINT, shouldDisplayStageFilmstrip } from '../../../react/features/filmstrip';
|
||||
import { FILMSTRIP_BREAKPOINT } from '../../../react/features/filmstrip';
|
||||
import { ORIENTATION, LargeVideoBackground, updateLastLargeVideoMediaEvent } from '../../../react/features/large-video';
|
||||
import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
|
||||
/* eslint-enable no-unused-vars */
|
||||
@@ -414,7 +414,7 @@ export class VideoContainer extends LargeContainer {
|
||||
|
||||
const verticalFilmstripWidth = state['features/filmstrip'].width?.current;
|
||||
|
||||
if (currentLayout === LAYOUTS.TILE_VIEW || shouldDisplayStageFilmstrip(state)) {
|
||||
if (currentLayout === LAYOUTS.TILE_VIEW || currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW) {
|
||||
// We don't need to resize the large video since it won't be displayed and we'll resize when returning back
|
||||
// to stage view.
|
||||
return;
|
||||
|
||||
79
package-lock.json
generated
79
package-lock.json
generated
@@ -72,10 +72,10 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1413.0.0+474b2ec7/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1425.0.0+6b629a19/lib-jitsi-meet.tgz",
|
||||
"libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
"moment": "2.29.2",
|
||||
"moment-duration-format": "2.2.2",
|
||||
"optional-require": "1.0.3",
|
||||
"promise.allsettled": "1.0.4",
|
||||
@@ -91,7 +91,7 @@
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-callstats": "3.73.7",
|
||||
"react-native-collapsible": "1.6.0",
|
||||
"react-native-default-preference": "https://git@github.com/kevinresol/react-native-default-preference#11bff5eb05cb04fd8d35b5e761eeee80525e8c6c",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-dialog": "9.2.1",
|
||||
"react-native-gesture-handler": "2.1.0",
|
||||
@@ -6017,9 +6017,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz",
|
||||
"integrity": "sha1-rDYTsdqb7RtHUQu0ZRuJMeRxRsc="
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
|
||||
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g=="
|
||||
},
|
||||
"node_modules/async-limiter": {
|
||||
"version": "1.0.1",
|
||||
@@ -11796,15 +11796,15 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1413.0.0+474b2ec7/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-7jVHp71Xo32NjLNX0OaqzXPMqVhAUKP+BxX8v3iskfmLGF9bvjIx0LntB6EJB8ApvS4J2ir5IJx7+EP1nBXt7Q==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1425.0.0+6b629a19/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-oqWGJv62jBTtGsAA1ZkBrkuzLYxAOpA/ppZ5kisy54boWbCh8/GVOfmf/OwkDrUj7iBkjlh/qRU3DUlz9l0pMw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/sdp-interop": "https://git@github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
|
||||
"@jitsi/sdp-simulcast": "0.4.0",
|
||||
"async": "0.9.0",
|
||||
"async": "3.2.3",
|
||||
"base64-js": "1.3.1",
|
||||
"current-executing-script": "0.1.3",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
@@ -12503,9 +12503,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/metro/node_modules/async": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
@@ -12781,9 +12781,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
|
||||
"version": "2.29.2",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
|
||||
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
@@ -13889,9 +13889,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/portfinder/node_modules/async": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.14"
|
||||
@@ -15069,10 +15069,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-default-preference": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "git+https://git@github.com/kevinresol/react-native-default-preference.git#11bff5eb05cb04fd8d35b5e761eeee80525e8c6c",
|
||||
"integrity": "sha512-vmUyt63mLc+xebOOWrZxTF7o7AdWQqzy6lUn7pgjnyUd93//AOpQ6iXGijL9KpNiOv8mDKWAPZKhLY1XVuzZwA==",
|
||||
"license": "MIT",
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native-default-preference/-/react-native-default-preference-1.4.4.tgz",
|
||||
"integrity": "sha512-h0vtgiSKws3UmMRJykXAVM4ne1SgfoocUcoBD19ewRpQd6wqurE0HJRQGrSxcHK5LdKE7QPSIB1VX3YGIVS8Jg==",
|
||||
"peerDependencies": {
|
||||
"react-native": ">=0.47.0"
|
||||
}
|
||||
@@ -24420,9 +24419,9 @@
|
||||
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg=="
|
||||
},
|
||||
"async": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz",
|
||||
"integrity": "sha1-rDYTsdqb7RtHUQu0ZRuJMeRxRsc="
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
|
||||
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g=="
|
||||
},
|
||||
"async-limiter": {
|
||||
"version": "1.0.1",
|
||||
@@ -28900,14 +28899,14 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1413.0.0+474b2ec7/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-7jVHp71Xo32NjLNX0OaqzXPMqVhAUKP+BxX8v3iskfmLGF9bvjIx0LntB6EJB8ApvS4J2ir5IJx7+EP1nBXt7Q==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1425.0.0+6b629a19/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-oqWGJv62jBTtGsAA1ZkBrkuzLYxAOpA/ppZ5kisy54boWbCh8/GVOfmf/OwkDrUj7iBkjlh/qRU3DUlz9l0pMw==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/sdp-interop": "https://git@github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
|
||||
"@jitsi/sdp-simulcast": "0.4.0",
|
||||
"async": "0.9.0",
|
||||
"async": "3.2.3",
|
||||
"base64-js": "1.3.1",
|
||||
"current-executing-script": "0.1.3",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
@@ -29248,9 +29247,9 @@
|
||||
}
|
||||
},
|
||||
"async": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
@@ -29728,9 +29727,9 @@
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
|
||||
"version": "2.29.2",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
|
||||
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg=="
|
||||
},
|
||||
"moment-duration-format": {
|
||||
"version": "2.2.2",
|
||||
@@ -30536,9 +30535,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
@@ -31505,9 +31504,9 @@
|
||||
"integrity": "sha512-beZjdgbT9Y/Pg591Xy5XkKG20HffJiVad4n9bfcUF/f783A+tvOVXnqvbS58Lkaym93mi4jcDPMuW9Vc1t6rqg=="
|
||||
},
|
||||
"react-native-default-preference": {
|
||||
"version": "git+https://git@github.com/kevinresol/react-native-default-preference.git#11bff5eb05cb04fd8d35b5e761eeee80525e8c6c",
|
||||
"integrity": "sha512-vmUyt63mLc+xebOOWrZxTF7o7AdWQqzy6lUn7pgjnyUd93//AOpQ6iXGijL9KpNiOv8mDKWAPZKhLY1XVuzZwA==",
|
||||
"from": "react-native-default-preference@https://git@github.com/kevinresol/react-native-default-preference#11bff5eb05cb04fd8d35b5e761eeee80525e8c6c"
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native-default-preference/-/react-native-default-preference-1.4.4.tgz",
|
||||
"integrity": "sha512-h0vtgiSKws3UmMRJykXAVM4ne1SgfoocUcoBD19ewRpQd6wqurE0HJRQGrSxcHK5LdKE7QPSIB1VX3YGIVS8Jg=="
|
||||
},
|
||||
"react-native-device-info": {
|
||||
"version": "8.4.8",
|
||||
|
||||
@@ -77,10 +77,10 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1413.0.0+474b2ec7/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1425.0.0+6b629a19/lib-jitsi-meet.tgz",
|
||||
"libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
"moment": "2.29.2",
|
||||
"moment-duration-format": "2.2.2",
|
||||
"optional-require": "1.0.3",
|
||||
"promise.allsettled": "1.0.4",
|
||||
@@ -96,7 +96,7 @@
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-callstats": "3.73.7",
|
||||
"react-native-collapsible": "1.6.0",
|
||||
"react-native-default-preference": "https://git@github.com/kevinresol/react-native-default-preference#11bff5eb05cb04fd8d35b5e761eeee80525e8c6c",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-dialog": "9.2.1",
|
||||
"react-native-gesture-handler": "2.1.0",
|
||||
|
||||
@@ -68,11 +68,12 @@ export default class AudioMuteButton extends Component<Props, State> {
|
||||
|
||||
Promise.all([
|
||||
api.isAudioAvailable(),
|
||||
api.isAudioMuted()
|
||||
api.isAudioMuted(),
|
||||
api.isAudioDisabled?.() || Promise.resolve(false)
|
||||
])
|
||||
.then(([ audioAvailable, audioMuted ]) =>
|
||||
.then(([ audioAvailable, audioMuted, audioDisabled ]) =>
|
||||
this.setState({
|
||||
audioAvailable,
|
||||
audioAvailable: audioAvailable && !audioDisabled,
|
||||
audioMuted
|
||||
}))
|
||||
.catch(console.error);
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
storeConfig
|
||||
} from '../base/config';
|
||||
import { connect, disconnect, setLocationURL } from '../base/connection';
|
||||
import { loadConfig } from '../base/lib-jitsi-meet';
|
||||
import { loadConfig } from '../base/lib-jitsi-meet/functions.native';
|
||||
import { createDesiredLocalTracks } from '../base/tracks';
|
||||
import {
|
||||
getBackendSafeRoomName,
|
||||
@@ -39,6 +39,8 @@ export * from './actions.any';
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function appNavigate(uri: ?string) {
|
||||
logger.info(`appNavigate to ${uri}`);
|
||||
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
let location = parseURIString(uri);
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ const styles = () => {
|
||||
color: 'rgba(255, 255, 255, 1)',
|
||||
fontWeight: '100',
|
||||
objectFit: 'cover',
|
||||
textAlign: 'center',
|
||||
|
||||
'&.avatar-small': {
|
||||
height: '28px !important',
|
||||
|
||||
@@ -56,6 +56,11 @@ export type Props = {
|
||||
*/
|
||||
onKeyPress?: Function,
|
||||
|
||||
/**
|
||||
* TestId of the element, if any.
|
||||
*/
|
||||
testId?: string,
|
||||
|
||||
/**
|
||||
* Action text.
|
||||
*/
|
||||
@@ -112,6 +117,7 @@ const ContextMenuItem = ({
|
||||
onClick,
|
||||
onKeyDown,
|
||||
onKeyPress,
|
||||
testId,
|
||||
text,
|
||||
textClassName }: Props) => {
|
||||
const styles = useStyles();
|
||||
@@ -126,6 +132,7 @@ const ContextMenuItem = ({
|
||||
disabled && styles.contextMenuItemDisabled,
|
||||
className
|
||||
) }
|
||||
data-testid = { testId }
|
||||
id = { id }
|
||||
key = { text }
|
||||
onClick = { disabled ? undefined : onClick }
|
||||
|
||||
@@ -49,15 +49,14 @@ const _updateLastN = debounce(({ dispatch, getState }) => {
|
||||
const { appState } = state['features/background'] || {};
|
||||
const { enabled: filmStripEnabled } = state['features/filmstrip'];
|
||||
const config = state['features/base/config'];
|
||||
const { lastNLimits, lastN } = state['features/base/lastn'];
|
||||
const { lastNLimits } = state['features/base/lastn'];
|
||||
const participantCount = getParticipantCount(state);
|
||||
|
||||
// Select the lastN value based on the following preference order.
|
||||
// 1. The last-n value in redux.
|
||||
// 2. The last-n value from 'startLastN' if it is specified in config.js
|
||||
// 3. The last-n value from 'channelLastN' if specified in config.js.
|
||||
// 4. -1 as the default value.
|
||||
let lastNSelected = lastN || (config.startLastN ?? (config.channelLastN ?? -1));
|
||||
// Select the (initial) lastN value based on the following preference order.
|
||||
// 1. The last-n value from 'startLastN' if it is specified in config.js
|
||||
// 2. The last-n value from 'channelLastN' if specified in config.js.
|
||||
// 3. -1 as the default value.
|
||||
let lastNSelected = config.startLastN ?? (config.channelLastN ?? -1);
|
||||
|
||||
// Apply last N limit based on the # of participants and config settings.
|
||||
const limitedLastN = limitLastN(participantCount, lastNLimits);
|
||||
|
||||
@@ -37,6 +37,11 @@ type Props = {
|
||||
*/
|
||||
hasTabNavigator?: boolean,
|
||||
|
||||
/**
|
||||
* Insets for the SafeAreaView.
|
||||
*/
|
||||
safeAreaInsets?: Array,
|
||||
|
||||
/**
|
||||
* Additional style to be appended to the KeyboardAvoidingView containing the content of the modal.
|
||||
*/
|
||||
@@ -49,6 +54,7 @@ const JitsiScreen = ({
|
||||
footerComponent,
|
||||
hasTabNavigator = false,
|
||||
hasBottomTextInput = false,
|
||||
safeAreaInsets = [ 'bottom', 'left', 'right' ],
|
||||
style
|
||||
}: Props) => (
|
||||
<View
|
||||
@@ -59,11 +65,7 @@ const JitsiScreen = ({
|
||||
hasTabNavigator = { hasTabNavigator }
|
||||
style = { style }>
|
||||
<SafeAreaView
|
||||
edges = { [
|
||||
'bottom',
|
||||
'left',
|
||||
'right'
|
||||
] }
|
||||
edges = { safeAreaInsets }
|
||||
style = { styles.safeArea }>
|
||||
{ children }
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -173,6 +173,17 @@ export const HIDDEN_PARTICIPANT_LEFT = 'HIDDEN_PARTICIPANT_LEFT';
|
||||
*/
|
||||
export const SET_LOADABLE_AVATAR_URL = 'SET_LOADABLE_AVATAR_URL';
|
||||
|
||||
/**
|
||||
* The type of Redux action which notifies that the screenshare participant's display name has changed.
|
||||
*
|
||||
* {
|
||||
* type: SCREENSHARE_PARTICIPANT_NAME_CHANGED,
|
||||
* id: string,
|
||||
* name: string
|
||||
* }
|
||||
*/
|
||||
export const SCREENSHARE_PARTICIPANT_NAME_CHANGED = 'SCREENSHARE_PARTICIPANT_NAME_CHANGED';
|
||||
|
||||
/**
|
||||
* Raises hand for the local participant.
|
||||
* {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
PARTICIPANT_LEFT,
|
||||
PARTICIPANT_UPDATED,
|
||||
PIN_PARTICIPANT,
|
||||
SCREENSHARE_PARTICIPANT_NAME_CHANGED,
|
||||
SET_LOADABLE_AVATAR_URL,
|
||||
RAISE_HAND_UPDATED
|
||||
} from './actionTypes';
|
||||
@@ -432,6 +433,25 @@ export function participantRoleChanged(id, role) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to signal that a participant's display name has changed.
|
||||
*
|
||||
* @param {string} id - Screenshare participant's ID.
|
||||
* @param {name} name - The new display name of the screenshare participant's owner.
|
||||
* @returns {{
|
||||
* type: SCREENSHARE_PARTICIPANT_NAME_CHANGED,
|
||||
* id: string,
|
||||
* name: string
|
||||
* }}
|
||||
*/
|
||||
export function screenshareParticipantDisplayNameChanged(id, name) {
|
||||
return {
|
||||
type: SCREENSHARE_PARTICIPANT_NAME_CHANGED,
|
||||
id,
|
||||
name
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to signal that some of participant properties has been changed.
|
||||
*
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import { getGravatarURL } from '@jitsi/js-utils/avatar';
|
||||
import type { Store } from 'redux';
|
||||
|
||||
import { isStageFilmstripEnabled } from '../../filmstrip/functions';
|
||||
import { i18next } from '../../base/i18n';
|
||||
import { isStageFilmstripAvailable } from '../../filmstrip/functions';
|
||||
import { GRAVATAR_BASE_URL, isCORSAvatarURL } from '../avatar';
|
||||
import { getSourceNameSignalingFeatureFlag } from '../config';
|
||||
import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
|
||||
@@ -105,6 +106,22 @@ export function getLocalScreenShareParticipant(stateful: Object | Function) {
|
||||
return state.localScreenShare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns screenshare participant.
|
||||
*
|
||||
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
|
||||
* retrieve the state features/base/participants.
|
||||
* @param {string} id - The owner ID of the screenshare participant to retrieve.
|
||||
* @returns {(Participant|undefined)}
|
||||
*/
|
||||
export function getScreenshareParticipantByOwnerId(stateful: Object | Function, id: string) {
|
||||
const track = getTrackByMediaTypeAndParticipant(
|
||||
toState(stateful)['features/base/tracks'], MEDIA_TYPE.SCREENSHARE, id
|
||||
);
|
||||
|
||||
return getParticipantById(stateful, track?.jitsiTrack.getSourceName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a display name so then no invalid values (padding, length...etc)
|
||||
* can be set.
|
||||
@@ -244,14 +261,12 @@ export function getParticipantCountWithFake(stateful: Object | Function) {
|
||||
/**
|
||||
* Returns participant's display name.
|
||||
*
|
||||
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
|
||||
* {@code getState} function to be used to retrieve the state.
|
||||
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
|
||||
* retrieve the state.
|
||||
* @param {string} id - The ID of the participant's display name to retrieve.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getParticipantDisplayName(
|
||||
stateful: Object | Function,
|
||||
id: string) {
|
||||
export function getParticipantDisplayName(stateful: Object | Function, id: string) {
|
||||
const participant = getParticipantById(stateful, id);
|
||||
const {
|
||||
defaultLocalDisplayName,
|
||||
@@ -259,6 +274,10 @@ export function getParticipantDisplayName(
|
||||
} = toState(stateful)['features/base/config'];
|
||||
|
||||
if (participant) {
|
||||
if (participant.isFakeScreenShareParticipant) {
|
||||
return getScreenshareParticipantDisplayName(stateful, id);
|
||||
}
|
||||
|
||||
if (participant.name) {
|
||||
return participant.name;
|
||||
}
|
||||
@@ -271,6 +290,21 @@ export function getParticipantDisplayName(
|
||||
return defaultRemoteDisplayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns screenshare participant's display name.
|
||||
*
|
||||
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
|
||||
* retrieve the state.
|
||||
* @param {string} id - The ID of the screenshare participant's display name to retrieve.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getScreenshareParticipantDisplayName(stateful: Object | Function, id: string) {
|
||||
const owner = getParticipantById(stateful, getFakeScreenShareParticipantOwnerId(id));
|
||||
const name = owner.name;
|
||||
|
||||
return i18next.t('screenshareDisplayName', { name });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the presence status of a participant associated with the passed id.
|
||||
*
|
||||
@@ -338,7 +372,7 @@ export function getRemoteParticipantsSorted(stateful: Object | Function) {
|
||||
export function getPinnedParticipant(stateful: Object | Function) {
|
||||
const state = toState(stateful);
|
||||
const { pinnedParticipant } = state['features/base/participants'];
|
||||
const stageFilmstrip = isStageFilmstripEnabled(state);
|
||||
const stageFilmstrip = isStageFilmstripAvailable(state);
|
||||
|
||||
if (stageFilmstrip) {
|
||||
const { activeParticipants } = state['features/filmstrip'];
|
||||
|
||||
@@ -210,14 +210,22 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
|
||||
case PARTICIPANT_JOINED: {
|
||||
_maybePlaySounds(store, action);
|
||||
const { isFakeScreenShareParticipant } = action.participant;
|
||||
|
||||
// Do not play sounds when a fake participant tile is created for screenshare.
|
||||
!isFakeScreenShareParticipant && _maybePlaySounds(store, action);
|
||||
|
||||
return _participantJoinedOrUpdated(store, next, action);
|
||||
}
|
||||
|
||||
case PARTICIPANT_LEFT:
|
||||
_maybePlaySounds(store, action);
|
||||
case PARTICIPANT_LEFT: {
|
||||
const { isFakeScreenShareParticipant } = action.participant;
|
||||
|
||||
// Do not play sounds when a tile for screenshare is removed.
|
||||
!isFakeScreenShareParticipant && _maybePlaySounds(store, action);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PARTICIPANT_UPDATED:
|
||||
return _participantJoinedOrUpdated(store, next, action);
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
PARTICIPANT_UPDATED,
|
||||
PIN_PARTICIPANT,
|
||||
RAISE_HAND_UPDATED,
|
||||
SCREENSHARE_PARTICIPANT_NAME_CHANGED,
|
||||
SET_LOADABLE_AVATAR_URL
|
||||
} from './actionTypes';
|
||||
import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants';
|
||||
@@ -209,6 +210,23 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
|
||||
...state
|
||||
};
|
||||
}
|
||||
case SCREENSHARE_PARTICIPANT_NAME_CHANGED: {
|
||||
const { id, name } = action;
|
||||
|
||||
if (state.sortedRemoteFakeScreenShareParticipants.has(id)) {
|
||||
state.sortedRemoteFakeScreenShareParticipants.delete(id);
|
||||
|
||||
const sortedRemoteFakeScreenShareParticipants = [ ...state.sortedRemoteFakeScreenShareParticipants ];
|
||||
|
||||
sortedRemoteFakeScreenShareParticipants.push([ id, name ]);
|
||||
sortedRemoteFakeScreenShareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
|
||||
state.sortedRemoteFakeScreenShareParticipants = new Map(sortedRemoteFakeScreenShareParticipants);
|
||||
}
|
||||
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
case PARTICIPANT_JOINED: {
|
||||
const participant = _participantJoined(action);
|
||||
const { id, isFakeParticipant, isFakeScreenShareParticipant, isLocalScreenShare, name, pinned } = participant;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { connect } from '../../../../base/redux';
|
||||
import DeviceStatus from '../../../../prejoin/components/preview/DeviceStatus';
|
||||
import { Toolbox } from '../../../../toolbox/components/web';
|
||||
import { PREMEETING_BUTTONS, THIRD_PARTY_PREJOIN_BUTTONS } from '../../../config/constants';
|
||||
import { getToolbarButtons, isToolbarButtonEnabled } from '../../../config/functions.web';
|
||||
|
||||
import ConnectionStatus from './ConnectionStatus';
|
||||
import Preview from './Preview';
|
||||
@@ -147,14 +148,23 @@ class PreMeetingScreen extends PureComponent<Props> {
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state, ownProps): Object {
|
||||
const hideButtons = state['features/base/config'].hiddenPremeetingButtons || [];
|
||||
const premeetingButtons = ownProps.thirdParty
|
||||
const { hiddenPremeetingButtons } = state['features/base/config'];
|
||||
const toolbarButtons = getToolbarButtons(state);
|
||||
const premeetingButtons = (ownProps.thirdParty
|
||||
? THIRD_PARTY_PREJOIN_BUTTONS
|
||||
: PREMEETING_BUTTONS;
|
||||
: PREMEETING_BUTTONS).filter(b => !(hiddenPremeetingButtons || []).includes(b));
|
||||
|
||||
const { premeetingBackground } = state['features/dynamic-branding'];
|
||||
|
||||
return {
|
||||
_buttons: premeetingButtons.filter(b => !hideButtons.includes(b)),
|
||||
// For keeping backwards compat.: if we pass an empty hiddenPremeetingButtons
|
||||
// array through external api, we have all prejoin buttons present on premeeting
|
||||
// screen regardless of passed values into toolbarButtons config overwrite.
|
||||
// If hiddenPremeetingButtons is missing, we hide the buttons according to
|
||||
// toolbarButtons config overwrite.
|
||||
_buttons: hiddenPremeetingButtons
|
||||
? premeetingButtons
|
||||
: premeetingButtons.filter(b => isToolbarButtonEnabled(b, toolbarButtons)),
|
||||
_premeetingBackground: premeetingBackground
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Icon } from '../../../icons';
|
||||
import { Popover } from '../../../popover';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Whether the element popup is expanded.
|
||||
*/
|
||||
ariaExpanded?: boolean,
|
||||
|
||||
/**
|
||||
* The id of the element this button icon controls.
|
||||
*/
|
||||
ariaControls?: string,
|
||||
|
||||
/**
|
||||
* Whether the element has a popup.
|
||||
*/
|
||||
ariaHasPopup?: boolean,
|
||||
|
||||
/**
|
||||
* Aria label for the Icon.
|
||||
*/
|
||||
ariaLabel?: string,
|
||||
|
||||
/**
|
||||
* The decorated component (ToolboxButton).
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* Icon of the button.
|
||||
*/
|
||||
icon: Function,
|
||||
|
||||
/**
|
||||
* Flag used for disabling the small icon.
|
||||
*/
|
||||
iconDisabled: boolean,
|
||||
|
||||
/**
|
||||
* The ID of the icon button.
|
||||
*/
|
||||
iconId: string,
|
||||
|
||||
/**
|
||||
* Popover close callback.
|
||||
*/
|
||||
onPopoverClose: Function,
|
||||
|
||||
/**
|
||||
* Popover open callback.
|
||||
*/
|
||||
onPopoverOpen: Function,
|
||||
|
||||
/**
|
||||
* The content that will be displayed inside the popover.
|
||||
*/
|
||||
popoverContent: React$Node,
|
||||
|
||||
/**
|
||||
* Additional styles.
|
||||
*/
|
||||
styles?: Object,
|
||||
|
||||
/**
|
||||
* Whether or not the popover is visible.
|
||||
*/
|
||||
visible: boolean
|
||||
};
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Displays the `ToolboxButtonWithIcon` component.
|
||||
*
|
||||
* @param {Object} props - Component's props.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
export default function ToolboxButtonWithIconPopup(props: Props) {
|
||||
const {
|
||||
ariaControls,
|
||||
ariaExpanded,
|
||||
ariaHasPopup,
|
||||
ariaLabel,
|
||||
children,
|
||||
icon,
|
||||
iconDisabled,
|
||||
iconId,
|
||||
onPopoverClose,
|
||||
onPopoverOpen,
|
||||
popoverContent,
|
||||
styles,
|
||||
visible
|
||||
} = props;
|
||||
|
||||
const iconProps = {};
|
||||
|
||||
if (iconDisabled) {
|
||||
iconProps.className
|
||||
= 'settings-button-small-icon settings-button-small-icon--disabled';
|
||||
} else {
|
||||
iconProps.className = 'settings-button-small-icon';
|
||||
iconProps.role = 'button';
|
||||
iconProps.tabIndex = 0;
|
||||
iconProps.ariaControls = ariaControls;
|
||||
iconProps.ariaExpanded = ariaExpanded;
|
||||
iconProps.containerId = iconId;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'settings-button-container'
|
||||
styles = { styles }>
|
||||
{children}
|
||||
<div className = 'settings-button-small-icon-container'>
|
||||
<Popover
|
||||
content = { popoverContent }
|
||||
onPopoverClose = { onPopoverClose }
|
||||
onPopoverOpen = { onPopoverOpen }
|
||||
position = 'top'
|
||||
visible = { visible }>
|
||||
<Icon
|
||||
{ ...iconProps }
|
||||
ariaHasPopup = { ariaHasPopup }
|
||||
ariaLabel = { ariaLabel }
|
||||
size = { 9 }
|
||||
src = { icon } />
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { batch } from 'react-redux';
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { showModeratedNotification } from '../../av-moderation/actions';
|
||||
import { shouldShowModeratedNotification } from '../../av-moderation/functions';
|
||||
import { _RESET_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
|
||||
import { hideNotification, isModerationNotificationDisplayed } from '../../notifications';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
@@ -318,9 +319,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
StateListenerRegistry.register(
|
||||
state => getCurrentConference(state),
|
||||
(conference, { dispatch, getState }, prevConference) => {
|
||||
const { authRequired, error } = getState()['features/base/conference'];
|
||||
|
||||
// conference keep flipping while we are authenticating, skip clearing while we are in that process
|
||||
if (prevConference && !conference && !getState()['features/base/conference'].authRequired) {
|
||||
if (prevConference && !conference && !authRequired && !error) {
|
||||
|
||||
// Clear all tracks.
|
||||
const remoteTracks = getState()['features/base/tracks'].filter(t => !t.local);
|
||||
@@ -330,6 +332,7 @@ StateListenerRegistry.register(
|
||||
for (const track of remoteTracks) {
|
||||
dispatch(trackRemoved(track.jitsiTrack));
|
||||
}
|
||||
dispatch({ type: _RESET_BREAKOUT_ROOMS });
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -400,7 +403,7 @@ function createFakeScreenShareParticipant({ dispatch, getState }, { track }) {
|
||||
id: track.jitsiTrack.getSourceName(),
|
||||
isFakeScreenShareParticipant: true,
|
||||
isLocalScreenShare: track?.jitsiTrack.isLocal(),
|
||||
name: `${participant.name}'s screen`
|
||||
name: participant.name
|
||||
}));
|
||||
} else {
|
||||
logger.error(`Failed to create a screenshare participant for participantId: ${participantId}`);
|
||||
|
||||
@@ -183,7 +183,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
const { _showLobby } = this.props;
|
||||
|
||||
if (!prevProps._showLobby && _showLobby) {
|
||||
navigate(screen.lobby);
|
||||
navigate(screen.lobby.root);
|
||||
}
|
||||
|
||||
if (prevProps._showLobby && !_showLobby) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import clsx from 'clsx';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
@@ -12,7 +11,7 @@ import { translate } from '../../../base/i18n';
|
||||
import { connect as reactReduxConnect } from '../../../base/redux';
|
||||
import { setColorAlpha } from '../../../base/util';
|
||||
import { Chat } from '../../../chat';
|
||||
import { MainFilmstrip, StageFilmstrip, shouldDisplayStageFilmstrip } from '../../../filmstrip';
|
||||
import { MainFilmstrip, StageFilmstrip } from '../../../filmstrip';
|
||||
import { CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { LobbyScreen } from '../../../lobby';
|
||||
@@ -59,7 +58,8 @@ const FULL_SCREEN_EVENTS = [
|
||||
export const LAYOUT_CLASSNAMES = {
|
||||
[LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'horizontal-filmstrip',
|
||||
[LAYOUTS.TILE_VIEW]: 'tile-view',
|
||||
[LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'vertical-filmstrip'
|
||||
[LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'vertical-filmstrip',
|
||||
[LAYOUTS.STAGE_FILMSTRIP_VIEW]: 'stage-filmstrip'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -103,11 +103,6 @@ type Props = AbstractProps & {
|
||||
*/
|
||||
_showPrejoin: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the stage filmstrip should be displayed.
|
||||
*/
|
||||
_showStageFilmstrip: boolean,
|
||||
|
||||
dispatch: Function,
|
||||
t: Function
|
||||
}
|
||||
@@ -220,8 +215,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
_notificationsVisible,
|
||||
_overflowDrawer,
|
||||
_showLobby,
|
||||
_showPrejoin,
|
||||
_showStageFilmstrip
|
||||
_showPrejoin
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -233,7 +227,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
ref = { this._setBackground }>
|
||||
<Chat />
|
||||
<div
|
||||
className = { clsx(_layoutClassName, _showStageFilmstrip && 'stage-filmstrip') }
|
||||
className = { _layoutClassName }
|
||||
id = 'videoconference_page'
|
||||
onMouseMove = { isMobileBrowser() ? undefined : this._onShowToolbar }>
|
||||
<ConferenceInfo />
|
||||
@@ -242,7 +236,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
id = 'videospace'
|
||||
onTouchStart = { this._onVidespaceTouchStart }>
|
||||
<LargeVideo />
|
||||
{_showStageFilmstrip && <StageFilmstrip />}
|
||||
<StageFilmstrip />
|
||||
<MainFilmstrip />
|
||||
</div>
|
||||
|
||||
@@ -402,8 +396,7 @@ function _mapStateToProps(state) {
|
||||
_overflowDrawer: overflowDrawer,
|
||||
_roomName: getConferenceNameForTitle(state),
|
||||
_showLobby: getIsLobbyVisible(state),
|
||||
_showPrejoin: isPrejoinPageVisible(state),
|
||||
_showStageFilmstrip: shouldDisplayStageFilmstrip(state)
|
||||
_showPrejoin: isPrejoinPageVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -52,12 +52,14 @@ export const ConnectionIndicatorIcon = ({
|
||||
}: Props) => {
|
||||
const sourceNameSignalingEnabled = useSelector(state => getSourceNameSignalingFeatureFlag(state));
|
||||
const dispatch = useDispatch();
|
||||
const sourceName = track?.jitsiTrack?.getSourceName?.();
|
||||
const sourceName = track?.jitsiTrack?.getSourceName();
|
||||
|
||||
const handleTrackStreamingStatusChanged = streamingStatus => {
|
||||
dispatch(trackStreamingStatusChanged(track.jitsiTrack, streamingStatus));
|
||||
};
|
||||
|
||||
// TODO: replace this with a custom hook to be reused where track streaming status is needed.
|
||||
// TODO: In the hood the listener should updates a local track streaming status instead of that in redux store.
|
||||
useEffect(() => {
|
||||
if (track && !track.local && sourceNameSignalingEnabled) {
|
||||
track.jitsiTrack.on(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED, handleTrackStreamingStatusChanged);
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
setAudioOutputDeviceId,
|
||||
setVideoInputDevice
|
||||
} from '../base/devices';
|
||||
import { isIosMobileBrowser } from '../base/environment/utils';
|
||||
import { browser } from '../base/lib-jitsi-meet';
|
||||
import { updateSettings } from '../base/settings';
|
||||
|
||||
import { getDeviceSelectionDialogProps } from './functions';
|
||||
@@ -17,33 +19,34 @@ import logger from './logger';
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function submitDeviceSelectionTab(newState) {
|
||||
// Always use the new track for mobile Safari because of https://bugs.webkit.org/show_bug.cgi?id=179363#c30. The
|
||||
// old track is stopped by the browser when a new track is created for preview so it needs to be replaced even if
|
||||
// the device selection doesn't change.
|
||||
const replaceTrackAlways = isIosMobileBrowser() && browser.isVersionGreaterThan('15.3');
|
||||
|
||||
return (dispatch, getState) => {
|
||||
const currentState = getDeviceSelectionDialogProps(getState());
|
||||
|
||||
if (newState.selectedVideoInputId
|
||||
&& newState.selectedVideoInputId
|
||||
!== currentState.selectedVideoInputId) {
|
||||
if ((newState.selectedVideoInputId && (newState.selectedVideoInputId !== currentState.selectedVideoInputId))
|
||||
|| replaceTrackAlways) {
|
||||
dispatch(updateSettings({
|
||||
userSelectedCameraDeviceId: newState.selectedVideoInputId,
|
||||
userSelectedCameraDeviceLabel:
|
||||
getDeviceLabelById(getState(), newState.selectedVideoInputId, 'videoInput')
|
||||
}));
|
||||
|
||||
dispatch(
|
||||
setVideoInputDevice(newState.selectedVideoInputId));
|
||||
dispatch(setVideoInputDevice(newState.selectedVideoInputId));
|
||||
}
|
||||
|
||||
if (newState.selectedAudioInputId
|
||||
&& newState.selectedAudioInputId
|
||||
!== currentState.selectedAudioInputId) {
|
||||
if ((newState.selectedAudioInputId && newState.selectedAudioInputId !== currentState.selectedAudioInputId)
|
||||
|| replaceTrackAlways) {
|
||||
dispatch(updateSettings({
|
||||
userSelectedMicDeviceId: newState.selectedAudioInputId,
|
||||
userSelectedMicDeviceLabel:
|
||||
getDeviceLabelById(getState(), newState.selectedAudioInputId, 'audioInput')
|
||||
}));
|
||||
|
||||
dispatch(
|
||||
setAudioInputDevice(newState.selectedAudioInputId));
|
||||
dispatch(setAudioInputDevice(newState.selectedAudioInputId));
|
||||
}
|
||||
|
||||
if (newState.selectedAudioOutputId
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
setAudioOutputDevice,
|
||||
setVideoInputDeviceAndUpdateSettings
|
||||
} from '../base/devices';
|
||||
import { isIosMobileBrowser } from '../base/environment/utils';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { toState } from '../base/redux';
|
||||
import {
|
||||
@@ -34,16 +33,15 @@ export function getDeviceSelectionDialogProps(stateful: Object | Function) {
|
||||
const settings = state['features/base/settings'];
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { permissions } = state['features/base/devices'];
|
||||
const isMobileSafari = isIosMobileBrowser();
|
||||
const cameraChangeSupported = JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('input');
|
||||
const speakerChangeSupported = JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output');
|
||||
const userSelectedCamera = getUserSelectedCameraDeviceId(state);
|
||||
const userSelectedMic = getUserSelectedMicDeviceId(state);
|
||||
let disableAudioInputChange = !JitsiMeetJS.mediaDevices.isMultipleAudioInputSupported();
|
||||
let disableVideoInputSelect = !cameraChangeSupported;
|
||||
let selectedAudioInputId = isMobileSafari ? userSelectedMic : settings.micDeviceId;
|
||||
let selectedAudioInputId = settings.micDeviceId;
|
||||
let selectedAudioOutputId = getAudioOutputDeviceId();
|
||||
let selectedVideoInputId = isMobileSafari ? userSelectedCamera : settings.cameraDeviceId;
|
||||
let selectedVideoInputId = settings.cameraDeviceId;
|
||||
|
||||
// audio input change will be a problem only when we are in a
|
||||
// conference and this is not supported, when we open device selection on
|
||||
|
||||
@@ -36,11 +36,6 @@ type Props = {
|
||||
*/
|
||||
allowEditing: boolean,
|
||||
|
||||
/**
|
||||
* The current layout of the filmstrip.
|
||||
*/
|
||||
currentLayout: string,
|
||||
|
||||
/**
|
||||
* Invoked to update the participant's display name.
|
||||
*/
|
||||
@@ -70,7 +65,12 @@ type Props = {
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* The type of thumbnail.
|
||||
*/
|
||||
thumbnailType: string
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -183,11 +183,11 @@ class DisplayName extends Component<Props, State> {
|
||||
const {
|
||||
_nameToDisplay,
|
||||
allowEditing,
|
||||
currentLayout,
|
||||
displayNameSuffix,
|
||||
classes,
|
||||
elementID,
|
||||
t
|
||||
t,
|
||||
thumbnailType
|
||||
} = this.props;
|
||||
|
||||
if (allowEditing && this.state.isEditing) {
|
||||
@@ -211,7 +211,7 @@ class DisplayName extends Component<Props, State> {
|
||||
return (
|
||||
<Tooltip
|
||||
content = { appendSuffix(_nameToDisplay, displayNameSuffix) }
|
||||
position = { getIndicatorsTooltipPosition(currentLayout) }>
|
||||
position = { getIndicatorsTooltipPosition(thumbnailType) }>
|
||||
<span
|
||||
className = { `displayname ${classes.displayName}` }
|
||||
id = { elementID }
|
||||
|
||||
@@ -6,7 +6,7 @@ import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { isDisplayNameVisible } from '../../../base/config/functions.any';
|
||||
import { getLocalParticipant } from '../../../base/participants';
|
||||
import { getLocalParticipant, getParticipantDisplayName } from '../../../base/participants';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import { getLargeVideoParticipant } from '../../../large-video/functions';
|
||||
import { isToolboxVisible } from '../../../toolbox/functions.web';
|
||||
@@ -44,8 +44,8 @@ const useStyles = makeStyles(theme => {
|
||||
const StageParticipantNameLabel = () => {
|
||||
const classes = useStyles();
|
||||
const largeVideoParticipant = useSelector(getLargeVideoParticipant);
|
||||
const nameToDisplay = largeVideoParticipant?.name;
|
||||
const selectedId = largeVideoParticipant?.id;
|
||||
const nameToDisplay = useSelector(state => getParticipantDisplayName(state, selectedId));
|
||||
|
||||
const localParticipant = useSelector(getLocalParticipant);
|
||||
const localId = localParticipant?.id;
|
||||
|
||||
@@ -170,3 +170,20 @@ export const SET_STAGE_PARTICIPANTS = 'SET_STAGE_PARTICIPANTS';
|
||||
* }
|
||||
*/
|
||||
export const SET_MAX_STAGE_PARTICIPANTS = 'SET_MAX_STAGE_PARTICIPANTS';
|
||||
|
||||
/**
|
||||
* The type of Redux action which toggles the pin state of stage participants.
|
||||
* {
|
||||
* type: TOGGLE_PIN_STAGE_PARTICIPANT,
|
||||
* participantId: String
|
||||
* }
|
||||
*/
|
||||
export const TOGGLE_PIN_STAGE_PARTICIPANT = 'TOGGLE_PIN_STAGE_PARTICIPANT';
|
||||
|
||||
/**
|
||||
* The type of Redux action which clears the list of stage participants.
|
||||
* {
|
||||
* type: CLEAR_STAGE_PARTICIPANTS
|
||||
* }
|
||||
*/
|
||||
export const CLEAR_STAGE_PARTICIPANTS = 'CLEAR_STAGE_PARTICIPANTS';
|
||||
|
||||
@@ -23,7 +23,9 @@ import {
|
||||
SET_USER_IS_RESIZING,
|
||||
SET_VERTICAL_VIEW_DIMENSIONS,
|
||||
SET_VOLUME,
|
||||
SET_MAX_STAGE_PARTICIPANTS
|
||||
SET_MAX_STAGE_PARTICIPANTS,
|
||||
TOGGLE_PIN_STAGE_PARTICIPANT,
|
||||
CLEAR_STAGE_PARTICIPANTS
|
||||
} from './actionTypes';
|
||||
import {
|
||||
HORIZONTAL_FILMSTRIP_MARGIN,
|
||||
@@ -82,14 +84,15 @@ export function setTileViewDimensions() {
|
||||
disableTileEnlargement,
|
||||
maxColumns,
|
||||
numberOfParticipants,
|
||||
numberOfVisibleTiles
|
||||
desiredNumberOfVisibleTiles: numberOfVisibleTiles
|
||||
});
|
||||
const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height);
|
||||
const hasScroll = clientHeight < thumbnailsTotalHeight;
|
||||
const availableHeight = clientHeight - TILE_VIEW_GRID_VERTICAL_MARGIN;
|
||||
const hasScroll = availableHeight < thumbnailsTotalHeight;
|
||||
const filmstripWidth
|
||||
= Math.min(clientWidth - TILE_VIEW_GRID_HORIZONTAL_MARGIN, columns * (TILE_HORIZONTAL_MARGIN + width))
|
||||
+ (hasScroll ? SCROLL_SIZE : 0);
|
||||
const filmstripHeight = Math.min(clientHeight - TILE_VIEW_GRID_VERTICAL_MARGIN, thumbnailsTotalHeight);
|
||||
const filmstripHeight = Math.min(availableHeight, thumbnailsTotalHeight);
|
||||
|
||||
dispatch({
|
||||
type: SET_TILE_VIEW_DIMENSIONS,
|
||||
@@ -138,7 +141,11 @@ export function setVerticalViewDimensions() {
|
||||
const { tileView = {} } = state['features/base/config'];
|
||||
const { numberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES } = tileView;
|
||||
const numberOfParticipants = getNumberOfPartipantsForTileView(state);
|
||||
const maxColumns = getMaxColumnCount(state);
|
||||
const maxColumns = getMaxColumnCount(state, {
|
||||
width: filmstripWidth.current,
|
||||
disableResponsiveTiles: false,
|
||||
disableTileEnlargement: false
|
||||
});
|
||||
const {
|
||||
height,
|
||||
width,
|
||||
@@ -151,7 +158,7 @@ export function setVerticalViewDimensions() {
|
||||
maxColumns,
|
||||
noHorizontalContainerMargin: true,
|
||||
numberOfParticipants,
|
||||
numberOfVisibleTiles
|
||||
desiredNumberOfVisibleTiles: numberOfVisibleTiles
|
||||
});
|
||||
const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height);
|
||||
|
||||
@@ -260,32 +267,33 @@ export function setStageFilmstripViewDimensions() {
|
||||
const state = getState();
|
||||
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
|
||||
const {
|
||||
disableResponsiveTiles,
|
||||
disableTileEnlargement,
|
||||
tileView = {}
|
||||
} = state['features/base/config'];
|
||||
const { visible } = state['features/filmstrip'];
|
||||
const verticalWidth = visible ? getVerticalViewMaxWidth(state) : 0;
|
||||
const { numberOfVisibleTiles = MAX_ACTIVE_PARTICIPANTS } = tileView;
|
||||
const numberOfParticipants = state['features/filmstrip'].activeParticipants.length;
|
||||
const maxColumns = getMaxColumnCount(state);
|
||||
const availableWidth = clientWidth - verticalWidth;
|
||||
const maxColumns = getMaxColumnCount(state, {
|
||||
width: availableWidth,
|
||||
disableResponsiveTiles: false,
|
||||
disableTileEnlargement: false
|
||||
});
|
||||
|
||||
const {
|
||||
height,
|
||||
width,
|
||||
columns,
|
||||
rows
|
||||
} = disableResponsiveTiles
|
||||
? calculateNonResponsiveTileViewDimensions(state, true)
|
||||
: calculateResponsiveTileViewDimensions({
|
||||
clientWidth: clientWidth - verticalWidth,
|
||||
clientHeight,
|
||||
disableTileEnlargement,
|
||||
maxColumns,
|
||||
noHorizontalContainerMargin: verticalWidth > 0,
|
||||
numberOfParticipants,
|
||||
numberOfVisibleTiles
|
||||
});
|
||||
} = calculateResponsiveTileViewDimensions({
|
||||
clientWidth: availableWidth,
|
||||
clientHeight,
|
||||
disableTileEnlargement: false,
|
||||
maxColumns,
|
||||
noHorizontalContainerMargin: verticalWidth > 0,
|
||||
numberOfParticipants,
|
||||
numberOfVisibleTiles
|
||||
});
|
||||
const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height);
|
||||
const hasScroll = clientHeight < thumbnailsTotalHeight;
|
||||
const filmstripWidth
|
||||
@@ -449,3 +457,27 @@ export function setMaxStageParticipants(maxParticipants) {
|
||||
maxParticipants
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the pin state of the given participant.
|
||||
*
|
||||
* @param {string} participantId - The id of the participant to be toggled.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function togglePinStageParticipant(participantId) {
|
||||
return {
|
||||
type: TOGGLE_PIN_STAGE_PARTICIPANT,
|
||||
participantId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the stage participants list.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function clearStageParticipants() {
|
||||
return {
|
||||
type: CLEAR_STAGE_PARTICIPANTS
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { connect } from '../../../base/redux';
|
||||
import { shouldHideSelfView } from '../../../base/settings/functions.any';
|
||||
import { showToolbox } from '../../../toolbox/actions.web';
|
||||
import { isButtonEnabled, isToolboxVisible } from '../../../toolbox/functions.web';
|
||||
import { LAYOUTS } from '../../../video-layout';
|
||||
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||
import {
|
||||
setFilmstripVisible,
|
||||
setVisibleRemoteParticipants,
|
||||
@@ -110,7 +110,7 @@ type Props = {
|
||||
/**
|
||||
* The local screen share participant. This prop is behind the sourceNameSignaling feature flag.
|
||||
*/
|
||||
_localScreenShare: Object,
|
||||
_localScreenShare: Object,
|
||||
|
||||
/**
|
||||
* The maximum width of the vertical filmstrip.
|
||||
@@ -318,32 +318,29 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
const { isMouseDown } = this.state;
|
||||
const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
|
||||
|
||||
switch (_currentLayout) {
|
||||
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: {
|
||||
if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && _stageFilmstrip) {
|
||||
if (_visible) {
|
||||
filmstripStyle.maxWidth = `calc(100% - ${_verticalViewMaxWidth}px)`;
|
||||
}
|
||||
} else if (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW
|
||||
|| (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && !_stageFilmstrip)) {
|
||||
filmstripStyle.maxWidth = _verticalViewMaxWidth;
|
||||
if (!_visible) {
|
||||
filmstripStyle.right = `-${filmstripStyle.maxWidth}px`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LAYOUTS.TILE_VIEW: {
|
||||
if (_stageFilmstrip && _visible) {
|
||||
filmstripStyle.maxWidth = `calc(100% - ${_verticalViewMaxWidth}px)`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let toolbar = null;
|
||||
|
||||
if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled && _currentLayout !== LAYOUTS.TILE_VIEW) {
|
||||
if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled
|
||||
&& _currentLayout !== LAYOUTS.TILE_VIEW && !_stageFilmstrip) {
|
||||
toolbar = this._renderToggleButton();
|
||||
}
|
||||
|
||||
const filmstrip = (<>
|
||||
<div
|
||||
className = { clsx(this.props._videosClassName,
|
||||
!tileViewActive && !_resizableFilmstrip && 'filmstrip-hover',
|
||||
!tileViewActive && !_stageFilmstrip && !_resizableFilmstrip && 'filmstrip-hover',
|
||||
_verticalViewGrid && 'vertical-view-grid') }
|
||||
id = 'remoteVideos'>
|
||||
{!_disableSelfView && !_verticalViewGrid && (
|
||||
@@ -351,7 +348,7 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
className = 'filmstrip__videos'
|
||||
id = 'filmstripLocalVideo'>
|
||||
{
|
||||
!tileViewActive && <div id = 'filmstripLocalVideoThumbnail'>
|
||||
!tileViewActive && !_stageFilmstrip && <div id = 'filmstripLocalVideoThumbnail'>
|
||||
<Thumbnail
|
||||
key = 'local' />
|
||||
</div>
|
||||
@@ -364,7 +361,7 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
id = 'filmstripLocalScreenShare'>
|
||||
<div id = 'filmstripLocalScreenShareThumbnail'>
|
||||
{
|
||||
!tileViewActive && <Thumbnail
|
||||
!tileViewActive && !_stageFilmstrip && <Thumbnail
|
||||
key = 'localScreenShare'
|
||||
participantID = { _localScreenShare.id } />
|
||||
|
||||
@@ -604,6 +601,7 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
_filmstripHeight,
|
||||
_filmstripWidth,
|
||||
_hasScroll,
|
||||
_isVerticalFilmstrip,
|
||||
_remoteParticipantsLength,
|
||||
_resizableFilmstrip,
|
||||
_rows,
|
||||
@@ -619,7 +617,7 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_currentLayout === LAYOUTS.TILE_VIEW || _verticalViewGrid) {
|
||||
if (_currentLayout === LAYOUTS.TILE_VIEW || _verticalViewGrid || _stageFilmstrip) {
|
||||
return (
|
||||
<FixedSizeGrid
|
||||
className = 'filmstrip__videos remote-videos'
|
||||
@@ -669,7 +667,7 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
props.className += ' is-not-overflowing';
|
||||
}
|
||||
|
||||
} else if (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
|
||||
} else if (_isVerticalFilmstrip) {
|
||||
const itemSize = _thumbnailHeight + TILE_VERTICAL_MARGIN;
|
||||
const isNotOverflowing = !_hasScroll;
|
||||
|
||||
@@ -820,15 +818,20 @@ function _mapStateToProps(state, ownProps) {
|
||||
shouldReduceHeight ? 'reduce-height' : ''
|
||||
} ${shiftRight ? 'shift-right' : ''} ${collapseTileView ? 'collapse' : ''} ${visible ? '' : 'hidden'}`.trim();
|
||||
|
||||
const _currentLayout = getCurrentLayout(state);
|
||||
const _isVerticalFilmstrip = _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW
|
||||
|| (!ownProps._stageFilmstrip && _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW);
|
||||
|
||||
return {
|
||||
_className: className,
|
||||
_chatOpen: state['features/chat'].isOpen,
|
||||
_currentLayout,
|
||||
_disableSelfView: disableSelfView,
|
||||
_hasScroll,
|
||||
_iAmRecorder: Boolean(iAmRecorder),
|
||||
_isFilmstripButtonEnabled: isButtonEnabled('filmstrip', state),
|
||||
_isToolboxVisible: isToolboxVisible(state),
|
||||
_isVerticalFilmstrip: ownProps._currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW,
|
||||
_isVerticalFilmstrip,
|
||||
_localScreenShare: getSourceNameSignalingFeatureFlag(state) && localScreenShare,
|
||||
_maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
|
||||
_thumbnailsReordered: enableThumbnailReordering,
|
||||
|
||||
@@ -17,11 +17,6 @@ import Filmstrip from './Filmstrip';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The current layout of the filmstrip.
|
||||
*/
|
||||
_currentLayout: string,
|
||||
|
||||
/**
|
||||
* The number of columns in tile view.
|
||||
*/
|
||||
@@ -143,7 +138,8 @@ function _mapStateToProps(state) {
|
||||
&& clientWidth <= ASPECT_RATIO_BREAKPOINT;
|
||||
|
||||
const shouldReduceHeight = reduceHeight && (
|
||||
isMobileBrowser() || _currentLayout !== LAYOUTS.VERTICAL_FILMSTRIP_VIEW);
|
||||
isMobileBrowser() || (_currentLayout !== LAYOUTS.VERTICAL_FILMSTRIP_VIEW
|
||||
&& _currentLayout !== LAYOUTS.STAGE_FILMSTRIP_VIEW));
|
||||
|
||||
let _thumbnailSize, remoteFilmstripHeight, remoteFilmstripWidth;
|
||||
|
||||
@@ -154,7 +150,8 @@ function _mapStateToProps(state) {
|
||||
remoteFilmstripHeight = filmstripHeight - (collapseTileView && filmstripPadding > 0 ? filmstripPadding : 0);
|
||||
remoteFilmstripWidth = filmstripWidth;
|
||||
break;
|
||||
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: {
|
||||
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
|
||||
case LAYOUTS.STAGE_FILMSTRIP_VIEW: {
|
||||
const {
|
||||
remote,
|
||||
remoteVideosContainer,
|
||||
@@ -189,7 +186,6 @@ function _mapStateToProps(state) {
|
||||
|
||||
return {
|
||||
_columns: gridDimensions.columns,
|
||||
_currentLayout,
|
||||
_filmstripHeight: remoteFilmstripHeight,
|
||||
_filmstripWidth: remoteFilmstripWidth,
|
||||
_hasScroll,
|
||||
|
||||
@@ -92,11 +92,10 @@ type Props = {
|
||||
_visible: boolean
|
||||
};
|
||||
|
||||
const StageFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW && (
|
||||
const StageFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && (
|
||||
<span className = { LAYOUT_CLASSNAMES[LAYOUTS.TILE_VIEW] }>
|
||||
<Filmstrip
|
||||
{ ...props }
|
||||
_currentLayout = { LAYOUTS.TILE_VIEW }
|
||||
_stageFilmstrip = { true } />
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,6 @@ import { MEDIA_TYPE } from '../../../base/media';
|
||||
import { getParticipantByIdOrUndefined, PARTICIPANT_ROLE } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { getTrackByMediaTypeAndParticipant, isLocalTrackMuted, isRemoteTrackMuted } from '../../../base/tracks';
|
||||
import { getCurrentLayout } from '../../../video-layout';
|
||||
import { getIndicatorsTooltipPosition } from '../../functions.web';
|
||||
|
||||
import AudioMutedIndicator from './AudioMutedIndicator';
|
||||
@@ -20,11 +19,6 @@ declare var interfaceConfig: Object;
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The current layout of the filmstrip.
|
||||
*/
|
||||
_currentLayout: string,
|
||||
|
||||
/**
|
||||
* Indicates if the audio muted indicator should be visible or not.
|
||||
*/
|
||||
@@ -43,7 +37,12 @@ type Props = {
|
||||
/**
|
||||
* The ID of the participant for which the status bar is rendered.
|
||||
*/
|
||||
participantID: String
|
||||
participantID: String,
|
||||
|
||||
/**
|
||||
* The type of thumbnail.
|
||||
*/
|
||||
thumbnailType: string
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -60,12 +59,12 @@ class StatusIndicators extends Component<Props> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_currentLayout,
|
||||
_showAudioMutedIndicator,
|
||||
_showModeratorIndicator,
|
||||
_showScreenShareIndicator
|
||||
_showScreenShareIndicator,
|
||||
thumbnailType
|
||||
} = this.props;
|
||||
const tooltipPosition = getIndicatorsTooltipPosition(_currentLayout);
|
||||
const tooltipPosition = getIndicatorsTooltipPosition(thumbnailType);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -111,7 +110,6 @@ function _mapStateToProps(state, ownProps) {
|
||||
const { disableModeratorIndicator } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_currentLayout: getCurrentLayout(state),
|
||||
_showAudioMutedIndicator: isAudioMuted && audio,
|
||||
_showModeratorIndicator:
|
||||
!disableModeratorIndicator && participant && participant.role === PARTICIPANT_ROLE.MODERATOR && moderator,
|
||||
|
||||
@@ -9,8 +9,10 @@ import { createScreenSharingIssueEvent, sendAnalytics } from '../../../analytics
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { JitsiTrackEvents } from '../../../base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantByIdOrUndefined,
|
||||
hasRaisedHand,
|
||||
pinParticipant
|
||||
@@ -23,34 +25,38 @@ import {
|
||||
getLocalVideoTrack,
|
||||
getTrackByMediaTypeAndParticipant,
|
||||
getFakeScreenshareParticipantTrack,
|
||||
updateLastTrackVideoMediaEvent
|
||||
updateLastTrackVideoMediaEvent,
|
||||
trackStreamingStatusChanged
|
||||
} from '../../../base/tracks';
|
||||
import { getVideoObjectPosition } from '../../../face-landmarks/functions';
|
||||
import { hideGif, showGif } from '../../../gifs/actions';
|
||||
import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions';
|
||||
import { PresenceLabel } from '../../../presence-status';
|
||||
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||
import { addStageParticipant } from '../../actions.web';
|
||||
import { togglePinStageParticipant } from '../../actions';
|
||||
import {
|
||||
DISPLAY_MODE_TO_CLASS_NAME,
|
||||
DISPLAY_VIDEO,
|
||||
SHOW_TOOLBAR_CONTEXT_MENU_AFTER,
|
||||
THUMBNAIL_TYPE,
|
||||
VIDEO_TEST_EVENTS
|
||||
} from '../../constants';
|
||||
import {
|
||||
computeDisplayModeFromInput,
|
||||
getActiveParticipantsIds,
|
||||
getDisplayModeInput,
|
||||
getThumbnailTypeFromLayout,
|
||||
isVideoPlayable,
|
||||
isStageFilmstripAvailable,
|
||||
showGridInVerticalView
|
||||
} from '../../functions';
|
||||
import { isStageFilmstripEnabled } from '../../functions.web';
|
||||
|
||||
import FakeScreenShareParticipant from './FakeScreenShareParticipant';
|
||||
import ThumbnailAudioIndicator from './ThumbnailAudioIndicator';
|
||||
import ThumbnailBottomIndicators from './ThumbnailBottomIndicators';
|
||||
import ThumbnailTopIndicators from './ThumbnailTopIndicators';
|
||||
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
@@ -89,11 +95,6 @@ export type Props = {|
|
||||
*/
|
||||
_audioTrack: ?Object,
|
||||
|
||||
/**
|
||||
* The current layout of the filmstrip.
|
||||
*/
|
||||
_currentLayout: string,
|
||||
|
||||
/**
|
||||
* Indicates whether the local video flip feature is disabled or not.
|
||||
*/
|
||||
@@ -187,9 +188,21 @@ export type Props = {|
|
||||
_raisedHand: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the stage filmstrip is disabled.
|
||||
* Whether or not the current layout is stage filmstrip layout.
|
||||
*/
|
||||
_stageFilmstripDisabled: boolean,
|
||||
_stageFilmstripLayout: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the participants are displayed on stage.
|
||||
* (and not screensharing or shared video; used to determine
|
||||
* whether or not the display the participant video in the vertical filmstrip).
|
||||
*/
|
||||
_stageParticipantsVisible: boolean,
|
||||
|
||||
/**
|
||||
* The type of thumbnail to display.
|
||||
*/
|
||||
_thumbnailType: string,
|
||||
|
||||
/**
|
||||
* The video object position for the participant.
|
||||
@@ -234,7 +247,12 @@ export type Props = {|
|
||||
/**
|
||||
* Styles that will be set to the Thumbnail's main span element.
|
||||
*/
|
||||
style?: ?Object
|
||||
style?: ?Object,
|
||||
|
||||
/**
|
||||
* Whether source name signaling is enabled.
|
||||
*/
|
||||
_sourceNameSignalingEnabled: boolean
|
||||
|};
|
||||
|
||||
const defaultStyles = theme => {
|
||||
@@ -394,6 +412,7 @@ class Thumbnail extends Component<Props, State> {
|
||||
this._hidePopover = this._hidePopover.bind(this);
|
||||
this._onGifMouseEnter = this._onGifMouseEnter.bind(this);
|
||||
this._onGifMouseLeave = this._onGifMouseLeave.bind(this);
|
||||
this.handleTrackStreamingStatusChanged = this.handleTrackStreamingStatusChanged.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,6 +423,38 @@ class Thumbnail extends Component<Props, State> {
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._onDisplayModeChanged();
|
||||
|
||||
|
||||
// Listen to track streaming status changed event to keep it updated.
|
||||
// TODO: after converting this component to a react function component,
|
||||
// use a custom hook to update local track streaming status.
|
||||
const { _videoTrack, dispatch, _sourceNameSignalingEnabled } = this.props;
|
||||
|
||||
if (_sourceNameSignalingEnabled && _videoTrack && !_videoTrack.local) {
|
||||
_videoTrack.jitsiTrack.on(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED,
|
||||
this.handleTrackStreamingStatusChanged);
|
||||
dispatch(trackStreamingStatusChanged(_videoTrack.jitsiTrack,
|
||||
_videoTrack.jitsiTrack.getTrackStreamingStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listeners for track streaming status update.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
// TODO: after converting this component to a react function component,
|
||||
// use a custom hook to update local track streaming status.
|
||||
const { _videoTrack, dispatch, _sourceNameSignalingEnabled } = this.props;
|
||||
|
||||
if (_sourceNameSignalingEnabled && _videoTrack && !_videoTrack.local) {
|
||||
_videoTrack.jitsiTrack.off(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED,
|
||||
this.handleTrackStreamingStatusChanged);
|
||||
dispatch(trackStreamingStatusChanged(_videoTrack.jitsiTrack,
|
||||
_videoTrack.jitsiTrack.getTrackStreamingStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -417,6 +468,38 @@ class Thumbnail extends Component<Props, State> {
|
||||
if (prevState.displayMode !== this.state.displayMode) {
|
||||
this._onDisplayModeChanged();
|
||||
}
|
||||
|
||||
// TODO: after converting this component to a react function component,
|
||||
// use a custom hook to update local track streaming status.
|
||||
const { _videoTrack, dispatch, _sourceNameSignalingEnabled } = this.props;
|
||||
|
||||
if (_sourceNameSignalingEnabled
|
||||
&& prevProps._videoTrack?.jitsiTrack?.getSourceName() !== _videoTrack?.jitsiTrack?.getSourceName()) {
|
||||
if (prevProps._videoTrack && !prevProps._videoTrack.local) {
|
||||
prevProps._videoTrack.jitsiTrack.off(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED,
|
||||
this.handleTrackStreamingStatusChanged);
|
||||
dispatch(trackStreamingStatusChanged(prevProps._videoTrack.jitsiTrack,
|
||||
prevProps._videoTrack.jitsiTrack.getTrackStreamingStatus()));
|
||||
}
|
||||
if (_videoTrack && !_videoTrack.local) {
|
||||
_videoTrack.jitsiTrack.on(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED,
|
||||
this.handleTrackStreamingStatusChanged);
|
||||
dispatch(trackStreamingStatusChanged(_videoTrack.jitsiTrack,
|
||||
_videoTrack.jitsiTrack.getTrackStreamingStatus()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle track streaming status change event by
|
||||
* by dispatching an action to update track streaming status for the given track in app state.
|
||||
*
|
||||
* @param {JitsiTrack} jitsiTrack - The track with streaming status updated.
|
||||
* @param {JitsiTrackStreamingStatus} streamingStatus - The updated track streaming status.
|
||||
* @returns {void}
|
||||
*/
|
||||
handleTrackStreamingStatusChanged(jitsiTrack, streamingStatus) {
|
||||
this.props.dispatch(trackStreamingStatusChanged(jitsiTrack, streamingStatus));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,15 +521,15 @@ class Thumbnail extends Component<Props, State> {
|
||||
*/
|
||||
_maybeSendScreenSharingIssueEvents(input) {
|
||||
const {
|
||||
_currentLayout,
|
||||
_isAudioOnly,
|
||||
_isScreenSharing
|
||||
_isScreenSharing,
|
||||
_thumbnailType
|
||||
} = this.props;
|
||||
const { displayMode } = this.state;
|
||||
const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
|
||||
const isTileType = _thumbnailType === THUMBNAIL_TYPE.TILE;
|
||||
|
||||
if (!(DISPLAY_VIDEO === displayMode)
|
||||
&& tileViewActive
|
||||
&& isTileType
|
||||
&& _isScreenSharing
|
||||
&& !_isAudioOnly) {
|
||||
sendAnalytics(createScreenSharingIssueEvent({
|
||||
@@ -521,9 +604,9 @@ class Thumbnail extends Component<Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_hidePopover() {
|
||||
const { _currentLayout } = this.props;
|
||||
const { _thumbnailType } = this.props;
|
||||
|
||||
if (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
|
||||
if (_thumbnailType === THUMBNAIL_TYPE.VERTICAL) {
|
||||
this.setState({
|
||||
isHovered: false
|
||||
});
|
||||
@@ -541,13 +624,13 @@ class Thumbnail extends Component<Props, State> {
|
||||
_getStyles(): Object {
|
||||
const { canPlayEventReceived } = this.state;
|
||||
const {
|
||||
_currentLayout,
|
||||
_disableTileEnlargement,
|
||||
_height,
|
||||
_isFakeScreenShareParticipant,
|
||||
_isHidden,
|
||||
_isScreenSharing,
|
||||
_participant,
|
||||
_thumbnailType,
|
||||
_videoObjectPosition,
|
||||
_videoTrack,
|
||||
_width,
|
||||
@@ -555,7 +638,7 @@ class Thumbnail extends Component<Props, State> {
|
||||
style
|
||||
} = this.props;
|
||||
|
||||
const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
|
||||
const isTileType = _thumbnailType === THUMBNAIL_TYPE.TILE;
|
||||
const jitsiVideoTrack = _videoTrack?.jitsiTrack;
|
||||
const track = jitsiVideoTrack?.track;
|
||||
const isPortraitVideo = ((track && track.getSettings()?.aspectRatio) || 1) < 1;
|
||||
@@ -578,7 +661,7 @@ class Thumbnail extends Component<Props, State> {
|
||||
}
|
||||
|
||||
let videoStyles = null;
|
||||
const doNotStretchVideo = (isPortraitVideo && tileViewActive)
|
||||
const doNotStretchVideo = (isPortraitVideo && isTileType)
|
||||
|| _disableTileEnlargement
|
||||
|| _isScreenSharing;
|
||||
|
||||
@@ -627,13 +710,13 @@ class Thumbnail extends Component<Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
const { _participant, dispatch, _stageFilmstripDisabled } = this.props;
|
||||
const { _participant, dispatch, _stageFilmstripLayout } = this.props;
|
||||
const { id, pinned } = _participant;
|
||||
|
||||
if (_stageFilmstripDisabled) {
|
||||
dispatch(pinParticipant(pinned ? null : id));
|
||||
if (_stageFilmstripLayout) {
|
||||
dispatch(togglePinStageParticipant(id));
|
||||
} else {
|
||||
dispatch(addStageParticipant(id, true));
|
||||
dispatch(pinParticipant(pinned ? null : id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -781,8 +864,8 @@ class Thumbnail extends Component<Props, State> {
|
||||
const {
|
||||
_isDominantSpeakerDisabled,
|
||||
_participant,
|
||||
_currentLayout,
|
||||
_raisedHand,
|
||||
_thumbnailType,
|
||||
classes
|
||||
} = this.props;
|
||||
|
||||
@@ -795,7 +878,7 @@ class Thumbnail extends Component<Props, State> {
|
||||
if (!_isDominantSpeakerDisabled && _participant?.dominantSpeaker) {
|
||||
className += ` ${classes.activeSpeaker} dominant-speaker`;
|
||||
}
|
||||
if (_currentLayout !== LAYOUTS.TILE_VIEW && _participant?.pinned) {
|
||||
if (_thumbnailType !== THUMBNAIL_TYPE.TILE && _participant?.pinned) {
|
||||
className += ' videoContainerFocused';
|
||||
}
|
||||
|
||||
@@ -893,16 +976,16 @@ class Thumbnail extends Component<Props, State> {
|
||||
_renderParticipant(local = false) {
|
||||
const {
|
||||
_audioTrack,
|
||||
_currentLayout,
|
||||
_disableLocalVideoFlip,
|
||||
_gifSrc,
|
||||
_isMobile,
|
||||
_isMobilePortrait,
|
||||
_isScreenSharing,
|
||||
_isTestModeEnabled,
|
||||
_localFlipX,
|
||||
_participant,
|
||||
_thumbnailType,
|
||||
_videoTrack,
|
||||
_gifSrc,
|
||||
classes,
|
||||
stageFilmstrip
|
||||
} = this.props;
|
||||
@@ -966,28 +1049,28 @@ class Thumbnail extends Component<Props, State> {
|
||||
<div
|
||||
className = { clsx(classes.indicatorsContainer,
|
||||
classes.indicatorsTopContainer,
|
||||
_currentLayout === LAYOUTS.TILE_VIEW && 'tile-view-mode'
|
||||
_thumbnailType === THUMBNAIL_TYPE.TILE && 'tile-view-mode'
|
||||
) }>
|
||||
<ThumbnailTopIndicators
|
||||
currentLayout = { _currentLayout }
|
||||
hidePopover = { this._hidePopover }
|
||||
indicatorsClassName = { classes.indicatorsBackground }
|
||||
isHovered = { isHovered }
|
||||
local = { local }
|
||||
participantId = { id }
|
||||
popoverVisible = { popoverVisible }
|
||||
showPopover = { this._showPopover } />
|
||||
showPopover = { this._showPopover }
|
||||
thumbnailType = { _thumbnailType } />
|
||||
</div>
|
||||
<div
|
||||
className = { clsx(classes.indicatorsContainer,
|
||||
classes.indicatorsBottomContainer,
|
||||
_currentLayout === LAYOUTS.TILE_VIEW && 'tile-view-mode'
|
||||
_thumbnailType === THUMBNAIL_TYPE.TILE && 'tile-view-mode'
|
||||
) }>
|
||||
<ThumbnailBottomIndicators
|
||||
className = { classes.indicatorsBackground }
|
||||
currentLayout = { _currentLayout }
|
||||
local = { local }
|
||||
participantId = { id } />
|
||||
participantId = { id }
|
||||
thumbnailType = { _thumbnailType } />
|
||||
</div>
|
||||
{!_gifSrc && this._renderAvatar(styles.avatar) }
|
||||
{ !local && (
|
||||
@@ -1094,7 +1177,7 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
}
|
||||
const _audioTrack = isLocal
|
||||
? getLocalAudioTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, participantID);
|
||||
const _currentLayout = stageFilmstrip ? LAYOUTS.TILE_VIEW : getCurrentLayout(state);
|
||||
const _currentLayout = getCurrentLayout(state);
|
||||
let size = {};
|
||||
let _isMobilePortrait = false;
|
||||
const {
|
||||
@@ -1107,10 +1190,12 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
const { localFlipX } = state['features/base/settings'];
|
||||
const _isMobile = isMobileBrowser();
|
||||
const activeParticipants = getActiveParticipantsIds(state);
|
||||
const tileType = getThumbnailTypeFromLayout(_currentLayout, stageFilmstrip);
|
||||
|
||||
switch (_currentLayout) {
|
||||
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
|
||||
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: {
|
||||
|
||||
switch (tileType) {
|
||||
case THUMBNAIL_TYPE.VERTICAL:
|
||||
case THUMBNAIL_TYPE.HORIZONTAL: {
|
||||
const {
|
||||
horizontalViewDimensions = {
|
||||
local: {},
|
||||
@@ -1124,7 +1209,7 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
} = state['features/filmstrip'];
|
||||
const _verticalViewGrid = showGridInVerticalView(state);
|
||||
const { local, remote }
|
||||
= _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW
|
||||
= tileType === THUMBNAIL_TYPE.VERTICAL
|
||||
? verticalViewDimensions : horizontalViewDimensions;
|
||||
const { width, height } = (isLocal ? local : remote) ?? {};
|
||||
|
||||
@@ -1146,7 +1231,7 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
|
||||
break;
|
||||
}
|
||||
case LAYOUTS.TILE_VIEW: {
|
||||
case THUMBNAIL_TYPE.TILE: {
|
||||
const { thumbnailSize } = state['features/filmstrip'].tileViewDimensions;
|
||||
const {
|
||||
stageFilmstripDimensions = {
|
||||
@@ -1173,14 +1258,14 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
|
||||
const { gifUrl: gifSrc } = getGifForParticipant(state, id);
|
||||
const mode = getGifDisplayMode(state);
|
||||
const participantId = isLocal ? getLocalParticipant(state).id : participantID;
|
||||
|
||||
return {
|
||||
_audioTrack,
|
||||
_currentLayout,
|
||||
_defaultLocalDisplayName: defaultLocalDisplayName,
|
||||
_disableLocalVideoFlip: Boolean(disableLocalVideoFlip),
|
||||
_disableTileEnlargement: Boolean(disableTileEnlargement),
|
||||
_isActiveParticipant: activeParticipants.find(pId => pId === participantID),
|
||||
_isActiveParticipant: activeParticipants.find(pId => pId === participantId),
|
||||
_isHidden: isLocal && iAmRecorder && !iAmSipGateway,
|
||||
_isAudioOnly: Boolean(state['features/base/audio-only'].enabled),
|
||||
_isCurrentlyOnLargeVideo: state['features/large-video']?.participantId === id,
|
||||
@@ -1194,11 +1279,14 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
_localFlipX: Boolean(localFlipX),
|
||||
_participant: participant,
|
||||
_raisedHand: hasRaisedHand(participant),
|
||||
_stageFilmstripDisabled: !isStageFilmstripEnabled(state),
|
||||
_stageFilmstripLayout: isStageFilmstripAvailable(state),
|
||||
_stageParticipantsVisible: _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW,
|
||||
_thumbnailType: tileType,
|
||||
_videoObjectPosition: getVideoObjectPosition(state, participant?.id),
|
||||
_videoTrack,
|
||||
...size,
|
||||
_gifSrc: mode === 'chat' ? null : gifSrc
|
||||
_gifSrc: mode === 'chat' ? null : gifSrc,
|
||||
_sourceNameSignalingEnabled: sourceNameSignalingEnabled
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useSelector } from 'react-redux';
|
||||
|
||||
import { isDisplayNameVisible, isNameReadOnly } from '../../../base/config/functions.any';
|
||||
import DisplayName from '../../../display-name/components/web/DisplayName';
|
||||
import { LAYOUTS } from '../../../video-layout';
|
||||
import { THUMBNAIL_TYPE } from '../../constants';
|
||||
|
||||
import StatusIndicators from './StatusIndicators';
|
||||
|
||||
@@ -14,11 +14,6 @@ declare var interfaceConfig: Object;
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The current layout of the filmstrip.
|
||||
*/
|
||||
currentLayout: string,
|
||||
|
||||
/**
|
||||
* Class name for indicators container.
|
||||
*/
|
||||
@@ -37,7 +32,12 @@ type Props = {
|
||||
/**
|
||||
* Whether or not to show the status indicators.
|
||||
*/
|
||||
showStatusIndicators: string
|
||||
showStatusIndicators: string,
|
||||
|
||||
/**
|
||||
* The type of thumbnail.
|
||||
*/
|
||||
thumbnailType: string
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => {
|
||||
@@ -61,10 +61,10 @@ const useStyles = makeStyles(() => {
|
||||
|
||||
const ThumbnailBottomIndicators = ({
|
||||
className,
|
||||
currentLayout,
|
||||
local,
|
||||
participantId,
|
||||
showStatusIndicators = true
|
||||
showStatusIndicators = true,
|
||||
thumbnailType
|
||||
}: Props) => {
|
||||
const styles = useStyles();
|
||||
const _allowEditing = !useSelector(isNameReadOnly);
|
||||
@@ -77,17 +77,18 @@ const ThumbnailBottomIndicators = ({
|
||||
audio = { true }
|
||||
moderator = { true }
|
||||
participantID = { participantId }
|
||||
screenshare = { currentLayout === LAYOUTS.TILE_VIEW } />
|
||||
screenshare = { thumbnailType === THUMBNAIL_TYPE.TILE }
|
||||
thumbnailType = { thumbnailType } />
|
||||
}
|
||||
{
|
||||
_showDisplayName && (
|
||||
<span className = { styles.nameContainer }>
|
||||
<DisplayName
|
||||
allowEditing = { local ? _allowEditing : false }
|
||||
currentLayout = { currentLayout }
|
||||
displayNameSuffix = { local ? _defaultLocalDisplayName : '' }
|
||||
elementID = { local ? 'localDisplayName' : `participant_${participantId}_name` }
|
||||
participantID = { participantId } />
|
||||
participantID = { participantId }
|
||||
thumbnailType = { thumbnailType } />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ import { useSelector } from 'react-redux';
|
||||
import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import ConnectionIndicator from '../../../connection-indicator/components/web/ConnectionIndicator';
|
||||
import { LAYOUTS } from '../../../video-layout';
|
||||
import { STATS_POPOVER_POSITION } from '../../constants';
|
||||
import { STATS_POPOVER_POSITION, THUMBNAIL_TYPE } from '../../constants';
|
||||
import { getIndicatorsTooltipPosition } from '../../functions.web';
|
||||
|
||||
import PinnedIndicator from './PinnedIndicator';
|
||||
@@ -21,11 +20,6 @@ declare var interfaceConfig: Object;
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The current layout of the filmstrip.
|
||||
*/
|
||||
currentLayout: string,
|
||||
|
||||
/**
|
||||
* Hide popover callback.
|
||||
*/
|
||||
@@ -64,7 +58,12 @@ type Props = {
|
||||
/**
|
||||
* Show popover callback.
|
||||
*/
|
||||
showPopover: Function
|
||||
showPopover: Function,
|
||||
|
||||
/**
|
||||
* The type of thumbnail.
|
||||
*/
|
||||
thumbnailType: string
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => {
|
||||
@@ -80,7 +79,6 @@ const useStyles = makeStyles(() => {
|
||||
});
|
||||
|
||||
const ThumbnailTopIndicators = ({
|
||||
currentLayout,
|
||||
hidePopover,
|
||||
indicatorsClassName,
|
||||
isFakeScreenShareParticipant,
|
||||
@@ -88,7 +86,8 @@ const ThumbnailTopIndicators = ({
|
||||
local,
|
||||
participantId,
|
||||
popoverVisible,
|
||||
showPopover
|
||||
showPopover,
|
||||
thumbnailType
|
||||
}: Props) => {
|
||||
const styles = useStyles();
|
||||
|
||||
@@ -111,32 +110,34 @@ const ThumbnailTopIndicators = ({
|
||||
enableStatsDisplay = { true }
|
||||
iconSize = { _indicatorIconSize }
|
||||
participantId = { participantId }
|
||||
statsPopoverPosition = { STATS_POPOVER_POSITION[currentLayout] } />
|
||||
statsPopoverPosition = { STATS_POPOVER_POSITION[thumbnailType] } />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const tooltipPosition = getIndicatorsTooltipPosition(thumbnailType);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className = { styles.container }>
|
||||
<PinnedIndicator
|
||||
iconSize = { _indicatorIconSize }
|
||||
participantId = { participantId }
|
||||
tooltipPosition = { getIndicatorsTooltipPosition(currentLayout) } />
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
{!_connectionIndicatorDisabled
|
||||
&& <ConnectionIndicator
|
||||
alwaysVisible = { showConnectionIndicator }
|
||||
enableStatsDisplay = { true }
|
||||
iconSize = { _indicatorIconSize }
|
||||
participantId = { participantId }
|
||||
statsPopoverPosition = { STATS_POPOVER_POSITION[currentLayout] } />
|
||||
statsPopoverPosition = { STATS_POPOVER_POSITION[thumbnailType] } />
|
||||
}
|
||||
<RaisedHandIndicator
|
||||
iconSize = { _indicatorIconSize }
|
||||
participantId = { participantId }
|
||||
tooltipPosition = { getIndicatorsTooltipPosition(currentLayout) } />
|
||||
{currentLayout !== LAYOUTS.TILE_VIEW && (
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
{thumbnailType !== THUMBNAIL_TYPE.TILE && (
|
||||
<div className = { clsx(indicatorsClassName, 'top-indicators') }>
|
||||
<StatusIndicators
|
||||
participantID = { participantId }
|
||||
@@ -146,12 +147,12 @@ const ThumbnailTopIndicators = ({
|
||||
</div>
|
||||
<div className = { styles.container }>
|
||||
<VideoMenuTriggerButton
|
||||
currentLayout = { currentLayout }
|
||||
hidePopover = { hidePopover }
|
||||
local = { local }
|
||||
participantId = { participantId }
|
||||
popoverVisible = { popoverVisible }
|
||||
showPopover = { showPopover }
|
||||
thumbnailType = { thumbnailType }
|
||||
visible = { isHovered } />
|
||||
</div>
|
||||
</>);
|
||||
|
||||
@@ -148,7 +148,8 @@ function _mapStateToProps(state, ownProps) {
|
||||
const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
|
||||
const _verticalViewGrid = showGridInVerticalView(state);
|
||||
const stageFilmstrip = ownProps.data?.stageFilmstrip;
|
||||
const remoteParticipants = stageFilmstrip ? activeParticipants : remote;
|
||||
const sortedActiveParticipants = activeParticipants.sort();
|
||||
const remoteParticipants = stageFilmstrip ? sortedActiveParticipants : remote;
|
||||
const remoteParticipantsLength = remoteParticipants.length;
|
||||
const localId = getLocalParticipant(state).id;
|
||||
|
||||
|
||||
@@ -6,11 +6,6 @@ import { LocalVideoMenuTriggerButton, RemoteVideoMenuTriggerButton } from '../..
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The current layout of the filmstrip.
|
||||
*/
|
||||
currentLayout: string,
|
||||
|
||||
/**
|
||||
* Hide popover callback.
|
||||
*/
|
||||
@@ -36,6 +31,11 @@ type Props = {
|
||||
*/
|
||||
showPopover: Function,
|
||||
|
||||
/**
|
||||
* The type of thumbnail.
|
||||
*/
|
||||
thumbnailType: string,
|
||||
|
||||
/**
|
||||
* Whether or not the component is visible.
|
||||
*/
|
||||
@@ -44,33 +44,33 @@ type Props = {
|
||||
|
||||
// eslint-disable-next-line no-confusing-arrow
|
||||
const VideoMenuTriggerButton = ({
|
||||
currentLayout,
|
||||
hidePopover,
|
||||
local,
|
||||
participantId,
|
||||
popoverVisible,
|
||||
showPopover,
|
||||
thumbnailType,
|
||||
visible
|
||||
}: Props) => local
|
||||
? (
|
||||
<span id = 'localvideomenu'>
|
||||
<LocalVideoMenuTriggerButton
|
||||
buttonVisible = { visible }
|
||||
currentLayout = { currentLayout }
|
||||
hidePopover = { hidePopover }
|
||||
popoverVisible = { popoverVisible }
|
||||
showPopover = { showPopover } />
|
||||
showPopover = { showPopover }
|
||||
thumbnailType = { thumbnailType } />
|
||||
</span>
|
||||
)
|
||||
: (
|
||||
<span id = 'remotevideomenu'>
|
||||
<RemoteVideoMenuTriggerButton
|
||||
buttonVisible = { visible }
|
||||
currentLayout = { currentLayout }
|
||||
hidePopover = { hidePopover }
|
||||
participantID = { participantId }
|
||||
popoverVisible = { popoverVisible }
|
||||
showPopover = { showPopover } />
|
||||
showPopover = { showPopover }
|
||||
thumbnailType = { thumbnailType } />
|
||||
</span>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { BoxModel } from '../base/styles';
|
||||
import { LAYOUTS } from '../video-layout/constants';
|
||||
|
||||
/**
|
||||
* The size (height and width) of the small (not tile view) thumbnails.
|
||||
@@ -228,22 +227,31 @@ export const SHOW_TOOLBAR_CONTEXT_MENU_AFTER = 600;
|
||||
*/
|
||||
export const TILE_MARGIN = 10;
|
||||
|
||||
/**
|
||||
* The types of thumbnails for filmstrip.
|
||||
*/
|
||||
export const THUMBNAIL_TYPE = {
|
||||
TILE: 'TILE',
|
||||
VERTICAL: 'VERTICAL',
|
||||
HORIZONTAL: 'HORIZONTAL'
|
||||
};
|
||||
|
||||
/**
|
||||
* The popover position for the connection stats table.
|
||||
*/
|
||||
export const STATS_POPOVER_POSITION = {
|
||||
[LAYOUTS.TILE_VIEW]: 'right-start',
|
||||
[LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'left-start',
|
||||
[LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'top-end'
|
||||
[THUMBNAIL_TYPE.TILE]: 'right-start',
|
||||
[THUMBNAIL_TYPE.VERTICAL]: 'left-start',
|
||||
[THUMBNAIL_TYPE.HORIZONTAL]: 'top-end'
|
||||
};
|
||||
|
||||
/**
|
||||
* The tooltip position for the indicators on the thumbnail.
|
||||
*/
|
||||
export const INDICATORS_TOOLTIP_POSITION = {
|
||||
[LAYOUTS.TILE_VIEW]: 'right',
|
||||
[LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'left',
|
||||
[LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'top'
|
||||
[THUMBNAIL_TYPE.TILE]: 'right',
|
||||
[THUMBNAIL_TYPE.VERTICAL]: 'left',
|
||||
[THUMBNAIL_TYPE.HORIZONTAL]: 'top'
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -103,11 +103,20 @@ export function isReorderingEnabled(state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the stage filmstrip is disabled or not.
|
||||
* Whether the stage filmstrip is available or not.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isStageFilmstripAvailable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the stage filmstrip is enabled.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isStageFilmstripEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
INDICATORS_TOOLTIP_POSITION,
|
||||
SCROLL_SIZE,
|
||||
SQUARE_TILE_ASPECT_RATIO,
|
||||
THUMBNAIL_TYPE,
|
||||
TILE_ASPECT_RATIO,
|
||||
TILE_HORIZONTAL_MARGIN,
|
||||
TILE_MIN_HEIGHT_LARGE,
|
||||
@@ -243,18 +244,16 @@ export function getNumberOfPartipantsForTileView(state) {
|
||||
* disabled.
|
||||
*
|
||||
* @param {Object} state - The redux store state.
|
||||
* @param {boolean} stageFilmstrip - Whether the dimensions should be calculated for the stage filmstrip.
|
||||
* @returns {Object} - The dimensions.
|
||||
*/
|
||||
export function calculateNonResponsiveTileViewDimensions(state, stageFilmstrip = false) {
|
||||
export function calculateNonResponsiveTileViewDimensions(state) {
|
||||
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
|
||||
const { disableTileEnlargement } = state['features/base/config'];
|
||||
const { columns: c, minVisibleRows, rows: r } = getNotResponsiveTileViewGridDimensions(state, stageFilmstrip);
|
||||
const filmstripWidth = getVerticalViewMaxWidth(state);
|
||||
const { columns: c, minVisibleRows, rows: r } = getNotResponsiveTileViewGridDimensions(state);
|
||||
const size = calculateThumbnailSizeForTileView({
|
||||
columns: c,
|
||||
minVisibleRows,
|
||||
clientWidth: clientWidth - (stageFilmstrip ? filmstripWidth : 0),
|
||||
clientWidth,
|
||||
clientHeight,
|
||||
disableTileEnlargement,
|
||||
disableResponsiveTiles: true
|
||||
@@ -297,7 +296,7 @@ export function calculateResponsiveTileViewDimensions({
|
||||
noHorizontalContainerMargin = false,
|
||||
maxColumns,
|
||||
numberOfParticipants,
|
||||
numberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES
|
||||
desiredNumberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES
|
||||
}) {
|
||||
let height, width;
|
||||
let columns, rows;
|
||||
@@ -311,12 +310,12 @@ export function calculateResponsiveTileViewDimensions({
|
||||
maxArea: 0
|
||||
};
|
||||
|
||||
for (let c = 1; c <= Math.min(maxColumns, numberOfParticipants); c++) {
|
||||
for (let c = 1; c <= Math.min(maxColumns, numberOfParticipants, desiredNumberOfVisibleTiles); c++) {
|
||||
const r = Math.ceil(numberOfParticipants / c);
|
||||
|
||||
// we want to display as much as possible tumbnails up to numberOfVisibleTiles
|
||||
// we want to display as much as possible tumbnails up to desiredNumberOfVisibleTiles
|
||||
const visibleRows
|
||||
= numberOfParticipants <= numberOfVisibleTiles ? r : Math.floor(numberOfVisibleTiles / c);
|
||||
= numberOfParticipants <= desiredNumberOfVisibleTiles ? r : Math.floor(desiredNumberOfVisibleTiles / c);
|
||||
|
||||
const size = calculateThumbnailSizeForTileView({
|
||||
columns: c,
|
||||
@@ -330,18 +329,38 @@ export function calculateResponsiveTileViewDimensions({
|
||||
|
||||
if (size) {
|
||||
const { height: currentHeight, width: currentWidth, minHeightEnforced, maxVisibleRows } = size;
|
||||
let area = currentHeight * currentWidth * Math.min(c * maxVisibleRows, numberOfParticipants);
|
||||
const numberOfVisibleParticipants = Math.min(c * maxVisibleRows, numberOfParticipants);
|
||||
|
||||
let area = Math.round(
|
||||
(currentHeight + TILE_VERTICAL_MARGIN)
|
||||
* (currentWidth + TILE_HORIZONTAL_MARGIN)
|
||||
* numberOfVisibleParticipants);
|
||||
|
||||
const currentDimensions = {
|
||||
maxArea: area,
|
||||
height: currentHeight,
|
||||
width: currentWidth,
|
||||
columns: c,
|
||||
rows: r
|
||||
rows: r,
|
||||
numberOfVisibleParticipants
|
||||
};
|
||||
const { numberOfVisibleParticipants: oldNumberOfVisibleParticipants = 0 } = dimensions;
|
||||
|
||||
if (!minHeightEnforced && area > dimensions.maxArea) {
|
||||
dimensions = currentDimensions;
|
||||
} else if (minHeightEnforced && area > minHeightEnforcedDimensions.maxArea) {
|
||||
if (!minHeightEnforced) {
|
||||
if (area > dimensions.maxArea) {
|
||||
dimensions = currentDimensions;
|
||||
} else if ((area === dimensions.maxArea)
|
||||
&& ((oldNumberOfVisibleParticipants > desiredNumberOfVisibleTiles
|
||||
&& oldNumberOfVisibleParticipants >= numberOfParticipants)
|
||||
|| (oldNumberOfVisibleParticipants < numberOfParticipants
|
||||
&& numberOfVisibleParticipants <= desiredNumberOfVisibleTiles))
|
||||
) { // If the area of the new candidates and the old ones are equal we preffer the one that will have
|
||||
// closer number of visible participants to desiredNumberOfVisibleTiles config.
|
||||
dimensions = currentDimensions;
|
||||
}
|
||||
} else if (minHeightEnforced && area >= minHeightEnforcedDimensions.maxArea) {
|
||||
// If we choose configuration with minHeightEnforced there will be less than desiredNumberOfVisibleTiles
|
||||
// visible tiles, that's why we prefer more columns when the area is the same.
|
||||
minHeightEnforcedDimensions = currentDimensions;
|
||||
} else if (minHeightEnforced && maxVisibleRows === 0) {
|
||||
area = currentHeight * currentWidth * Math.min(c, numberOfParticipants);
|
||||
@@ -400,7 +419,8 @@ export function calculateThumbnailSizeForTileView({
|
||||
const minHeight = getThumbnailMinHeight(clientWidth);
|
||||
const viewWidth = clientWidth - (columns * TILE_HORIZONTAL_MARGIN)
|
||||
- (noHorizontalContainerMargin ? SCROLL_SIZE : TILE_VIEW_GRID_HORIZONTAL_MARGIN);
|
||||
const viewHeight = clientHeight - (minVisibleRows * TILE_VERTICAL_MARGIN) - TILE_VIEW_GRID_VERTICAL_MARGIN;
|
||||
const availableHeight = clientHeight - TILE_VIEW_GRID_VERTICAL_MARGIN;
|
||||
const viewHeight = availableHeight - (minVisibleRows * TILE_VERTICAL_MARGIN);
|
||||
const initialWidth = viewWidth / columns;
|
||||
let initialHeight = viewHeight / minVisibleRows;
|
||||
let minHeightEnforced = false;
|
||||
@@ -417,52 +437,47 @@ export function calculateThumbnailSizeForTileView({
|
||||
return;
|
||||
}
|
||||
|
||||
const height = Math.floor(Math.min(aspectRatioHeight, initialHeight));
|
||||
const height = Math.min(aspectRatioHeight, initialHeight);
|
||||
|
||||
return {
|
||||
height,
|
||||
width: Math.floor(aspectRatio * height),
|
||||
width: aspectRatio * height,
|
||||
minHeightEnforced,
|
||||
maxVisibleRows: Math.floor(viewHeight / height)
|
||||
maxVisibleRows: Math.floor(availableHeight / (height + TILE_VERTICAL_MARGIN))
|
||||
};
|
||||
}
|
||||
|
||||
const initialRatio = initialWidth / initialHeight;
|
||||
let height = Math.floor(initialHeight);
|
||||
let height = initialHeight;
|
||||
let width;
|
||||
|
||||
// The biggest area of the grid will be when the grid's height is equal to clientHeight or when the grid's width is
|
||||
// equal to clientWidth.
|
||||
|
||||
if (initialRatio > aspectRatio) {
|
||||
return {
|
||||
height,
|
||||
width: Math.floor(initialHeight * aspectRatio),
|
||||
minHeightEnforced,
|
||||
maxVisibleRows: Math.floor(viewHeight / height)
|
||||
};
|
||||
width = initialHeight * aspectRatio;
|
||||
} else if (initialRatio >= TILE_PORTRAIT_ASPECT_RATIO) {
|
||||
return {
|
||||
height,
|
||||
width: Math.floor(initialWidth),
|
||||
minHeightEnforced,
|
||||
maxVisibleRows: Math.floor(viewHeight / height)
|
||||
};
|
||||
width = initialWidth;
|
||||
// eslint-disable-next-line no-negated-condition
|
||||
} else if (!minHeightEnforced) {
|
||||
height = Math.floor(initialWidth / TILE_PORTRAIT_ASPECT_RATIO);
|
||||
height = initialWidth / TILE_PORTRAIT_ASPECT_RATIO;
|
||||
|
||||
if (height >= minHeight) {
|
||||
return {
|
||||
height,
|
||||
width: Math.floor(initialWidth),
|
||||
minHeightEnforced,
|
||||
maxVisibleRows: Math.floor(viewHeight / height)
|
||||
};
|
||||
width = initialWidth;
|
||||
} else { // The width is so small that we can't reach the minimum height with portrait aspect ratio.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// We can't fit that number of columns with the desired min height and aspect ratio.
|
||||
return;
|
||||
}
|
||||
|
||||
// else
|
||||
// We can't fit that number of columns with the desired min height and aspect ratio.
|
||||
return;
|
||||
return {
|
||||
height,
|
||||
width,
|
||||
minHeightEnforced,
|
||||
maxVisibleRows: Math.floor(availableHeight / (height + TILE_VERTICAL_MARGIN))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -498,6 +513,8 @@ export function computeDisplayModeFromInput(input: Object) {
|
||||
isScreenSharing,
|
||||
canPlayEventReceived,
|
||||
isRemoteParticipant,
|
||||
stageParticipantsVisible,
|
||||
stageFilmstrip,
|
||||
tileViewActive
|
||||
} = input;
|
||||
const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived);
|
||||
@@ -506,7 +523,8 @@ export function computeDisplayModeFromInput(input: Object) {
|
||||
return DISPLAY_VIDEO;
|
||||
}
|
||||
|
||||
if (!tileViewActive && ((isScreenSharing && isRemoteParticipant) || isActiveParticipant)) {
|
||||
if (!tileViewActive && ((isScreenSharing && isRemoteParticipant)
|
||||
|| (stageParticipantsVisible && isActiveParticipant && !stageFilmstrip))) {
|
||||
return DISPLAY_AVATAR;
|
||||
} else if (isCurrentlyOnLargeVideo && !tileViewActive) {
|
||||
// Display name is always and only displayed when user is on the stage
|
||||
@@ -537,7 +555,9 @@ export function getDisplayModeInput(props: Object, state: Object) {
|
||||
_isScreenSharing,
|
||||
_isVideoPlayable,
|
||||
_participant,
|
||||
_videoTrack
|
||||
_stageParticipantsVisible,
|
||||
_videoTrack,
|
||||
stageFilmstrip
|
||||
} = props;
|
||||
const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
|
||||
const { canPlayEventReceived } = state;
|
||||
@@ -554,6 +574,8 @@ export function getDisplayModeInput(props: Object, state: Object) {
|
||||
isRemoteParticipant: !_participant?.isFakeParticipant && !_participant?.local,
|
||||
isScreenSharing: _isScreenSharing,
|
||||
isFakeScreenShareParticipant: _isFakeScreenShareParticipant,
|
||||
stageParticipantsVisible: _stageParticipantsVisible,
|
||||
stageFilmstrip,
|
||||
videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream'
|
||||
};
|
||||
}
|
||||
@@ -561,11 +583,11 @@ export function getDisplayModeInput(props: Object, state: Object) {
|
||||
/**
|
||||
* Gets the tooltip position for the thumbnail indicators.
|
||||
*
|
||||
* @param {string} currentLayout - The current layout of the app.
|
||||
* @param {string} thumbnailType - The current thumbnail type.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getIndicatorsTooltipPosition(currentLayout: string) {
|
||||
return INDICATORS_TOOLTIP_POSITION[currentLayout] || 'top';
|
||||
export function getIndicatorsTooltipPosition(thumbnailType: string) {
|
||||
return INDICATORS_TOOLTIP_POSITION[thumbnailType] || 'top';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -579,7 +601,7 @@ export function isFilmstripResizable(state: Object) {
|
||||
const _currentLayout = getCurrentLayout(state);
|
||||
|
||||
return !filmstrip?.disableResizable && !isMobileBrowser()
|
||||
&& _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW;
|
||||
&& (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW || _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -645,7 +667,8 @@ export function isFilmstripScrollVisible(state) {
|
||||
case LAYOUTS.TILE_VIEW:
|
||||
({ hasScroll = false } = state['features/filmstrip'].tileViewDimensions);
|
||||
break;
|
||||
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: {
|
||||
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
|
||||
case LAYOUTS.STAGE_FILMSTRIP_VIEW: {
|
||||
({ hasScroll = false } = state['features/filmstrip'].verticalViewDimensions);
|
||||
break;
|
||||
}
|
||||
@@ -674,7 +697,7 @@ export function getActiveParticipantsIds(state) {
|
||||
* Gets the ids of the active participants.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {Array<string>}
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
export function getPinnedActiveParticipants(state) {
|
||||
const { activeParticipants } = state['features/filmstrip'];
|
||||
@@ -683,19 +706,20 @@ export function getPinnedActiveParticipants(state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not the stage filmstrip should be displayed.
|
||||
* Get whether or not the stage filmstrip is available (enabled & can be used).
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @param {number} minParticipantCount - The min number of participants for the stage filmstrip
|
||||
* to be displayed.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldDisplayStageFilmstrip(state) {
|
||||
export function isStageFilmstripAvailable(state, minParticipantCount = 0) {
|
||||
const { activeParticipants } = state['features/filmstrip'];
|
||||
const { remoteScreenShares } = state['features/video-layout'];
|
||||
const currentLayout = getCurrentLayout(state);
|
||||
const sharedVideo = isSharingStatus(state['features/shared-video']?.status);
|
||||
|
||||
return isStageFilmstripEnabled(state) && remoteScreenShares.length === 0 && !sharedVideo
|
||||
&& activeParticipants.length > 1 && currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW;
|
||||
&& activeParticipants.length >= minParticipantCount;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -707,5 +731,29 @@ export function shouldDisplayStageFilmstrip(state) {
|
||||
export function isStageFilmstripEnabled(state) {
|
||||
const { filmstrip } = state['features/base/config'];
|
||||
|
||||
return !filmstrip?.disableStageFilmstrip && interfaceConfig.VERTICAL_FILMSTRIP;
|
||||
return !(filmstrip?.disableStageFilmstrip ?? true) && interfaceConfig.VERTICAL_FILMSTRIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the thumbnail type by filmstrip type.
|
||||
*
|
||||
* @param {string} currentLayout - Current app layout.
|
||||
* @param {boolean} isStageFilmstrip - Whether the filmstrip is stage filmstrip or not.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getThumbnailTypeFromLayout(currentLayout, isStageFilmstrip = false) {
|
||||
switch (currentLayout) {
|
||||
case LAYOUTS.TILE_VIEW:
|
||||
return THUMBNAIL_TYPE.TILE;
|
||||
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
|
||||
return THUMBNAIL_TYPE.VERTICAL;
|
||||
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
|
||||
return THUMBNAIL_TYPE.HORIZONTAL;
|
||||
case LAYOUTS.STAGE_FILMSTRIP_VIEW:
|
||||
if (isStageFilmstrip) {
|
||||
return THUMBNAIL_TYPE.TILE;
|
||||
}
|
||||
|
||||
return THUMBNAIL_TYPE.VERTICAL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,11 @@ import {
|
||||
|
||||
import {
|
||||
ADD_STAGE_PARTICIPANT,
|
||||
CLEAR_STAGE_PARTICIPANTS,
|
||||
REMOVE_STAGE_PARTICIPANT,
|
||||
SET_MAX_STAGE_PARTICIPANTS,
|
||||
SET_USER_FILMSTRIP_WIDTH
|
||||
SET_USER_FILMSTRIP_WIDTH,
|
||||
TOGGLE_PIN_STAGE_PARTICIPANT
|
||||
} from './actionTypes';
|
||||
import {
|
||||
addStageParticipant,
|
||||
@@ -41,10 +43,12 @@ import {
|
||||
import {
|
||||
isFilmstripResizable,
|
||||
updateRemoteParticipants,
|
||||
updateRemoteParticipantsOnLeave
|
||||
updateRemoteParticipantsOnLeave,
|
||||
getActiveParticipantsIds,
|
||||
getPinnedActiveParticipants,
|
||||
isStageFilmstripAvailable
|
||||
} from './functions';
|
||||
import './subscriber';
|
||||
import { getActiveParticipantsIds, getPinnedActiveParticipants, isStageFilmstripEnabled } from './functions.web';
|
||||
|
||||
/**
|
||||
* Map of timers.
|
||||
@@ -141,6 +145,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const tid = timers.get(participantId);
|
||||
|
||||
clearTimeout(tid);
|
||||
timers.delete(participantId);
|
||||
} else if (activeParticipants.length < maxStageParticipants) {
|
||||
queue = [ ...activeParticipants, {
|
||||
participantId,
|
||||
@@ -200,15 +205,15 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case DOMINANT_SPEAKER_CHANGED: {
|
||||
const { id } = action.participant;
|
||||
const state = store.getState();
|
||||
const stageFilmstrip = isStageFilmstripEnabled(state);
|
||||
const currentLayout = getCurrentLayout(state);
|
||||
const stageFilmstrip = isStageFilmstripAvailable(state);
|
||||
const local = getLocalParticipant(state);
|
||||
const currentLayout = getCurrentLayout(state);
|
||||
|
||||
if (id === local.id) {
|
||||
if (id === local.id || currentLayout === LAYOUTS.TILE_VIEW) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (stageFilmstrip && currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
|
||||
if (stageFilmstrip) {
|
||||
const isPinned = getPinnedActiveParticipants(state).some(p => p.participantId === id);
|
||||
|
||||
store.dispatch(addStageParticipant(id, Boolean(isPinned)));
|
||||
@@ -216,11 +221,17 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
case PARTICIPANT_LEFT: {
|
||||
const state = store.getState();
|
||||
const { id } = action.participant;
|
||||
const activeParticipantsIds = getActiveParticipantsIds(store.getState());
|
||||
const activeParticipantsIds = getActiveParticipantsIds(state);
|
||||
|
||||
if (activeParticipantsIds.find(pId => pId === id)) {
|
||||
store.dispatch(removeStageParticipant(id));
|
||||
const tid = timers.get(id);
|
||||
const { activeParticipants } = state['features/filmstrip'];
|
||||
|
||||
clearTimeout(tid);
|
||||
timers.delete(id);
|
||||
store.dispatch(setStageParticipants(activeParticipants.filter(p => p.participantId !== id)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -240,6 +251,46 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TOGGLE_PIN_STAGE_PARTICIPANT: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const { participantId } = action;
|
||||
const pinnedParticipants = getPinnedActiveParticipants(state);
|
||||
const dominant = getDominantSpeakerParticipant(state);
|
||||
|
||||
if (pinnedParticipants.find(p => p.participantId === participantId)) {
|
||||
if (dominant?.id === participantId) {
|
||||
const { activeParticipants } = state['features/filmstrip'];
|
||||
const queue = activeParticipants.map(p => {
|
||||
if (p.participantId === participantId) {
|
||||
return {
|
||||
participantId,
|
||||
pinned: false
|
||||
};
|
||||
}
|
||||
|
||||
return p;
|
||||
});
|
||||
|
||||
dispatch(setStageParticipants(queue));
|
||||
} else {
|
||||
dispatch(removeStageParticipant(participantId));
|
||||
}
|
||||
} else {
|
||||
dispatch(addStageParticipant(participantId, true));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLEAR_STAGE_PARTICIPANTS: {
|
||||
const activeParticipants = getActiveParticipantsIds(store.getState());
|
||||
|
||||
activeParticipants.forEach(pId => {
|
||||
const tid = timers.get(pId);
|
||||
|
||||
clearTimeout(tid);
|
||||
timers.delete(pId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result ?? next(action);
|
||||
|
||||
@@ -18,7 +18,8 @@ import {
|
||||
SET_VERTICAL_VIEW_DIMENSIONS,
|
||||
SET_VISIBLE_REMOTE_PARTICIPANTS,
|
||||
SET_VOLUME,
|
||||
SET_MAX_STAGE_PARTICIPANTS
|
||||
SET_MAX_STAGE_PARTICIPANTS,
|
||||
CLEAR_STAGE_PARTICIPANTS
|
||||
} from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
@@ -273,6 +274,12 @@ ReducerRegistry.register(
|
||||
maxStageParticipants: action.maxParticipants
|
||||
};
|
||||
}
|
||||
case CLEAR_STAGE_PARTICIPANTS: {
|
||||
return {
|
||||
...state,
|
||||
activeParticipants: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
@@ -12,6 +12,13 @@ StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/video-layout'].remoteScreenShares,
|
||||
/* listener */ (remoteScreenShares, store) => updateRemoteParticipants(store));
|
||||
|
||||
/**
|
||||
* Listens for changes to the remote screenshare participants to recompute the reordered list of the remote endpoints.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/participants'].sortedRemoteFakeScreenShareParticipants,
|
||||
/* listener */ (sortedRemoteFakeScreenShareParticipants, store) => updateRemoteParticipants(store));
|
||||
|
||||
/**
|
||||
* Listens for changes to the dominant speaker to recompute the reordered list of the remote endpoints.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { isMobileBrowser } from '../base/environment/utils';
|
||||
import { getParticipantCountWithFake } from '../base/participants';
|
||||
import { getParticipantCountWithFake, pinParticipant } from '../base/participants';
|
||||
import { StateListenerRegistry } from '../base/redux';
|
||||
import { clientResized } from '../base/responsive-ui';
|
||||
import { shouldHideSelfView } from '../base/settings';
|
||||
@@ -12,6 +12,7 @@ import { setOverflowDrawer } from '../toolbox/actions.web';
|
||||
import { getCurrentLayout, shouldDisplayTileView, LAYOUTS } from '../video-layout';
|
||||
|
||||
import {
|
||||
clearStageParticipants,
|
||||
setHorizontalViewDimensions,
|
||||
setStageFilmstripViewDimensions,
|
||||
setTileViewDimensions,
|
||||
@@ -24,7 +25,6 @@ import {
|
||||
import {
|
||||
isFilmstripResizable,
|
||||
isFilmstripScrollVisible,
|
||||
shouldDisplayStageFilmstrip,
|
||||
updateRemoteParticipants
|
||||
} from './functions';
|
||||
|
||||
@@ -61,8 +61,13 @@ StateListenerRegistry.register(
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => {
|
||||
return { layout: getCurrentLayout(state),
|
||||
width: state['features/base/responsive-ui'].clientWidth };
|
||||
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
|
||||
|
||||
return {
|
||||
layout: getCurrentLayout(state),
|
||||
height: clientHeight,
|
||||
width: clientWidth
|
||||
};
|
||||
},
|
||||
/* listener */ ({ layout }, store) => {
|
||||
switch (layout) {
|
||||
@@ -74,6 +79,12 @@ StateListenerRegistry.register(
|
||||
break;
|
||||
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
|
||||
store.dispatch(setVerticalViewDimensions());
|
||||
if (store.getState()['features/filmstrip'].activeParticipants.length > 1) {
|
||||
store.dispatch(clearStageParticipants());
|
||||
}
|
||||
break;
|
||||
case LAYOUTS.STAGE_FILMSTRIP_VIEW:
|
||||
store.dispatch(pinParticipant(null));
|
||||
break;
|
||||
}
|
||||
}, {
|
||||
@@ -177,7 +188,7 @@ StateListenerRegistry.register(
|
||||
};
|
||||
},
|
||||
/* listener */(_, store) => {
|
||||
if (shouldDisplayStageFilmstrip(store.getState())) {
|
||||
if (getCurrentLayout(store.getState()) === LAYOUTS.STAGE_FILMSTRIP_VIEW) {
|
||||
store.dispatch(setStageFilmstripViewDimensions());
|
||||
}
|
||||
}, {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
getPinnedParticipant,
|
||||
getRemoteParticipants
|
||||
} from '../base/participants';
|
||||
import { isStageFilmstripEnabled } from '../filmstrip/functions';
|
||||
import { isStageFilmstripAvailable } from '../filmstrip/functions';
|
||||
|
||||
import {
|
||||
SELECT_LARGE_VIDEO_PARTICIPANT,
|
||||
@@ -30,6 +30,10 @@ export function selectParticipantInLargeVideo(participant: ?string) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
|
||||
if (isStageFilmstripAvailable(state, 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep Etherpad open.
|
||||
if (state['features/etherpad'].editing) {
|
||||
return;
|
||||
@@ -103,7 +107,7 @@ function _electLastVisibleRemoteVideo(tracks) {
|
||||
* @returns {(string|undefined)}
|
||||
*/
|
||||
function _electParticipantInLargeVideo(state) {
|
||||
const stageFilmstrip = isStageFilmstripEnabled(state);
|
||||
const stageFilmstrip = isStageFilmstripAvailable(state);
|
||||
let participant;
|
||||
|
||||
if (!stageFilmstrip) {
|
||||
|
||||
42
react/features/lobby/components/native/LobbyChatScreen.js
Normal file
42
react/features/lobby/components/native/LobbyChatScreen.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { connect } from '../../../base/redux';
|
||||
import ChatInputBar from '../../../chat/components/native/ChatInputBar';
|
||||
import MessageContainer from '../../../chat/components/native/MessageContainer';
|
||||
import AbstractLobbyScreen, {
|
||||
Props as AbstractProps,
|
||||
_mapStateToProps as abstractMapStateToProps
|
||||
} from '../AbstractLobbyScreen';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* Implements a chat screen that appears when communication is started
|
||||
* between the moderator and the participant being in the lobby.
|
||||
*/
|
||||
class LobbyChatScreen extends
|
||||
AbstractLobbyScreen<AbstractProps> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _lobbyChatMessages } = this.props;
|
||||
|
||||
return (
|
||||
<JitsiScreen style = { styles.lobbyChatWrapper }>
|
||||
<MessageContainer messages = { _lobbyChatMessages } />
|
||||
<ChatInputBar onSend = { this._onSendMessage } />
|
||||
</JitsiScreen>
|
||||
);
|
||||
}
|
||||
|
||||
_onSendMessage: () => void;
|
||||
}
|
||||
|
||||
export default translate(connect(abstractMapStateToProps)(LobbyChatScreen));
|
||||
@@ -2,50 +2,50 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Text, View, TouchableOpacity, TextInput } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconClose, IconEdit } from '../../../base/icons';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { LoadingIndicator } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import ChatInputBar from '../../../chat/components/native/ChatInputBar';
|
||||
import MessageContainer from '../../../chat/components/native/MessageContainer';
|
||||
import AbstractLobbyScreen, { _mapStateToProps } from '../AbstractLobbyScreen';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme';
|
||||
import InviteButton
|
||||
from '../../../invite/components/add-people-dialog/native/InviteButton';
|
||||
import { LargeVideo } from '../../../large-video/components';
|
||||
import { navigate }
|
||||
from '../../../mobile/navigation/components/lobby/LobbyNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import AudioMuteButton from '../../../toolbox/components/AudioMuteButton';
|
||||
import VideoMuteButton from '../../../toolbox/components/VideoMuteButton';
|
||||
import AbstractLobbyScreen, {
|
||||
Props as AbstractProps,
|
||||
_mapStateToProps as abstractMapStateToProps } from '../AbstractLobbyScreen';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The current aspect ratio of the screen.
|
||||
*/
|
||||
_aspectRatio: Symbol
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a waiting screen that represents the participant being in the lobby.
|
||||
*/
|
||||
class LobbyScreen extends AbstractLobbyScreen {
|
||||
class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _meetingName, t } = this.props;
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
style = { this.props._isLobbyChatActive && this.state.isChatOpen
|
||||
? styles.lobbyChatWrapper
|
||||
: styles.contentWrapper }>
|
||||
{this.props._isLobbyChatActive && this.state.isChatOpen
|
||||
? this._renderLobbyChat()
|
||||
: <SafeAreaView>
|
||||
<Text style = { styles.dialogTitle }>
|
||||
{ t(this._getScreenTitleKey(), { moderator: this.props._lobbyMessageRecipient }) }
|
||||
</Text>
|
||||
|
||||
<Text style = { styles.secondaryText }>
|
||||
{ _meetingName }
|
||||
</Text>
|
||||
{ this._renderContent()}
|
||||
</SafeAreaView> }
|
||||
</JitsiScreen>
|
||||
<>
|
||||
{ this._renderLobby() }
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,35 +69,58 @@ class LobbyScreen extends AbstractLobbyScreen {
|
||||
|
||||
_onSwitchToPasswordMode: () => void;
|
||||
|
||||
_onSendMessage: () => void;
|
||||
|
||||
_onToggleChat: () => void;
|
||||
|
||||
_renderContent: () => React$Element<*>;
|
||||
|
||||
_renderToolbarButtons: () => React$Element<*>;
|
||||
|
||||
_renderLobby: () => React$Element<*>;
|
||||
|
||||
_onNavigateToLobbyChat: () => void;
|
||||
|
||||
/**
|
||||
* Renders the lobby chat.
|
||||
* Navigates to the lobby chat screen.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onNavigateToLobbyChat() {
|
||||
navigate(screen.lobby.chat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the lobby.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderLobbyChat() {
|
||||
const { t } = this.props;
|
||||
_renderLobby() {
|
||||
const { _aspectRatio } = this.props;
|
||||
let contentStyles;
|
||||
let largeVideoContainerStyles;
|
||||
let contentContainerStyles;
|
||||
|
||||
if (_aspectRatio === ASPECT_RATIO_NARROW) {
|
||||
largeVideoContainerStyles = styles.largeVideoContainer;
|
||||
contentContainerStyles = styles.contentContainer;
|
||||
} else {
|
||||
contentStyles = styles.contentWide;
|
||||
largeVideoContainerStyles = styles.largeVideoContainerWide;
|
||||
contentContainerStyles = styles.contentContainerWide;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style = { styles.lobbyChatHeader }>
|
||||
<Text style = { styles.lobbyChatTitle }>
|
||||
{ t(this._getScreenTitleKey(), { moderator: this.props._lobbyMessageRecipient }) }
|
||||
</Text>
|
||||
<TouchableOpacity onPress = { this._onToggleChat }>
|
||||
<Icon
|
||||
src = { IconClose }
|
||||
style = { styles.lobbyChatCloseButton } />
|
||||
</TouchableOpacity>
|
||||
<JitsiScreen
|
||||
safeAreaInsets = { [ 'right' ] }
|
||||
style = { styles.contentWrapper }>
|
||||
<View style = { contentStyles }>
|
||||
<View style = { largeVideoContainerStyles }>
|
||||
<LargeVideo />
|
||||
</View>
|
||||
<View style = { contentContainerStyles }>
|
||||
{ this._renderContent() }
|
||||
{ this._renderToolbarButtons() }
|
||||
</View>
|
||||
</View>
|
||||
<MessageContainer messages = { this.props._lobbyChatMessages } />
|
||||
<ChatInputBar onSend = { this._onSendMessage } />
|
||||
</>
|
||||
</JitsiScreen>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,15 +131,15 @@ class LobbyScreen extends AbstractLobbyScreen {
|
||||
*/
|
||||
_renderJoining() {
|
||||
return (
|
||||
<>
|
||||
<View style = { styles.formWrapper }>
|
||||
<LoadingIndicator
|
||||
color = 'black'
|
||||
color = { BaseTheme.palette.icon01 }
|
||||
style = { styles.loadingIndicator } />
|
||||
<Text style = { styles.joiningMessage }>
|
||||
{ this.props.t('lobby.joiningMessage') }
|
||||
</Text>
|
||||
{ this._renderStandardButtons() }
|
||||
</>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -127,7 +150,7 @@ class LobbyScreen extends AbstractLobbyScreen {
|
||||
*/
|
||||
_renderParticipantForm() {
|
||||
const { t } = this.props;
|
||||
const { displayName, email } = this.state;
|
||||
const { displayName } = this.state;
|
||||
|
||||
return (
|
||||
<View style = { styles.formWrapper }>
|
||||
@@ -138,13 +161,6 @@ class LobbyScreen extends AbstractLobbyScreen {
|
||||
onChangeText = { this._onChangeDisplayName }
|
||||
style = { styles.field }
|
||||
value = { displayName } />
|
||||
<Text style = { styles.fieldLabel }>
|
||||
{ t('lobby.emailField') }
|
||||
</Text>
|
||||
<TextInput
|
||||
onChangeText = { this._onChangeEmail }
|
||||
style = { styles.field }
|
||||
value = { email } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -155,28 +171,7 @@ class LobbyScreen extends AbstractLobbyScreen {
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderParticipantInfo() {
|
||||
const { displayName, email } = this.state;
|
||||
|
||||
return (
|
||||
<View style = { styles.participantBox }>
|
||||
<TouchableOpacity
|
||||
onPress = { this._onEnableEdit }
|
||||
style = { styles.editButton }>
|
||||
<Icon
|
||||
src = { IconEdit }
|
||||
style = { styles.editIcon } />
|
||||
</TouchableOpacity>
|
||||
<Avatar
|
||||
participantId = { this.props._participantId }
|
||||
size = { 64 } />
|
||||
<Text style = { styles.displayNameText }>
|
||||
{ displayName }
|
||||
</Text>
|
||||
{ Boolean(email) && <Text style = { styles.secondaryText }>
|
||||
{ email }
|
||||
</Text> }
|
||||
</View>
|
||||
);
|
||||
return this._renderParticipantForm();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,7 +210,17 @@ class LobbyScreen extends AbstractLobbyScreen {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style = { styles.passwordJoinButtonsWrapper }>
|
||||
<TouchableOpacity
|
||||
onPress = { this._onSwitchToKnockMode }
|
||||
style = { [
|
||||
styles.button,
|
||||
styles.primaryButton
|
||||
] }>
|
||||
<Text style = { styles.primaryButtonText }>
|
||||
{ t('lobby.backToKnockModeButton') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
disabled = { !this.state.password }
|
||||
onPress = { this._onJoinWithPassword }
|
||||
@@ -227,17 +232,34 @@ class LobbyScreen extends AbstractLobbyScreen {
|
||||
{ t('lobby.passwordJoinButton') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress = { this._onSwitchToKnockMode }
|
||||
style = { [
|
||||
styles.button,
|
||||
styles.secondaryButton
|
||||
] }>
|
||||
<Text>
|
||||
{ t('lobby.backToKnockModeButton') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the toolbar buttons menu.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderToolbarButtons() {
|
||||
const { _aspectRatio } = this.props;
|
||||
let toolboxContainerStyles;
|
||||
|
||||
if (_aspectRatio === ASPECT_RATIO_NARROW) {
|
||||
toolboxContainerStyles = styles.toolboxContainer;
|
||||
} else {
|
||||
toolboxContainerStyles = styles.toolboxContainerWide;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { toolboxContainerStyles }>
|
||||
<AudioMuteButton
|
||||
styles = { styles.buttonStylesBorderless } />
|
||||
<VideoMuteButton
|
||||
styles = { styles.buttonStylesBorderless } />
|
||||
<InviteButton
|
||||
styles = { styles.buttonStylesBorderless } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -248,50 +270,72 @@ class LobbyScreen extends AbstractLobbyScreen {
|
||||
*/
|
||||
_renderStandardButtons() {
|
||||
const { _knocking, _renderPassword, _isLobbyChatActive, t } = this.props;
|
||||
const { displayName } = this.state;
|
||||
const askToJoinButtonStyles
|
||||
= displayName ? styles.primaryButton : styles.primaryButtonDisabled;
|
||||
|
||||
return (
|
||||
<>
|
||||
{ _knocking || <TouchableOpacity
|
||||
disabled = { !this.state.displayName }
|
||||
onPress = { this._onAskToJoin }
|
||||
<View style = { styles.standardButtonWrapper }>
|
||||
{ _knocking && _isLobbyChatActive && <TouchableOpacity
|
||||
onPress = { this._onNavigateToLobbyChat }
|
||||
style = { [
|
||||
styles.button,
|
||||
styles.primaryButton
|
||||
] }>
|
||||
<Text style = { styles.primaryButtonText }>
|
||||
{ t('lobby.knockButton') }
|
||||
</Text>
|
||||
</TouchableOpacity> }
|
||||
{ _knocking && _isLobbyChatActive && <TouchableOpacity
|
||||
onPress = { this._onToggleChat }
|
||||
style = { [
|
||||
styles.button,
|
||||
styles.secondaryButton
|
||||
] }>
|
||||
<Text>
|
||||
{ t('toolbar.openChat') }
|
||||
</Text>
|
||||
</TouchableOpacity>}
|
||||
{ _knocking || <TouchableOpacity
|
||||
disabled = { !displayName }
|
||||
onPress = { this._onAskToJoin }
|
||||
style = { [
|
||||
styles.button,
|
||||
askToJoinButtonStyles
|
||||
] }>
|
||||
<Text style = { styles.primaryButtonText }>
|
||||
{ t('lobby.knockButton') }
|
||||
</Text>
|
||||
</TouchableOpacity> }
|
||||
{ _renderPassword && <TouchableOpacity
|
||||
onPress = { this._onSwitchToPasswordMode }
|
||||
style = { [
|
||||
styles.button,
|
||||
styles.secondaryButton
|
||||
styles.primaryButton
|
||||
] }>
|
||||
<Text>
|
||||
<Text style = { styles.primaryButtonText }>
|
||||
{ t('lobby.enterPasswordButton') }
|
||||
</Text>
|
||||
</TouchableOpacity> }
|
||||
<TouchableOpacity
|
||||
onPress = { this._onCancel }
|
||||
style = { styles.cancelButton }>
|
||||
<Text>
|
||||
style = { [
|
||||
styles.button,
|
||||
styles.cancelButton
|
||||
] }>
|
||||
<Text style = { styles.cancelButtonText }>
|
||||
{ t('dialog.Cancel') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Props} ownProps - The own props of the component.
|
||||
* @returns {{
|
||||
* _aspectRatio: Symbol
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
return {
|
||||
...abstractMapStateToProps(state, ownProps),
|
||||
_aspectRatio: state['features/base/responsive-ui'].aspectRatio
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(LobbyScreen));
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
export { default as KnockingParticipantList } from './KnockingParticipantList';
|
||||
export { default as LobbyScreen } from './LobbyScreen';
|
||||
export { default as LobbyChatScreen } from './LobbyChatScreen';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
const SECONDARY_COLOR = BaseTheme.palette.border04;
|
||||
|
||||
@@ -8,8 +8,24 @@ export default {
|
||||
button: {
|
||||
alignItems: 'center',
|
||||
borderRadius: 4,
|
||||
marginVertical: BaseTheme.spacing[1],
|
||||
paddingVertical: BaseTheme.spacing[2]
|
||||
padding: BaseTheme.spacing[2],
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
buttonStylesBorderless: {
|
||||
iconStyle: {
|
||||
backgroundColor: BaseTheme.palette.action02Active,
|
||||
color: BaseTheme.palette.icon01,
|
||||
fontSize: 24
|
||||
},
|
||||
style: {
|
||||
backgroundColor: BaseTheme.palette.action02Active,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginHorizontal: BaseTheme.spacing[3],
|
||||
height: 24,
|
||||
width: 24
|
||||
}
|
||||
},
|
||||
|
||||
lobbyChatWrapper: {
|
||||
@@ -26,29 +42,66 @@ export default {
|
||||
},
|
||||
|
||||
lobbyChatTitle: {
|
||||
color: '#fff',
|
||||
color: BaseTheme.palette.text01,
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
flexShrink: 1
|
||||
},
|
||||
|
||||
lobbyChatCloseButton: {
|
||||
fontSize: 20,
|
||||
marginLeft: 20,
|
||||
color: '#fff'
|
||||
fontSize: 24,
|
||||
marginLeft: BaseTheme.spacing[3],
|
||||
marginTop: BaseTheme.spacing[1],
|
||||
color: BaseTheme.palette.icon01
|
||||
},
|
||||
|
||||
contentWrapper: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyItems: 'center',
|
||||
height: '100%'
|
||||
backgroundColor: BaseTheme.palette.ui02,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
closeIcon: {
|
||||
color: 'red',
|
||||
fontSize: 20
|
||||
contentWide: {
|
||||
backgroundColor: BaseTheme.palette.ui02,
|
||||
flex: 1,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
largeVideoContainer: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
minHeight: '50%'
|
||||
},
|
||||
|
||||
largeVideoContainerWide: {
|
||||
height: '100%',
|
||||
width: '50%'
|
||||
},
|
||||
|
||||
contentContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
contentContainerWide: {
|
||||
justifyContent: 'center',
|
||||
marginHorizontal: BaseTheme.spacing[2],
|
||||
width: '50%'
|
||||
},
|
||||
|
||||
toolboxContainer: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginTop: BaseTheme.spacing[4]
|
||||
},
|
||||
|
||||
toolboxContainerWide: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginTop: BaseTheme.spacing[4]
|
||||
},
|
||||
|
||||
dialogTitle: {
|
||||
@@ -75,40 +128,55 @@ export default {
|
||||
},
|
||||
|
||||
field: {
|
||||
backgroundColor: BaseTheme.palette.field02,
|
||||
borderColor: SECONDARY_COLOR,
|
||||
borderRadius: 4,
|
||||
borderWidth: 1,
|
||||
marginVertical: 8,
|
||||
padding: 8
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
borderWidth: 2,
|
||||
marginHorizontal: BaseTheme.spacing[3],
|
||||
padding: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
fieldError: {
|
||||
color: BaseTheme.palette.warning07,
|
||||
fontSize: 10
|
||||
},
|
||||
|
||||
fieldRow: {
|
||||
paddingTop: 16
|
||||
color: BaseTheme.palette.warning03,
|
||||
marginLeft: BaseTheme.spacing[3],
|
||||
fontSize: 16
|
||||
},
|
||||
|
||||
fieldLabel: {
|
||||
color: BaseTheme.palette.text01,
|
||||
marginVertical: BaseTheme.spacing[4],
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
formWrapper: {
|
||||
alignItems: 'stretch',
|
||||
alignSelf: 'stretch'
|
||||
},
|
||||
|
||||
standardButtonWrapper: {
|
||||
alignSelf: 'stretch',
|
||||
paddingVertical: 16
|
||||
marginHorizontal: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
joiningContainer: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
joiningMessage: {
|
||||
color: 'rgba(0, 0, 0, .7)',
|
||||
paddingBottom: 36,
|
||||
color: BaseTheme.palette.text01,
|
||||
marginBottom: BaseTheme.spacing[2],
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
passwordJoinButtonsWrapper: {
|
||||
alignItems: 'stretch',
|
||||
alignSelf: 'stretch',
|
||||
marginHorizontal: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
loadingIndicator: {
|
||||
marginVertical: 36
|
||||
marginVertical: BaseTheme.spacing[4]
|
||||
},
|
||||
|
||||
participantBox: {
|
||||
@@ -122,29 +190,33 @@ export default {
|
||||
},
|
||||
|
||||
primaryButton: {
|
||||
alignSelf: 'stretch',
|
||||
backgroundColor: 'rgb(3, 118, 218)'
|
||||
backgroundColor: BaseTheme.palette.action01,
|
||||
marginTop: BaseTheme.spacing[4]
|
||||
},
|
||||
|
||||
primaryButtonDisabled: {
|
||||
backgroundColor: BaseTheme.palette.action03Disabled,
|
||||
marginTop: BaseTheme.spacing[4]
|
||||
},
|
||||
|
||||
primaryButtonText: {
|
||||
color: 'white'
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
secondaryButton: {
|
||||
alignSelf: 'stretch',
|
||||
backgroundColor: 'transparent'
|
||||
},
|
||||
|
||||
secondaryText: {
|
||||
color: 'rgba(0, 0, 0, .7)',
|
||||
primaryText: {
|
||||
color: BaseTheme.palette.text01,
|
||||
margin: 'auto',
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
cancelButton: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'transparent',
|
||||
marginVertical: 4
|
||||
backgroundColor: BaseTheme.palette.action02Disabled,
|
||||
marginTop: BaseTheme.spacing[4]
|
||||
},
|
||||
|
||||
cancelButtonText: {
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
// KnockingParticipantList
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import React from 'react';
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
// $FlowExpectedError
|
||||
export const conferenceNavigationRef = React.createRef();
|
||||
|
||||
/**
|
||||
@@ -13,7 +10,6 @@ export const conferenceNavigationRef = React.createRef();
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function navigate(name: string, params: Object) {
|
||||
// $FlowExpectedError
|
||||
return conferenceNavigationRef.current?.navigate(name, params);
|
||||
}
|
||||
|
||||
@@ -23,7 +19,6 @@ export function navigate(name: string, params: Object) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function goBack() {
|
||||
// $FlowExpectedError
|
||||
return conferenceNavigationRef.current?.goBack();
|
||||
}
|
||||
|
||||
@@ -34,7 +29,6 @@ export function goBack() {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setParams(params: Object) {
|
||||
// $FlowExpectedError
|
||||
return conferenceNavigationRef.current?.setParams(params);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import React from 'react';
|
||||
@@ -13,7 +11,6 @@ import { SharedDocument } from '../../../../../etherpad';
|
||||
import { GifsMenu } from '../../../../../gifs/components';
|
||||
import AddPeopleDialog
|
||||
from '../../../../../invite/components/add-people-dialog/native/AddPeopleDialog';
|
||||
import LobbyScreen from '../../../../../lobby/components/native/LobbyScreen';
|
||||
import { ParticipantsPane } from '../../../../../participants-pane/components/native';
|
||||
import { StartLiveStreamDialog } from '../../../../../recording';
|
||||
import { StartRecordingDialog }
|
||||
@@ -31,7 +28,6 @@ import {
|
||||
gifsMenuOptions,
|
||||
inviteScreenOptions,
|
||||
liveStreamScreenOptions,
|
||||
lobbyScreenOptions,
|
||||
navigationContainerTheme,
|
||||
participantsScreenOptions,
|
||||
recordingScreenOptions,
|
||||
@@ -42,6 +38,8 @@ import {
|
||||
} from '../../../screenOptions';
|
||||
import ChatAndPollsNavigationContainer
|
||||
from '../../chat/components/ChatAndPollsNavigationContainer';
|
||||
import LobbyNavigationContainer
|
||||
from '../../lobby/components/LobbyNavigationContainer';
|
||||
import {
|
||||
conferenceNavigationRef
|
||||
} from '../ConferenceNavigationContainerRef';
|
||||
@@ -134,9 +132,12 @@ const ConferenceNavigationContainer = () => {
|
||||
title: t('notify.gifsMenu')
|
||||
}} />
|
||||
<ConferenceStack.Screen
|
||||
component = { LobbyScreen }
|
||||
name = { screen.lobby }
|
||||
options = { lobbyScreenOptions } />
|
||||
component = { LobbyNavigationContainer }
|
||||
name = { screen.lobby.root }
|
||||
options = {{
|
||||
gestureEnabled: false,
|
||||
headerShown: false
|
||||
}} />
|
||||
<ConferenceStack.Screen
|
||||
component = { AddPeopleDialog }
|
||||
name = { screen.conference.invite }
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
export const lobbyNavigationContainerRef = React.createRef();
|
||||
|
||||
/**
|
||||
* User defined navigation action included inside the reference to the container.
|
||||
*
|
||||
* @param {string} name - Destination name of the route that has been defined somewhere.
|
||||
* @param {Object} params - Params to pass to the destination route.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function navigate(name: string, params: Object) {
|
||||
return lobbyNavigationContainerRef.current?.navigate(name, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* User defined navigation action included inside the reference to the container.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function goBack() {
|
||||
return lobbyNavigationContainerRef.current?.goBack();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { LobbyChatScreen, LobbyScreen } from '../../../../../lobby';
|
||||
import { screen } from '../../../routes';
|
||||
import {
|
||||
lobbyChatScreenOptions,
|
||||
lobbyScreenOptions, navigationContainerTheme
|
||||
} from '../../../screenOptions';
|
||||
import { lobbyNavigationContainerRef } from '../LobbyNavigationContainerRef';
|
||||
|
||||
const LobbyStack = createStackNavigator();
|
||||
|
||||
|
||||
const LobbyNavigationContainer = () => {
|
||||
const { isLobbyChatActive }
|
||||
= useSelector(state => state['features/chat']);
|
||||
|
||||
return (
|
||||
<NavigationContainer
|
||||
independent = { true }
|
||||
ref = { lobbyNavigationContainerRef }
|
||||
theme = { navigationContainerTheme }>
|
||||
<LobbyStack.Navigator
|
||||
screenOptions = {{
|
||||
presentation: 'modal'
|
||||
}}>
|
||||
<LobbyStack.Screen
|
||||
component = { LobbyScreen }
|
||||
name = { screen.lobby.main }
|
||||
options = { lobbyScreenOptions } />
|
||||
{
|
||||
isLobbyChatActive
|
||||
&& <LobbyStack.Screen
|
||||
component = { LobbyChatScreen }
|
||||
name = { screen.lobby.chat }
|
||||
options = { lobbyChatScreenOptions } />
|
||||
}
|
||||
</LobbyStack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default LobbyNavigationContainer;
|
||||
31
react/features/mobile/navigation/functions.js
Normal file
31
react/features/mobile/navigation/functions.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
import { IconClose } from '../../base/icons';
|
||||
|
||||
import HeaderNavigationButton from './components/HeaderNavigationButton';
|
||||
|
||||
/**
|
||||
* Close icon/text button based on platform.
|
||||
*
|
||||
* @param {Function} goBack - Goes back to the previous screen function.
|
||||
* @returns {React.Component}
|
||||
*/
|
||||
export function screenHeaderCloseButton(goBack: Function) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
return (
|
||||
<HeaderNavigationButton
|
||||
label = { t('dialog.close') }
|
||||
onPress = { goBack } />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderNavigationButton
|
||||
onPress = { goBack }
|
||||
src = { IconClose } />
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import { SET_ROOM } from '../../base/conference/actionTypes';
|
||||
import { CONFERENCE_FAILED, SET_ROOM } from '../../base/conference/actionTypes';
|
||||
import { JitsiConferenceErrors } from '../../base/lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
import { readyToClose } from '../external-api/actions';
|
||||
|
||||
@@ -16,6 +15,9 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case SET_ROOM:
|
||||
return _setRoom(store, next, action);
|
||||
|
||||
case CONFERENCE_FAILED:
|
||||
return _conferenceFailed(store, next, action);
|
||||
}
|
||||
|
||||
return next(action);
|
||||
@@ -61,3 +63,31 @@ function _setRoom({ dispatch, getState }, next, action) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to handle the conference failed event and navigate the user to the lobby screen
|
||||
* based on the failure reason.
|
||||
*
|
||||
* @param {Object} store - The Redux store.
|
||||
* @param {Function} next - The Redux next function.
|
||||
* @param {Object} action - The Redux action.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _conferenceFailed({ dispatch, getState }, next, action) {
|
||||
const state = getState();
|
||||
const isWelcomePageEnabled = isWelcomePageAppEnabled(state);
|
||||
const { error } = action;
|
||||
|
||||
// We need to cover the case where knocking participant
|
||||
// is rejected from entering the conference
|
||||
if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
|
||||
if (isWelcomePageEnabled) {
|
||||
navigateRoot(screen.root);
|
||||
} else {
|
||||
// For JitsiSDK, WelcomePage is not available
|
||||
_sendReadyToClose(dispatch);
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
@@ -34,5 +34,9 @@ export const screen = {
|
||||
invite: 'Invite',
|
||||
sharedDocument: 'Shared document'
|
||||
},
|
||||
lobby: 'Lobby'
|
||||
lobby: {
|
||||
root: 'Lobby root',
|
||||
main: 'Lobby',
|
||||
chat: 'Lobby chat'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import { TransitionPresets } from '@react-navigation/stack';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
import {
|
||||
Icon,
|
||||
IconClose,
|
||||
IconHelp,
|
||||
IconHome,
|
||||
IconInfo,
|
||||
@@ -15,8 +11,9 @@ import {
|
||||
} from '../../base/icons';
|
||||
import BaseTheme from '../../base/ui/components/BaseTheme.native';
|
||||
|
||||
import HeaderNavigationButton from './components/HeaderNavigationButton';
|
||||
import { goBack } from './components/conference/ConferenceNavigationContainerRef';
|
||||
import { goBack as goBackToLobbyScreen } from './components/lobby/LobbyNavigationContainerRef';
|
||||
import { screenHeaderCloseButton } from './functions';
|
||||
|
||||
|
||||
/**
|
||||
@@ -167,16 +164,12 @@ export const helpScreenOptions = {
|
||||
/**
|
||||
* Screen options for conference.
|
||||
*/
|
||||
export const conferenceScreenOptions = {
|
||||
...fullScreenOptions
|
||||
};
|
||||
export const conferenceScreenOptions = fullScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for lobby modal.
|
||||
*/
|
||||
export const lobbyScreenOptions = {
|
||||
...fullScreenOptions
|
||||
};
|
||||
export const lobbyScreenOptions = fullScreenOptions;
|
||||
|
||||
/**
|
||||
* Tab bar options for chat screen.
|
||||
@@ -198,23 +191,7 @@ export const chatTabBarOptions = {
|
||||
export const presentationScreenOptions = {
|
||||
...conferenceModalPresentation,
|
||||
headerBackTitleVisible: false,
|
||||
headerLeft: () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
return (
|
||||
<HeaderNavigationButton
|
||||
label = { t('dialog.close') }
|
||||
onPress = { goBack } />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderNavigationButton
|
||||
onPress = { goBack }
|
||||
src = { IconClose } />
|
||||
);
|
||||
},
|
||||
headerLeft: () => screenHeaderCloseButton(goBack),
|
||||
headerStatusBarHeight: 0,
|
||||
headerStyle: {
|
||||
backgroundColor: BaseTheme.palette.screen01Header
|
||||
@@ -227,50 +204,44 @@ export const presentationScreenOptions = {
|
||||
/**
|
||||
* Screen options for chat.
|
||||
*/
|
||||
export const chatScreenOptions = {
|
||||
...presentationScreenOptions
|
||||
};
|
||||
export const chatScreenOptions = presentationScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for invite modal.
|
||||
*/
|
||||
export const inviteScreenOptions = {
|
||||
...presentationScreenOptions
|
||||
};
|
||||
export const inviteScreenOptions = presentationScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for participants modal.
|
||||
*/
|
||||
export const participantsScreenOptions = {
|
||||
...presentationScreenOptions
|
||||
};
|
||||
export const participantsScreenOptions = presentationScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for speaker stats modal.
|
||||
*/
|
||||
export const speakerStatsScreenOptions = {
|
||||
...presentationScreenOptions
|
||||
};
|
||||
export const speakerStatsScreenOptions = presentationScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for security options modal.
|
||||
*/
|
||||
export const securityScreenOptions = {
|
||||
...presentationScreenOptions
|
||||
};
|
||||
export const securityScreenOptions = presentationScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for recording modal.
|
||||
*/
|
||||
export const recordingScreenOptions = {
|
||||
...presentationScreenOptions
|
||||
};
|
||||
export const recordingScreenOptions = presentationScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for live stream modal.
|
||||
*/
|
||||
export const liveStreamScreenOptions = {
|
||||
...presentationScreenOptions
|
||||
export const liveStreamScreenOptions = presentationScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for lobby chat modal.
|
||||
*/
|
||||
export const lobbyChatScreenOptions = {
|
||||
...presentationScreenOptions,
|
||||
headerLeft: () => screenHeaderCloseButton(goBackToLobbyScreen)
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,7 +60,7 @@ class Notification extends AbstractNotification<Props> {
|
||||
description = { this._renderDescription() }
|
||||
icon = { this._mapAppearanceToIcon() }
|
||||
id = { uid }
|
||||
testId = { titleKey }
|
||||
testId = { titleKey || this._getDescriptionKey() }
|
||||
title = { title || t(titleKey, titleArguments) } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,7 +128,12 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const { participant: p } = action;
|
||||
const { conference } = state['features/base/conference'];
|
||||
|
||||
if (conference && !p.local && !joinLeaveNotificationsDisabled() && !p.isReplacing) {
|
||||
// Do not display notifications for the fake screenshare tiles.
|
||||
if (conference
|
||||
&& !p.local
|
||||
&& !p.isFakeScreenShareParticipant
|
||||
&& !joinLeaveNotificationsDisabled()
|
||||
&& !p.isReplacing) {
|
||||
dispatch(showParticipantJoinedNotification(
|
||||
getParticipantDisplayName(state, p.id)
|
||||
));
|
||||
@@ -143,7 +148,11 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
action.participant.id
|
||||
);
|
||||
|
||||
if (participant && !participant.local && !action.participant.isReplaced) {
|
||||
// Do not display notifications for the fake screenshare tiles.
|
||||
if (participant
|
||||
&& !participant.local
|
||||
&& !participant.isFakeScreenShareParticipant
|
||||
&& !action.participant.isReplaced) {
|
||||
dispatch(showParticipantLeftNotification(
|
||||
getParticipantDisplayName(state, participant.id)
|
||||
));
|
||||
|
||||
@@ -60,6 +60,11 @@ type Props = {
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Is the local participant moderator?
|
||||
*/
|
||||
isLocalModerator: boolean,
|
||||
|
||||
/**
|
||||
* List of participants waiting in lobby.
|
||||
*/
|
||||
@@ -191,6 +196,7 @@ class MeetingParticipantList extends PureComponent<Props> {
|
||||
_showInviteButton,
|
||||
_sortedRemoteParticipants,
|
||||
breakoutRooms,
|
||||
isLocalModerator,
|
||||
lobbyParticipants,
|
||||
t
|
||||
} = this.props;
|
||||
@@ -208,11 +214,14 @@ class MeetingParticipantList extends PureComponent<Props> {
|
||||
// we take the full container height
|
||||
const onlyMeetingParticipants
|
||||
= breakoutRooms?.length === 0 && lobbyParticipants?.length === 0;
|
||||
const containerStyle
|
||||
const containerStyleModerator
|
||||
= onlyMeetingParticipants
|
||||
? styles.meetingListFullContainer : styles.meetingListContainer;
|
||||
const containerStyle
|
||||
= isLocalModerator
|
||||
? containerStyleModerator : styles.notLocalModeratorContainer;
|
||||
const finalContainerStyle
|
||||
= _participantsCount > 3 && containerStyle;
|
||||
= _participantsCount > 6 && containerStyle;
|
||||
|
||||
return (
|
||||
<CollapsibleList
|
||||
|
||||
@@ -71,6 +71,7 @@ const ParticipantsPane = () => {
|
||||
<LobbyParticipantList />
|
||||
<MeetingParticipantList
|
||||
breakoutRooms = { rooms }
|
||||
isLocalModerator = { isLocalModerator }
|
||||
lobbyParticipants = { lobbyParticipants }
|
||||
searchString = { searchString }
|
||||
setSearchString = { setSearchString } />
|
||||
|
||||
@@ -216,8 +216,12 @@ export default {
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
notLocalModeratorContainer: {
|
||||
height: '100%'
|
||||
},
|
||||
|
||||
meetingListContainer: {
|
||||
height: '56%'
|
||||
height: '58%'
|
||||
},
|
||||
|
||||
meetingListFullContainer: {
|
||||
|
||||
@@ -31,7 +31,7 @@ type Props = {
|
||||
function LobbyParticipantItems({ openDrawerForParticipant, overflowDrawer, participants }: Props) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div id = 'lobby-list'>
|
||||
{participants.map(p => (
|
||||
<LobbyParticipantItem
|
||||
key = { p.id }
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconArrowUp } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { ToolboxButtonWithIcon } from '../../../base/toolbox/components';
|
||||
import ToolboxButtonWithIconPopup from '../../../base/toolbox/components/web/ToolboxButtonWithIconPopup';
|
||||
import { toggleReactionsMenuVisibility } from '../../actions.web';
|
||||
import { type ReactionEmojiProps } from '../../constants';
|
||||
import { getReactionsQueue, isReactionsEnabled } from '../../functions.any';
|
||||
@@ -14,7 +15,7 @@ import { getReactionsMenuVisibility } from '../../functions.web';
|
||||
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
import ReactionEmoji from './ReactionEmoji';
|
||||
import ReactionsMenuPopup from './ReactionsMenuPopup';
|
||||
import ReactionsMenu from './ReactionsMenu';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -26,7 +27,7 @@ type Props = {
|
||||
/**
|
||||
* The button's key.
|
||||
*/
|
||||
buttonKey?: string,
|
||||
buttonKey?: string,
|
||||
|
||||
/**
|
||||
* Redux dispatch function.
|
||||
@@ -84,38 +85,47 @@ function ReactionsMenuButton({
|
||||
reactionsQueue,
|
||||
t
|
||||
}: Props) {
|
||||
const visible = useSelector(getReactionsMenuVisibility);
|
||||
const toggleReactionsMenu = useCallback(() => {
|
||||
dispatch(toggleReactionsMenuVisibility());
|
||||
}, [ dispatch ]);
|
||||
|
||||
const openReactionsMenu = useCallback(() => {
|
||||
!visible && toggleReactionsMenu();
|
||||
}, [ visible, toggleReactionsMenu ]);
|
||||
|
||||
const reactionsMenu = (<div className = 'reactions-menu-container'>
|
||||
<ReactionsMenu />
|
||||
</div>);
|
||||
|
||||
return (
|
||||
<div className = 'reactions-menu-popup-container'>
|
||||
<ReactionsMenuPopup>
|
||||
{!_reactionsEnabled || isMobile ? (
|
||||
<RaiseHandButton
|
||||
{!_reactionsEnabled || isMobile ? (
|
||||
<RaiseHandButton
|
||||
buttonKey = { buttonKey }
|
||||
handleClick = { handleClick }
|
||||
notifyMode = { notifyMode } />)
|
||||
: (
|
||||
<ToolboxButtonWithIconPopup
|
||||
ariaControls = 'reactions-menu-dialog'
|
||||
ariaExpanded = { isOpen }
|
||||
ariaHasPopup = { true }
|
||||
ariaLabel = { t('toolbar.accessibilityLabel.reactionsMenu') }
|
||||
buttonKey = { buttonKey }
|
||||
handleClick = { handleClick }
|
||||
notifyMode = { notifyMode } />)
|
||||
: (
|
||||
<ToolboxButtonWithIcon
|
||||
ariaControls = 'reactions-menu-dialog'
|
||||
ariaExpanded = { isOpen }
|
||||
ariaHasPopup = { true }
|
||||
ariaLabel = { t('toolbar.accessibilityLabel.reactionsMenu') }
|
||||
icon = { IconArrowUp }
|
||||
iconDisabled = { false }
|
||||
iconId = 'reactions-menu-button'
|
||||
notifyMode = { notifyMode }
|
||||
onPopoverClose = { toggleReactionsMenu }
|
||||
onPopoverOpen = { openReactionsMenu }
|
||||
popoverContent = { reactionsMenu }
|
||||
visible = { visible }>
|
||||
<RaiseHandButton
|
||||
buttonKey = { buttonKey }
|
||||
icon = { IconArrowUp }
|
||||
iconDisabled = { false }
|
||||
iconId = 'reactions-menu-button'
|
||||
iconTooltip = { t(`toolbar.${isOpen ? 'closeReactionsMenu' : 'openReactionsMenu'}`) }
|
||||
notifyMode = { notifyMode }
|
||||
onIconClick = { toggleReactionsMenu }>
|
||||
<RaiseHandButton
|
||||
buttonKey = { buttonKey }
|
||||
handleClick = { handleClick }
|
||||
notifyMode = { notifyMode } />
|
||||
</ToolboxButtonWithIcon>
|
||||
)}
|
||||
</ReactionsMenuPopup>
|
||||
handleClick = { handleClick }
|
||||
notifyMode = { notifyMode } />
|
||||
</ToolboxButtonWithIconPopup>
|
||||
)}
|
||||
{reactionsQueue.map(({ reaction, uid }, index) => (<ReactionEmoji
|
||||
index = { index }
|
||||
key = { uid }
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import InlineDialog from '@atlaskit/inline-dialog';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { toggleReactionsMenuVisibility } from '../../actions.web';
|
||||
import { getReactionsMenuVisibility } from '../../functions.web';
|
||||
|
||||
import ReactionsMenu from './ReactionsMenu';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Component's children (the reactions menu button).
|
||||
*/
|
||||
children: React$Node
|
||||
}
|
||||
|
||||
/**
|
||||
* Popup with reactions menu.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function ReactionsMenuPopup({
|
||||
children
|
||||
}: Props) {
|
||||
/**
|
||||
* Flag controlling the visibility of the popup.
|
||||
*/
|
||||
const isOpen = useSelector(state => getReactionsMenuVisibility(state));
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const onClose = useCallback(() => {
|
||||
dispatch(toggleReactionsMenuVisibility());
|
||||
});
|
||||
|
||||
return (
|
||||
<div className = 'reactions-menu-popup'>
|
||||
<InlineDialog
|
||||
content = { <ReactionsMenu /> }
|
||||
isOpen = { isOpen }
|
||||
onClose = { onClose }
|
||||
placement = 'top'>
|
||||
{children}
|
||||
</InlineDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ReactionsMenuPopup;
|
||||
@@ -4,4 +4,3 @@ export { default as ReactionButton } from './ReactionButton';
|
||||
export { default as ReactionEmoji } from './ReactionEmoji';
|
||||
export { default as ReactionsMenu } from './ReactionsMenu';
|
||||
export { default as ReactionsMenuButton } from './ReactionsMenuButton';
|
||||
export { default as ReactionsMenuPopup } from './ReactionsMenuPopup';
|
||||
|
||||
@@ -119,11 +119,12 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
case ADD_FACE_EXPRESSION: {
|
||||
if (canSendRtcstatsData(state)) {
|
||||
const { duration, faceExpression } = action;
|
||||
const { duration, faceExpression, timestamp } = action;
|
||||
|
||||
RTCStats.sendFaceExpressionData({
|
||||
duration,
|
||||
faceExpression
|
||||
faceExpression,
|
||||
timestamp
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -92,6 +92,6 @@ export function _mapStateToProps(state: Object) {
|
||||
|
||||
return {
|
||||
_locked: locked || lobbyEnabled,
|
||||
visible: enabledFlag || (enabledLobbyModeFlag || enabledMeetingPassFlag)
|
||||
visible: enabledFlag && (enabledLobbyModeFlag || enabledMeetingPassFlag)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,3 +45,15 @@ export const UPDATE_TRANSCRIPT_MESSAGE = 'UPDATE_TRANSCRIPT_MESSAGE';
|
||||
*/
|
||||
export const TOGGLE_REQUESTING_SUBTITLES
|
||||
= 'TOGGLE_REQUESTING_SUBTITLES';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which indicates if the user set the state of
|
||||
* the subtitles to enabled or disabled.
|
||||
*
|
||||
* {
|
||||
* type: SET_REQUESTING_SUBTITLES
|
||||
* enabled: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_REQUESTING_SUBTITLES
|
||||
= 'SET_REQUESTING_SUBTITLES';
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ENDPOINT_MESSAGE_RECEIVED,
|
||||
REMOVE_TRANSCRIPT_MESSAGE,
|
||||
TOGGLE_REQUESTING_SUBTITLES,
|
||||
SET_REQUESTING_SUBTITLES,
|
||||
UPDATE_TRANSCRIPT_MESSAGE
|
||||
} from './actionTypes';
|
||||
|
||||
@@ -75,3 +76,19 @@ export function toggleRequestingSubtitles() {
|
||||
type: TOGGLE_REQUESTING_SUBTITLES
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the local user has enabled or disabled the subtitles.
|
||||
*
|
||||
* @param {boolean} enabled - The new state of the subtitles.
|
||||
* @returns {{
|
||||
* type: SET_REQUESTING_SUBTITLES,
|
||||
* enabled: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setRequestingSubtitles(enabled: boolean) {
|
||||
return {
|
||||
type: SET_REQUESTING_SUBTITLES,
|
||||
enabled
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
ENDPOINT_MESSAGE_RECEIVED,
|
||||
TOGGLE_REQUESTING_SUBTITLES
|
||||
TOGGLE_REQUESTING_SUBTITLES,
|
||||
SET_REQUESTING_SUBTITLES
|
||||
} from './actionTypes';
|
||||
import {
|
||||
removeTranscriptMessage,
|
||||
@@ -56,6 +57,9 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case TOGGLE_REQUESTING_SUBTITLES:
|
||||
_requestingSubtitlesToggled(store);
|
||||
break;
|
||||
case SET_REQUESTING_SUBTITLES:
|
||||
_requestingSubtitlesSet(store, action.enabled);
|
||||
break;
|
||||
}
|
||||
|
||||
return next(action);
|
||||
@@ -177,6 +181,24 @@ function _requestingSubtitlesToggled({ getState }) {
|
||||
!_requestingSubtitles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the local property 'requestingTranscription'. This will cause Jicofo
|
||||
* and Jigasi to decide whether the transcriber needs to be in the room.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @param {boolean} enabled - The new state of the subtitles.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _requestingSubtitlesSet({ getState }, enabled: boolean) {
|
||||
const state = getState();
|
||||
const { conference } = state['features/base/conference'];
|
||||
|
||||
conference.setLocalParticipantProperty(
|
||||
P_NAME_REQUESTING_TRANSCRIPTION,
|
||||
enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a timeout on a TranscriptMessage object so it clears itself when it's not
|
||||
* updated.
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
REMOVE_TRANSCRIPT_MESSAGE, TOGGLE_REQUESTING_SUBTITLES,
|
||||
UPDATE_TRANSCRIPT_MESSAGE
|
||||
SET_REQUESTING_SUBTITLES, UPDATE_TRANSCRIPT_MESSAGE
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
@@ -30,6 +30,11 @@ ReducerRegistry.register('features/subtitles', (
|
||||
...state,
|
||||
_requestingSubtitles: !state._requestingSubtitles
|
||||
};
|
||||
case SET_REQUESTING_SUBTITLES:
|
||||
return {
|
||||
...state,
|
||||
_requestingSubtitles: action.enabled
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
@@ -157,7 +157,7 @@ class AudioMuteButton extends AbstractAudioMuteButton<Props, *> {
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
const _audioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
|
||||
const _disabled = state['features/base/config'].startSilent || isAudioMuteButtonDisabled(state);
|
||||
const _disabled = isAudioMuteButtonDisabled(state);
|
||||
const enabledFlag = getFeatureFlag(state, AUDIO_MUTE_BUTTON_ENABLED, true);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
type Props = {
|
||||
@@ -41,8 +41,11 @@ function DialogPortal({ children, className, style, getRef, setSize }: Props) {
|
||||
const [ portalTarget ] = useState(() => {
|
||||
const portalDiv = document.createElement('div');
|
||||
|
||||
portalDiv.style.visibility = 'hidden';
|
||||
|
||||
return portalDiv;
|
||||
});
|
||||
const timerRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
if (style) {
|
||||
@@ -74,6 +77,10 @@ function DialogPortal({ children, className, style, getRef, setSize }: Props) {
|
||||
|
||||
if (contentRect.width !== size.width || contentRect.height !== size.height) {
|
||||
setSize && setSize(contentRect);
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = setTimeout(() => {
|
||||
portalTarget.style.visibility = 'visible';
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -863,6 +863,27 @@ class Toolbox extends Component<Props> {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the notify mode of the given toolbox button.
|
||||
*
|
||||
* @param {string} btnName - The toolbar button's name.
|
||||
* @returns {string|undefined} - The button's notify mode.
|
||||
*/
|
||||
_getButtonNotifyMode(btnName) {
|
||||
const notify = this.props._buttonsWithNotifyClick?.find(
|
||||
(btn: string | Object) =>
|
||||
(typeof btn === 'string' && btn === btnName)
|
||||
|| (typeof btn === 'object' && btn.key === btnName)
|
||||
);
|
||||
|
||||
if (notify) {
|
||||
return typeof notify === 'string' || notify.preventExecution
|
||||
? NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
: NOTIFY_CLICK_MODE.ONLY_NOTIFY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the notify click mode for the buttons.
|
||||
*
|
||||
@@ -876,19 +897,7 @@ class Toolbox extends Component<Props> {
|
||||
|
||||
Object.values(buttons).forEach((button: any) => {
|
||||
if (typeof button === 'object') {
|
||||
const notify = this.props._buttonsWithNotifyClick.find(
|
||||
(btn: string | Object) =>
|
||||
(typeof btn === 'string' && btn === button.key)
|
||||
|| (typeof btn === 'object' && btn.key === button.key)
|
||||
);
|
||||
|
||||
if (notify) {
|
||||
const notifyMode = typeof notify === 'string' || notify.preventExecution
|
||||
? NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
: NOTIFY_CLICK_MODE.ONLY_NOTIFY;
|
||||
|
||||
button.notifyMode = notifyMode;
|
||||
}
|
||||
button.notifyMode = this._getButtonNotifyMode(button.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1369,8 +1378,10 @@ class Toolbox extends Component<Props> {
|
||||
)}
|
||||
|
||||
<HangupButton
|
||||
buttonKey = 'hangup'
|
||||
customClass = 'hangup-button'
|
||||
key = 'hangup-button'
|
||||
notifyMode = { this._getButtonNotifyMode('hangup') }
|
||||
visible = { isToolbarButtonEnabled('hangup', _toolbarButtons) } />
|
||||
</div>
|
||||
</div>
|
||||
@@ -1419,14 +1430,7 @@ function _mapStateToProps(state, ownProps) {
|
||||
}
|
||||
}
|
||||
|
||||
let { toolbarButtons } = ownProps;
|
||||
const stateToolbarButtons = getToolbarButtons(state);
|
||||
|
||||
if (toolbarButtons) {
|
||||
toolbarButtons = toolbarButtons.filter(name => isToolbarButtonEnabled(name, stateToolbarButtons));
|
||||
} else {
|
||||
toolbarButtons = stateToolbarButtons;
|
||||
}
|
||||
const toolbarButtons = ownProps.toolbarButtons || getToolbarButtons(state);
|
||||
|
||||
return {
|
||||
_backgroundType: state['features/virtual-background'].backgroundType,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
export function isAudioMuteButtonDisabled(state: Object) {
|
||||
const { available, muted, unmuteBlocked } = state['features/base/media'].audio;
|
||||
const { startSilent } = state['features/base/config'];
|
||||
|
||||
return !available || (muted && unmuteBlocked);
|
||||
return Boolean(!available || startSilent || (muted && unmuteBlocked));
|
||||
}
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
export const LAYOUTS = {
|
||||
HORIZONTAL_FILMSTRIP_VIEW: 'horizontal-filmstrip-view',
|
||||
TILE_VIEW: 'tile-view',
|
||||
VERTICAL_FILMSTRIP_VIEW: 'vertical-filmstrip-view'
|
||||
VERTICAL_FILMSTRIP_VIEW: 'vertical-filmstrip-view',
|
||||
STAGE_FILMSTRIP_VIEW: 'stage-filmstrip-view'
|
||||
};
|
||||
|
||||
@@ -7,11 +7,7 @@ import {
|
||||
getParticipantCount,
|
||||
pinParticipant
|
||||
} from '../base/participants';
|
||||
import {
|
||||
DEFAULT_MAX_COLUMNS,
|
||||
ABSOLUTE_MAX_COLUMNS
|
||||
} from '../filmstrip/constants';
|
||||
import { getNumberOfPartipantsForTileView } from '../filmstrip/functions.web';
|
||||
import { isStageFilmstripAvailable } from '../filmstrip/functions';
|
||||
import { isVideoPlaying } from '../shared-video/functions';
|
||||
import { VIDEO_QUALITY_LEVELS } from '../video-quality/constants';
|
||||
|
||||
@@ -45,54 +41,16 @@ export function getCurrentLayout(state: Object) {
|
||||
if (shouldDisplayTileView(state)) {
|
||||
return LAYOUTS.TILE_VIEW;
|
||||
} else if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||
if (isStageFilmstripAvailable(state, 2)) {
|
||||
return LAYOUTS.STAGE_FILMSTRIP_VIEW;
|
||||
}
|
||||
|
||||
return LAYOUTS.VERTICAL_FILMSTRIP_VIEW;
|
||||
}
|
||||
|
||||
return LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many columns should be displayed in tile view. The number
|
||||
* returned will be between 1 and 7, inclusive.
|
||||
*
|
||||
* @param {Object} state - The redux store state.
|
||||
* @param {number} width - Custom width to use for calculation.
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getMaxColumnCount() {
|
||||
const configuredMax = (typeof interfaceConfig === 'undefined'
|
||||
? DEFAULT_MAX_COLUMNS
|
||||
: interfaceConfig.TILE_VIEW_MAX_COLUMNS) || DEFAULT_MAX_COLUMNS;
|
||||
|
||||
return Math.min(Math.max(configuredMax, 1), ABSOLUTE_MAX_COLUMNS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cell count dimensions for tile view. Tile view tries to uphold
|
||||
* equal count of tiles for height and width, until maxColumn is reached in
|
||||
* which rows will be added but no more columns.
|
||||
*
|
||||
* @param {Object} state - The redux store state.
|
||||
* @param {boolean} stageFilmstrip - Whether the dimensions should be calculated for the stage filmstrip.
|
||||
* @returns {Object} An object is return with the desired number of columns,
|
||||
* rows, and visible rows (the rest should overflow) for the tile view layout.
|
||||
*/
|
||||
export function getNotResponsiveTileViewGridDimensions(state: Object, stageFilmstrip: boolean = false) {
|
||||
const maxColumns = getMaxColumnCount(state);
|
||||
const { activeParticipants } = state['features/filmstrip'];
|
||||
const numberOfParticipants = stageFilmstrip ? activeParticipants.length : getNumberOfPartipantsForTileView(state);
|
||||
const columnsToMaintainASquare = Math.ceil(Math.sqrt(numberOfParticipants));
|
||||
const columns = Math.min(columnsToMaintainASquare, maxColumns);
|
||||
const rows = Math.ceil(numberOfParticipants / columns);
|
||||
const minVisibleRows = Math.min(maxColumns, rows);
|
||||
|
||||
return {
|
||||
columns,
|
||||
minVisibleRows,
|
||||
rows
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the UI layout should be in tile view. Tile view
|
||||
* is determined by more than just having the tile view setting enabled, as
|
||||
1
react/features/video-layout/functions.native.js
Normal file
1
react/features/video-layout/functions.native.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from './functions.any';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user