Compare commits

..

27 Commits
4617 ... 4635

Author SHA1 Message Date
Saúl Ibarra Corretgé
2763c2f5c9 fix(twa) update template
Set version to 1.0.0 with a very large version code so it's automatically kept
around when pushing new versions.

Additionally drop some no longer needed icon assets (bubblewrap did this).
2021-01-12 14:56:04 +01:00
Saúl Ibarra Corretgé
1ec8f70d55 fix(libre-build) skip spurious Firebase and GCM dependencies
Fixes: https://github.com/jitsi/jitsi-meet/issues/8353
2021-01-12 14:55:47 +01:00
Saúl Ibarra Corretgé
916208a5ff fix(libre-build) update react-native-device-info to 8.0.0
Will now work even if installreferrer or GCM are missing.
2021-01-12 14:55:47 +01:00
Mihai-Andrei Uscat
43e655b619 feat(chat): Improve responsiveness.
* Fix toolbox buttons not displaying properly when chat is open.
* Open chat in fullscreen dialog past custom thresholds when mobile/desktop toolbox would become unusable due to chat
* Remove mobile chat check when displaying toolbox
2021-01-12 15:24:55 +02:00
Avram Tudor
1ab0f1993a Merge pull request #8377 from jitsi/tavram/notifications
feat(external_api) allow notifications to be configured
2021-01-12 14:52:31 +02:00
Tudor-Ovidiu Avram
4cb7ebce70 feat(external_api) allow initial gUM requests to be disabled 2021-01-12 06:02:44 -06:00
hmuresan
0a5910f0b3 feat(external_api) set and cancel private chat through external API
- allow managing chat through API when chat button is not present on UI
2021-01-12 06:01:10 -06:00
Tudor-Ovidiu Avram
d91c546a1e feat(external_api) allow notifications to be configured 2021-01-12 11:22:53 +02:00
bgrozev
b6f7f8fba7 Remove the "focus" external component, use client_proxy instead. (#8381)
* feat: Add mod_client_proxy and mod_roster_command.

Taken from prosody-modules 4317:456b9f608fcf with the
mod_roster_command patch applied.

* feat: Use mod_client_proxy to proxy to jicofo.
2021-01-11 15:45:00 -06:00
Pawel Domas
6ebe2c2809 audio output selection in safari blocks the UI
It appears that at the time of this writing, creating audio tracks blocks
the browser's main thread for a long time on safari. Wasn't able to confirm
which part of track creation does the blocking exactly, but not creating
the tracks seems to help and makes the UI much more responsive.
2021-01-11 14:17:29 -05:00
Saygun ICYUZ
067610b3fd feat(recording) - Show recording started notification to the initiator (#8359)
* Show recording started notification to the initiator

* Translate 'recording.on' language key for English and Turkish
Translate 'liveStreaming.on' language key for English and Turkish
2021-01-11 08:21:33 -06:00
Avram Tudor
6f5534fcb6 Merge pull request #8352 from jitsi/tavram/allow-tileview-disabling
feat(jaas) allow tile view to be disabled
2021-01-08 16:03:21 +02:00
Tudor-Ovidiu Avram
dd8b220ff9 feat(jaas) allow tile view to be disabled 2021-01-08 12:00:13 +02:00
Avram Tudor
80d789879c Merge pull request #8356 from jitsi/tavram/top
feat(jaas) add config for displaying participants stats and conferenc…
2021-01-08 11:57:23 +02:00
Tudor-Ovidiu Avram
d49c5a6d8c feat(jaas) add config for displaying participants stats and conference subject 2021-01-08 10:21:07 +02:00
Mejans
9268255ca8 i18n: update Occitan language (#8346)
* Update main-oc.json

* Update languages-oc.json
2021-01-07 14:51:21 -06:00
Pawel Domas
a0806716ae fix(JitsiStreamPresenterEffect): frozen on Safari
Canvas rendering does not work as expected on Safari - the image stays
still on the first frame. Calling play() on the video tags seems to help.
2021-01-07 15:10:27 -05:00
Дамян Минков
3677a2f769 feat: Skip p2p when the participant is jigasi.
* feat: Skip p2p when the participant is jigasi.

1fd7256553...87c6e37475
2021-01-07 09:08:36 -06:00
Avram Tudor
3881da5db9 Merge pull request #8354 from jitsi/tavram/fix-filmstrip
fix(filmstrip) fix button not considering interface config settings
2021-01-07 15:20:22 +02:00
Tudor-Ovidiu Avram
35a586df3c fix(filmstrip) fix button not considering interface config settings 2021-01-07 14:53:11 +02:00
Saúl Ibarra Corretgé
dc5a776123 fix(ios) fix drag handle not rendering with latest react-native-svg
Fill must be properly specified.
2021-01-07 12:02:41 +01:00
Saúl Ibarra Corretgé
a3c6e690dd chore(deps) update react-native-svg to latest
Fixes icons not rendering on iOS 11.
2021-01-07 12:02:41 +01:00
Saúl Ibarra Corretgé
a1c197c73c fix(ios) fix crash on startup on iOS 11 2021-01-07 12:02:41 +01:00
damencho
e8c0c03e49 chore(deps) lib-jitsi-meet@latest
*  feat: Skips using disco-info for features. (#1450)
* sdp: improve sdp matching for simulcast lines (#1452)
* sdp: add missing colon to findLines calls (#1447)

310983c5b0...1fd7256553
2021-01-06 09:32:26 -06:00
Saúl Ibarra Corretgé
4798e0271b chore(android,ios) raise versions 2021-01-06 16:21:44 +01:00
Asif
cd29f10fa8 feat: emit raise hand event to external API (#8312)
* Expose raise hand event to external application

* Fix linting issues

* fix the app non existing issue
2021-01-06 08:49:10 -06:00
Pawel Domas
97dc07810c fix(setAudioOutputDeviceId): check if supported 2021-01-06 08:31:53 -06:00
78 changed files with 1064 additions and 260 deletions

View File

@@ -25,5 +25,5 @@ android.enableDexingArtifactTransform.desugaring=false
android.useAndroidX=true
android.enableJetifier=true
appVersion=20.6.0
sdkVersion=2.12.0
appVersion=21.0.0
sdkVersion=3.0.0

View File

@@ -47,7 +47,14 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.squareup.duktape:duktape-android:1.3.0'
if (!rootProject.ext.libreBuild) {
if (rootProject.ext.libreBuild) {
implementation(project(':react-native-device-info')) {
exclude group: 'com.google.firebase'
exclude group: 'com.google.android.gms'
exclude group: 'com.android.installreferrer'
}
} else {
implementation project(':react-native-device-info')
implementation(project(":react-native-google-signin")) {
exclude group: 'com.google.android.gms'
exclude group: 'androidx'
@@ -59,7 +66,6 @@ dependencies {
implementation project(':react-native-calendar-events')
implementation project(':react-native-community_netinfo')
implementation project(':react-native-default-preference')
implementation project(':react-native-device-info')
implementation project(':react-native-immersive')
implementation project(':react-native-keep-awake')
implementation project(':react-native-linear-gradient')

View File

@@ -472,8 +472,8 @@ export default {
*/
createInitialLocalTracks(options = {}) {
const errors = {};
const initialDevices = [ 'audio' ];
const requestedAudio = true;
const initialDevices = config.disableInitialGUM ? [] : [ 'audio' ];
const requestedAudio = !config.disableInitialGUM;
let requestedVideo = false;
// Always get a handle on the audio input device so that we have statistics even if the user joins the
@@ -484,19 +484,22 @@ export default {
this.muteAudio(true, true);
}
if (!options.startWithVideoMuted
if (!config.disableInitialGUM
&& !options.startWithVideoMuted
&& !options.startAudioOnly
&& !options.startScreenSharing) {
initialDevices.push('video');
requestedVideo = true;
}
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,
browserName =>
APP.store.dispatch(
mediaPermissionPromptVisibilityChanged(true, browserName))
);
if (!config.disableInitialGUM) {
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,
browserName =>
APP.store.dispatch(
mediaPermissionPromptVisibilityChanged(true, browserName))
);
}
let tryCreateLocalTracks;

View File

@@ -336,6 +336,10 @@ var config = {
// will be joined when no room is specified.
enableWelcomePage: true,
// Disable initial browser getUserMedia requests.
// This is useful for scenarios where users might want to start a conference for screensharing only
// disableInitialGUM: false,
// Enabling the close page will ignore the welcome page redirection when
// a call is hangup.
// enableClosePage: false,
@@ -623,9 +627,18 @@ var config = {
// otherwise the app doesn't render it.
// moderatedRoomServiceUrl: 'https://moderated.jitsi-meet.example.com',
// If true, tile view will not be enabled automatically when the participants count threshold is reached.
// disableTileView: true,
// Hides the conference subject
// hideConferenceSubject: true
// Hides the conference timer.
// hideConferenceTimer: true,
// Hides the participants stats
// hideParticipantsStats: true
// Sets the conference subject
// subject: 'Conference Subject',
@@ -682,6 +695,65 @@ var config = {
ignoreStartMuted
*/
/**
Use this array to configure which notifications will be shown to the user
The items correspond to the title or description key of that notification
Some of these notifications also depend on some other internal logic to be displayed or not,
so adding them here will not ensure they will always be displayed
A falsy value for this prop will result in having all notifications enabled (e.g null, undefined, false)
*/
// notifications: [
// 'connection.CONNFAIL', // shown when the connection fails,
// 'dialog.cameraNotSendingData', // shown when there's no feed from user's camera
// 'dialog.kickTitle', // shown when user has been kicked
// 'dialog.liveStreaming', // livestreaming notifications (pending, on, off, limits)
// 'dialog.lockTitle', // shown when setting conference password fails
// 'dialog.maxUsersLimitReached', // shown when maximmum users limit has been reached
// 'dialog.micNotSendingData', // shown when user's mic is not sending any audio
// 'dialog.passwordNotSupportedTitle', // shown when setting conference password fails due to password format
// 'dialog.recording', // recording notifications (pending, on, off, limits)
// 'dialog.remoteControlTitle', // remote control notifications (allowed, denied, start, stop, error)
// 'dialog.reservationError',
// 'dialog.serviceUnavailable', // shown when server is not reachable
// 'dialog.sessTerminated', // shown when there is a failed conference session
// 'dialog.tokenAuthFailed', // show when an invalid jwt is used
// 'dialog.transcribing', // transcribing notifications (pending, off)
// 'dialOut.statusMessage', // shown when dial out status is updated.
// 'liveStreaming.busy', // shown when livestreaming service is busy
// 'liveStreaming.failedToStart', // shown when livestreaming fails to start
// 'liveStreaming.unavailableTitle', // shown when livestreaming service is not reachable
// 'lobby.joinRejectedMessage', // shown when while in a lobby, user's request to join is rejected
// 'lobby.notificationTitle', // shown when lobby is toggled and when join requests are allowed / denied
// 'localRecording.localRecording', // shown when a local recording is started
// 'notify.disconnected', // shown when a participant has left
// 'notify.grantedTo', // shown when moderator rights were granted to a participant
// 'notify.invitedOneMember', // shown when 1 participant has been invited
// 'notify.invitedThreePlusMembers', // shown when 3+ participants have been invited
// 'notify.invitedTwoMembers', // shown when 2 participants have been invited
// 'notify.kickParticipant', // shown when a participant is kicked
// 'notify.mutedRemotelyTitle', // shown when user is muted by a remote party
// 'notify.mutedTitle', // shown when user has been muted upon joining,
// 'notify.newDeviceAudioTitle', // prompts the user to use a newly detected audio device
// 'notify.newDeviceCameraTitle', // prompts the user to use a newly detected camera
// 'notify.passwordRemovedRemotely', // shown when a password has been removed remotely
// 'notify.passwordSetRemotely', // shown when a password has been set remotely
// 'notify.raisedHand', // shown when a partcipant used raise hand,
// 'notify.startSilentTitle', // shown when user joined with no audio
// 'prejoin.errorDialOut',
// 'prejoin.errorDialOutDisconnected',
// 'prejoin.errorDialOutFailed',
// 'prejoin.errorDialOutStatus',
// 'prejoin.errorStatusCode',
// 'prejoin.errorValidation',
// 'recording.busy', // shown when recording service is busy
// 'recording.failedToStart', // shown when recording fails to start
// 'recording.unavailableTitle', // shown when recording service is not reachable
// 'toolbar.noAudioSignalTitle', // shown when a broken mic is detected
// 'toolbar.noisyAudioInputTitle', // shown when noise is detected for the current microphone
// 'toolbar.talkWhileMutedPopup', // shown when user tries to speak while muted
// 'transcribing.failedToStart' // shown when transcribing fails to start
// ]
// Allow all above example options to include a trailing comma and
// prevent fear when commenting out the last value.

View File

@@ -17,6 +17,7 @@ textarea {
html {
height: 100%;
width: 100%;
overflow: hidden;
}
body {
@@ -201,74 +202,3 @@ form {
background: rgba(0, 0, 0, .5);
border-radius: 4px;
}
.desktop-browser {
@media only screen and (max-width: $smallScreen) {
.watermark {
width: 20%;
height: 20%;
}
.new-toolbox {
.toolbox-content {
.button-group-center, .button-group-left, .button-group-right {
.toolbox-button {
.toolbox-icon {
width: 28px;
height: 28px;
svg {
width: 18px;
height: 18px;
}
}
&:nth-child(2) {
.toolbox-icon {
width: 30px;
height: 30px;
}
}
}
}
}
}
}
@media only screen and (max-width: $verySmallScreen) {
#videoResolutionLabel {
display: none;
}
.vertical-filmstrip .filmstrip {
display: none;
}
.new-toolbox {
.toolbox-content {
.button-group-center, .button-group-left, .button-group-right {
.settings-button-small-icon {
display: none;
}
.toolbox-button {
.toolbox-icon {
width: 18px;
height: 18px;
svg {
width: 12px;
height: 12px;
}
}
&:nth-child(2) {
.toolbox-icon {
width: 20px;
height: 20px;
}
}
}
}
}
}
.chrome-extension-banner {
display: none;
}
}
}

View File

@@ -379,3 +379,31 @@
}
}
}
.chat-dialog {
display: flex;
flex-direction: column;
height: 100%;
margin-top: -5px; // Margin set by atlaskit.
&-header {
display: flex;
justify-content: space-between;
margin: 16px 16px 24px;
width: calc(100% - 32px);
box-sizing: border-box;
color: #fff;
font-weight: 600;
font-size: 24px;
line-height: 32px;
.jitsi-icon > svg {
cursor: pointer;
fill: #A4B8D1;
}
}
#chatconversation {
width: 100%;
}
}

View File

@@ -1,3 +1,73 @@
@mixin small-button-size() {
.new-toolbox {
.toolbox-content {
.button-group-center, .button-group-left, .button-group-right {
.toolbox-button {
.toolbox-icon {
width: 28px;
height: 28px;
svg {
width: 18px;
height: 18px;
}
}
&:nth-child(2) {
.toolbox-icon {
width: 30px;
height: 30px;
}
}
}
}
}
}
}
@mixin very-small-button-size() {
.new-toolbox {
.toolbox-content {
.button-group-center, .button-group-left, .button-group-right {
.settings-button-small-icon {
display: none;
}
.toolbox-button {
.toolbox-icon {
width: 18px;
height: 18px;
svg {
width: 12px;
height: 12px;
}
}
&:nth-child(2) {
.toolbox-icon {
width: 20px;
height: 20px;
}
}
}
}
}
}
}
@mixin full-size-modal-positioner() {
height: 100%;
left: 0;
position: fixed;
top: 0;
max-width: 100%;
width: 100%;
}
@mixin full-size-modal-dialog() {
height: 100%;
max-height: 100%;
border-radius: 0;
}
@media only screen and (max-width: $verySmallScreen) {
.welcome {
display: block;
@@ -65,3 +135,65 @@
}
}
}
.desktop-browser {
@media only screen and (max-width: $smallScreen) {
@include small-button-size();
}
@media only screen and (max-width: $verySmallScreen) {
@include very-small-button-size();
#videoResolutionLabel {
display: none;
}
.vertical-filmstrip .filmstrip {
display: none;
}
.chrome-extension-banner {
display: none;
}
}
&.shift-right {
@media only screen and (max-width: $smallScreen + $sidebarWidth) {
@include small-button-size()
}
@media only screen and (max-width: $verySmallScreen + $sidebarWidth) {
@include very-small-button-size();
#videoResolutionLabel {
display: none;
}
.vertical-filmstrip .filmstrip {
display: none;
}
.chrome-extension-banner {
display: none;
}
}
}
}
@media (min-width: 480px) and (max-width: 580px) {
.shift-right [class^="Modal__PositionerAbsolute"] {
@include full-size-modal-positioner();
}
.shift-right [class^="Modal__Dialog-"] {
@include full-size-modal-dialog();
}
}
@media (min-width: 580px) and (max-width: 680px) {
.mobile-browser {
&.shift-right [class^="Modal__PositionerAbsolute"] {
@include full-size-modal-positioner();
}
&.shift-right [class^="Modal__Dialog-"] {
@include full-size-modal-dialog();
}
}
}

View File

@@ -142,6 +142,21 @@ case "$1" in
echo -e " admins = { \"$JICOFO_AUTH_USER@auth.$JVB_HOSTNAME\", \"jvb@auth.$JVB_HOSTNAME\" }" >> $PROSODY_HOST_CONFIG
fi
# Convert the old focus component config to the new one.
# Old:
# Component "focus.jitmeet.example.com"
# component_secret = "focusSecret"
# New:
# Component "focus.jitmeet.example.com" "client_proxy"
# target_address = "focus@auth.jitmeet.example.com"
if grep -q "Component \"focus.$JVB_HOSTNAME\"" $PROSODY_HOST_CONFIG && ! grep "Component \"focus.$JVB_HOSTNAME\" \"client_proxy\"" $PROSODY_HOST_CONFIG ;then
sed -i -e "s/Component \"focus.$JVB_HOSTNAME\"/Component \"focus.$JVB_HOSTNAME\" \"client_proxy\"\n target_address = \"$JICOFO_AUTH_USER@auth.$JVB_HOSTNAME\"/" $PROSODY_HOST_CONFIG
PROSODY_CONFIG_PRESENT="false"
fi
# Make sure the focus@auth user's roster includes the proxy component (this is idempotent)
prosodyctl mod_roster_command subscribe focus.$JVB_HOSTNAME $JICOFO_AUTH_USER@auth.$JVB_HOSTNAME
if [ ! -f /var/lib/prosody/$JVB_HOSTNAME.crt ]; then
# prosodyctl takes care for the permissions
# echo for using all default values

View File

@@ -77,8 +77,9 @@ Component "internal.auth.jitmeet.example.com" "muc"
VirtualHost "auth.jitmeet.example.com"
authentication = "internal_plain"
Component "focus.jitmeet.example.com"
component_secret = "focusSecret"
-- Proxy to jicofo's user JID, so that it doesn't have to register as a component.
Component "focus.jitmeet.example.com" "client_proxy"
target_address = "focusUser@auth.jitmeet.example.com"
Component "speakerstats.jitmeet.example.com" "speakerstats_component"
muc_component = "conference.jitmeet.example.com"

View File

@@ -354,7 +354,7 @@ PODS:
- React
- RNDefaultPreference (1.4.2):
- React
- RNDeviceInfo (7.3.1):
- RNDeviceInfo (8.0.0):
- React-Core
- RNGoogleSignin (3.0.1):
- GoogleSignIn (~> 5.0.0)
@@ -364,7 +364,7 @@ PODS:
- RNSound/Core (= 0.11.0)
- RNSound/Core (0.11.0):
- React
- RNSVG (10.1.0):
- RNSVG (12.1.0):
- React
- RNWatch (0.4.3):
- React
@@ -577,10 +577,10 @@ SPEC CHECKSUMS:
ReactCommon: 18014e1d98dbeb9141e935cfe35fc93bd511ffb6
RNCAsyncStorage: bc2f81cc1df90c267ce9ed30bb2dbc93b945a8ee
RNDefaultPreference: 56a405ce61033ac77b95004dccd7ac54c2eb50d1
RNDeviceInfo: 57bb2806fb7bd982a1434e9f0b4e6a6ab1f6702e
RNDeviceInfo: 72ded653ce636b3f03571e90bed99309a714944e
RNGoogleSignin: 39336070b35fc4cea6a98cf111e00480317be0ae
RNSound: c980916b596cc15c8dcd2f6ecd3b13c4881dbe20
RNSVG: 069864be08c9fe065a2cf7e63656a34c78653c99
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c

View File

@@ -715,6 +715,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 609CB2080B75F75A89923F3D /* Pods-JitsiMeet.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDebug;
CODE_SIGN_ENTITLEMENTS = app.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -744,6 +745,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = FC040BBED70876444D89E91C /* Pods-JitsiMeet.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconRelease;
CODE_SIGN_ENTITLEMENTS = app.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";

View File

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

View File

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

View File

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

View File

@@ -28,6 +28,8 @@
"kab": "Cabil",
"ko": "Corean",
"lt": "Lituanian",
"ml": "Malaialam",
"lv": "Leton",
"nl": "Neerlandés",
"oc": "Occitan",
"pl": "Polonés",

View File

@@ -380,7 +380,7 @@
"getStreamKeyManually": "We werent able to fetch any live streams. Try getting your live stream key from YouTube.",
"invalidStreamKey": "Live stream key may be incorrect.",
"off": "Live Streaming stopped",
"on": "Live Streaming",
"on": "Live Streaming started",
"pending": "Starting Live Stream…",
"serviceName": "Live Streaming service",
"signedInAs": "You are currently signed in as:",
@@ -497,7 +497,7 @@
"live": "LIVE",
"loggedIn": "Logged in as {{userName}}",
"off": "Recording stopped",
"on": "Recording",
"on": "Recording started",
"pending": "Preparing to record the meeting…",
"rec": "REC",
"serviceDescription": "Your recording will be saved by the recording service",

View File

@@ -314,7 +314,8 @@
"e2eeDescription": "Lo chiframent del cap a la fin es actualament EXPERIMENTALA. Mercés de gardar a l'esperit qu'activar lo chiframent del cap a la fin desactivarà en efièch los servicis costat servidor coma: l'enregistrament, la difusion en dirècte e las participacions telefonicas. Remembratz tanben que la conferéncia foncionarà pas que per lo monde que participan amb un navigador compatible amb los fluxes inseribles.",
"screenSharingFailed": "Ops! Quicòm a trucat, avèm pas pogut començar lo partiment d'ecran!",
"e2eeWarning": "AVERTIMENT: pas totes los participants d'aquesta conferéncia semblan poder suportar lo chiframent del cap a la fin. Se l'activatz poiràn pas vos veire nimai vos entendre.",
"muteEveryoneElseDialog": "Un còp mut, poiretz pas mai lo tornar la paraula, mas la se pòdon tornar quora vòlgan."
"muteEveryoneElseDialog": "Un còp mut, poiretz pas mai lo tornar la paraula, mas la se pòdon tornar quora vòlgan.",
"user": "utilizaire"
},
"dialOut": {
"statusMessage": "ara es {{status}}"
@@ -549,7 +550,8 @@
"signIn": "Connexion",
"signOut": "Se desconnectar",
"unavailable": "Ops! Lo {{serviceName}} es pas disponible pel moment. Sèm a reglar aqueste problèma. Mercés de tornar ensajar mai tard.",
"unavailableTitle": "Enregistrament indisponible"
"unavailableTitle": "Enregistrament indisponible",
"serviceDescriptionCloud": "Enregistrament distant"
},
"sectionList": {
"pullToRefresh": "Tirar per actualizar"
@@ -616,7 +618,8 @@
},
"startupoverlay": {
"policyText": " ",
"title": "{{app}} a besonh d'utilizar vòstre microfòn e camèra."
"title": "{{app}} a besonh d'utilizar vòstre microfòn e camèra.",
"genericTitle": "Aquesta reünion requerís l'utilizacion del microfòn e de la camèra."
},
"suspendedoverlay": {
"rejoinKeyTitle": "Tornar participar",
@@ -666,7 +669,8 @@
"security": "Opcions de seguretat",
"embedMeeting": "Conferéncia integrada",
"grantModerator": "Passar moderator",
"lobbyButton": "Activar/Desactivar mòde sala d'espèra"
"lobbyButton": "Activar/Desactivar mòde sala d'espèra",
"toggleFilmstrip": "Bascular mòde benda film"
},
"addPeople": "Ajustar de monde a vòstra sonada",
"audioOnlyOff": "Desactivar lo mòde connexion febla",
@@ -791,7 +795,9 @@
"remoteControl": "Contraròtle alonhat",
"show": "Mostrar davant",
"videomute": "Lo participant a arrestat la camèra",
"domuteOthers": "Rendre mut totes los autres"
"domuteOthers": "Rendre mut totes los autres",
"connectionInfo": "Info connexion",
"grantModerator": "Nomenar moderator"
},
"welcomepage": {
"accessibilityLabel": {
@@ -827,7 +833,8 @@
"jitsiOnMobile": "Jitsi sus mobil telecargatz nòstra aplicacion e començatz de conferéncias de pertot",
"moderatedMessage": "O <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">reservatz una URL de conferéncia</a> a l'avança ont sètz l'unic moderator.",
"jitsiMeet": "Jitsi Meet",
"secureMeetings": "Conferéncias seguras e de nauta qualitat"
"secureMeetings": "Conferéncias seguras e de nauta qualitat",
"headerTitle": "Jitsi Meet"
},
"helpView": {
"header": "Centre dajuda"
@@ -884,7 +891,14 @@
"videoFreezing": "Nos esperam a veire vòstra vidèo se gelar, venir negra e se pixelizar."
},
"premeeting": "Preconferéncia",
"errorMissingName": "Mercés de picar vòstre nom per rejónher la conferéncia"
"errorMissingName": "Mercés de picar vòstre nom per rejónher la conferéncia",
"errorDialOutDisconnected": "Impossible de sonar aqueste numèro. Desconnectat",
"errorDialOutStatus": "Error en recuperant lestat de la sonada sortissenta",
"errorDialOut": "Impossible de sonar aqueste numèro",
"errorDialOutFailed": "Impossible de sonar aqueste numèro. La sonada a fracassat",
"dialInMeeting": "Participatz a la reünion",
"dialInPin": "Participar a la reünion e picar lo còdi PIN :",
"iWantToDialIn": "Vòli me connectar"
},
"lobby": {
"reject": "Regetar",

View File

@@ -404,7 +404,7 @@
"invalidStreamKey": "Canlı akış anahtarı yanlış olabilir.",
"off": "Canlı Akış durduruldu",
"offBy": "{{name}} canlı akışı durdurdu",
"on": "Canlı Akış",
"on": "Canlı Akış başlatıldı",
"onBy": "{{name}} canlı akışı başlattı",
"pending": "Canlı Akış başlatılıyor...",
"serviceName": "Canlı Akış hizmeti",
@@ -564,7 +564,7 @@
"live": "CANLI",
"loggedIn": "{{userName}} olarak giriş yapıldı",
"off": "Kayıt durdu",
"on": "Kaydediliyor",
"on": "Kayıt başladı",
"pending": "Toplantıyı kaydetmeye hazırlanıyor ...",
"rec": "KAYIT",
"serviceDescription": "Kaydınız kayıt hizmeti tarafından kaydedilecektir",
@@ -844,7 +844,7 @@
"sendFeedback": "Geri bildirim gönder",
"terms": "Kurallar",
"title": "Güvenli, tüm özelliklere erişimli ve tamamen ücretsiz görüntülü arama"
},
"lonelyMeetingExperience": {

View File

@@ -416,7 +416,7 @@
"invalidStreamKey": "Live stream key may be incorrect.",
"off": "Live Streaming stopped",
"offBy": "{{name}} stopped the live streaming",
"on": "Live Streaming",
"on": "Live Streaming started",
"onBy": "{{name}} started the live streaming",
"pending": "Starting Live Stream...",
"serviceName": "Live Streaming service",
@@ -596,7 +596,7 @@
"loggedIn": "Logged in as {{userName}}",
"off": "Recording stopped",
"offBy": "{{name}} stopped the recording",
"on": "Recording",
"on": "Recording started",
"onBy": "{{name}} started the recording",
"pending": "Preparing to record the meeting...",
"rec": "REC",

View File

@@ -14,7 +14,8 @@ import {
} from '../../react/features/base/conference';
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
import { pinParticipant } from '../../react/features/base/participants';
import { pinParticipant, getParticipantById } from '../../react/features/base/participants';
import { setPrivateMessageRecipient } from '../../react/features/chat/actions';
import {
processExternalDeviceRequest
} from '../../react/features/device-selection/functions';
@@ -330,6 +331,24 @@ function initCommands() {
} else {
logger.error('No recording or streaming session found');
}
},
'initiate-private-chat': participantId => {
const state = APP.store.getState();
const participant = getParticipantById(state, participantId);
if (participant) {
const { isOpen: isChatOpen } = state['features/chat'];
if (!isChatOpen) {
APP.UI.toggleChat();
}
APP.store.dispatch(setPrivateMessageRecipient(participant));
} else {
logger.error('No participant found for the given participantId');
}
},
'cancel-private-chat': () => {
APP.store.dispatch(setPrivateMessageRecipient());
}
};
transport.on('event', ({ data, name }) => {
@@ -1022,6 +1041,21 @@ class API {
});
}
/**
* Notify external application (if API is enabled) that user updated their hand raised.
*
* @param {string} id - User id.
* @param {boolean} handRaised - Whether user has raised hand.
* @returns {void}
*/
notifyRaiseHandUpdated(id: string, handRaised: boolean) {
this._sendEvent({
name: 'raise-hand-updated',
handRaised,
id
});
}
/**
* Disposes the allocated resources.
*

View File

@@ -28,11 +28,13 @@ const ALWAYS_ON_TOP_FILENAMES = [
*/
const commands = {
avatarUrl: 'avatar-url',
cancelPrivateChat: 'cancel-private-chat',
displayName: 'display-name',
e2eeKey: 'e2ee-key',
email: 'email',
toggleLobby: 'toggle-lobby',
hangup: 'video-hangup',
intiatePrivateChat: 'initiate-private-chat',
muteEveryone: 'mute-everyone',
password: 'password',
pinParticipant: 'pin-participant',
@@ -79,6 +81,7 @@ const events = {
'participant-role-changed': 'participantRoleChanged',
'password-required': 'passwordRequired',
'proxy-connection-event': 'proxyConnectionEvent',
'raise-hand-updated': 'raiseHandUpdated',
'video-ready-to-close': 'readyToClose',
'video-conference-joined': 'videoConferenceJoined',
'video-conference-left': 'videoConferenceLeft',

30
package-lock.json generated
View File

@@ -7198,18 +7198,18 @@
}
},
"css-tree": {
"version": "1.0.0-alpha.39",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz",
"integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz",
"integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==",
"requires": {
"mdn-data": "2.0.6",
"mdn-data": "2.0.14",
"source-map": "^0.6.1"
},
"dependencies": {
"mdn-data": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz",
"integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA=="
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow=="
},
"source-map": {
"version": "0.6.1",
@@ -10816,8 +10816,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#310983c5b0398d213a2ca054c2eb0e9797fa1ae0",
"from": "github:jitsi/lib-jitsi-meet#310983c5b0398d213a2ca054c2eb0e9797fa1ae0",
"version": "github:jitsi/lib-jitsi-meet#87c6e374755718fdb0804c2c798ea4bc832f4fca",
"from": "github:jitsi/lib-jitsi-meet#87c6e374755718fdb0804c2c798ea4bc832f4fca",
"requires": {
"@jitsi/js-utils": "1.0.2",
"@jitsi/sdp-interop": "1.0.3",
@@ -14199,9 +14199,9 @@
"integrity": "sha512-kNhBLv8s6kO2gJJFEKM7qew7oRvJnygjgG1CU2ZEY6SlG5qsRX8z1Ms7z1Oo/XB7fVfyXrAoZDGhIvy+uiByrg=="
},
"react-native-device-info": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-7.3.1.tgz",
"integrity": "sha512-RQP3etbmXsOlcaxHeHNug68nRli02S9iGC7TbaXpkvyyevIuRogfnrI71sWtqmlT91kdpYAOYKmNfRL9LOSKVw=="
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-8.0.0.tgz",
"integrity": "sha512-7/DOEhg8GtyW1hpVtWf8F6RvGLaFaOGmex+IkmiBWQC2uW4NFDcfXm+lMMZnduFavTyUTX7AF6lAM3y286cEfA=="
},
"react-native-immersive": {
"version": "2.0.0",
@@ -14228,9 +14228,9 @@
"integrity": "sha512-Ls9qiNZzW/OLFoI25wfjjAcrf2DZ975hn2vr6U9gyuxi2nooVbzQeFoQS5vQcbCt9QX5NY8ASEEAtlLdIa6KVg=="
},
"react-native-svg": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-10.1.0.tgz",
"integrity": "sha512-mgo6CshQIQrDDBVUPqJK/iOsJEdlagk7N4q8fyo1sqCiSUP2efpt+AQ1IRXZtHXut210/7TliAamvM59NV0Bzg==",
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-12.1.0.tgz",
"integrity": "sha512-1g9qBRci7man8QsHoXn6tP3DhCDiypGgc6+AOWq+Sy+PmP6yiyf8VmvKuoqrPam/tf5x+ZaBT2KI0gl7bptZ7w==",
"requires": {
"css-select": "^2.1.0",
"css-tree": "^1.0.0-alpha.39"

View File

@@ -56,7 +56,7 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#310983c5b0398d213a2ca054c2eb0e9797fa1ae0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#87c6e374755718fdb0804c2c798ea4bc832f4fca",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.19",
"moment": "2.19.4",
@@ -75,13 +75,13 @@
"react-native-callstats": "3.61.0",
"react-native-collapsible": "1.5.1",
"react-native-default-preference": "1.4.2",
"react-native-device-info": "7.3.1",
"react-native-device-info": "8.0.0",
"react-native-immersive": "2.0.0",
"react-native-keep-awake": "4.0.0",
"react-native-linear-gradient": "2.5.6",
"react-native-sound": "github:jitsi/react-native-sound#3fe5480fce935e888d5089d94a191c7c7e3aa190",
"react-native-splash-screen": "3.2.0",
"react-native-svg": "10.1.0",
"react-native-svg": "12.1.0",
"react-native-svg-transformer": "0.14.3",
"react-native-url-polyfill": "1.2.0",
"react-native-watch-connectivity": "0.4.3",

View File

@@ -80,6 +80,7 @@ export default [
'disableAP',
'disableAudioLevels',
'disableDeepLinking',
'disableInitialGUM',
'disableH264',
'disableHPF',
'disableInviteFunctions',
@@ -91,6 +92,7 @@ export default [
'disableRtx',
'disableSimulcast',
'disableThirdPartyRequests',
'disableTileView',
'displayJids',
'doNotStoreRoom',
'e2eping',
@@ -117,6 +119,8 @@ export default [
'forceTurnRelay',
'gatherStats',
'googleApiApplicationClientID',
'hideConferenceSubject',
'hideParticipantsStats',
'hideConferenceTimer',
'hiddenDomain',
'hideLobbyButton',
@@ -128,6 +132,7 @@ export default [
'liveStreamingEnabled',
'localRecording',
'maxFullResolutionParticipants',
'notifications',
'openBridgeChannel',
'openSharedDocumentOnJoin',
'opusMaxAverageBitrate',

View File

@@ -267,6 +267,12 @@ export function setAudioOutputDeviceId(
logger.debug(`setAudioOutputDevice: ${String(newLabel)}[${newId}]`);
if (!JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
logger.warn('Adjusting audio output is not supported');
return Promise.resolve();
}
return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
.then(() => {
const newSettings = {

View File

@@ -171,10 +171,6 @@ ColorSchemeRegistry.register('BottomSheet', {
underlayColor: ColorPalette.overflowMenuItemUnderlay
},
expandIcon: {
color: schemeColor('icon')
},
/**
* Bottom sheet's base style.
*/

View File

@@ -448,6 +448,10 @@ function _raiseHandUpdated({ dispatch, getState }, conference, participantId, ne
raisedHand
}));
if (typeof APP !== 'undefined') {
APP.API.notifyRaiseHandUpdated(participantId, raisedHand);
}
if (raisedHand) {
dispatch(showNotification({
titleArguments: {

View File

@@ -3,14 +3,21 @@
import { Component } from 'react';
import type { Dispatch } from 'redux';
import { isMobileBrowser } from '../../base/environment/utils';
import { getLocalParticipant } from '../../base/participants';
import { sendMessage, toggleChat } from '../actions';
import { DESKTOP_SMALL_WIDTH_THRESHOLD, MOBILE_SMALL_WIDTH_THRESHOLD } from '../constants';
/**
* The type of the React {@code Component} props of {@code AbstractChat}.
*/
export type Props = {
/**
* Whether the chat is opened in a modal or not (computed based on window width).
*/
_isModal: boolean,
/**
* True if the chat window should be rendered.
*/
@@ -106,6 +113,9 @@ export function _mapStateToProps(state: Object) {
const _localParticipant = getLocalParticipant(state);
return {
_isModal: isMobileBrowser()
? window.innerWidth <= MOBILE_SMALL_WIDTH_THRESHOLD
: window.innerWidth <= DESKTOP_SMALL_WIDTH_THRESHOLD,
_isOpen: isOpen,
_messages: messages,
_showNamePrompt: !_localParticipant.name

View File

@@ -11,6 +11,7 @@ import AbstractChat, {
type Props
} from '../AbstractChat';
import ChatDialog from './ChatDialog';
import ChatInput from './ChatInput';
import DisplayNameForm from './DisplayNameForm';
import MessageContainer from './MessageContainer';
@@ -151,16 +152,25 @@ class Chat extends AbstractChat<Props> {
* @returns {ReactElement | null}
*/
_renderPanelContent() {
const { _isOpen, _showNamePrompt } = this.props;
const ComponentToRender = _isOpen
? (
<>
{ this._renderChatHeader() }
{ _showNamePrompt
? <DisplayNameForm /> : this._renderChat() }
</>
)
: null;
const { _isModal, _isOpen, _showNamePrompt } = this.props;
let ComponentToRender = null;
if (_isOpen) {
if (_isModal) {
ComponentToRender = (
<ChatDialog>
{ _showNamePrompt ? <DisplayNameForm /> : this._renderChat() }
</ChatDialog>
);
} else {
ComponentToRender = (
<>
{ this._renderChatHeader() }
{ _showNamePrompt ? <DisplayNameForm /> : this._renderChat() }
</>
);
}
}
let className = '';
if (_isOpen) {

View File

@@ -0,0 +1,37 @@
// @flow
import React from 'react';
import { Dialog } from '../../../base/dialog';
import Header from './ChatDialogHeader';
type Props = {
/**
* Children of the component.
*/
children: React$Node
}
/**
* Component that renders the content of the chat in a modal.
*
* @returns {React$Element<any>}
*/
function ChatDialog({ children }: Props) {
return (
<Dialog
customHeader = { Header }
disableEnter = { true }
hideCancelButton = { true }
submitDisabled = { true }
titleKey = 'chat.title'>
<div className = 'chat-dialog'>
{children}
</div>
</Dialog>
);
}
export default ChatDialog;

View File

@@ -0,0 +1,42 @@
// @flow
import React from 'react';
import { translate } from '../../../base/i18n';
import { Icon, IconClose } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { toggleChat } from '../../../chat';
type Props = {
/**
* Function to be called when pressing the close button.
*/
onCancel: Function,
/**
* Invoked to obtain translated strings.
*/
t: Function
};
/**
* Custom header of the {@code ChatDialog}.
*
* @returns {React$Element<any>}
*/
function Header({ onCancel, t }: Props) {
return (
<div
className = 'chat-dialog-header'>
{ t('chat.title') }
<Icon
onClick = { onCancel }
src = { IconClose } />
</div>
);
}
const mapDispatchToProps = { onCancel: toggleChat };
export default translate(connect(null, mapDispatchToProps)(Header));

View File

@@ -29,3 +29,7 @@ export const MESSAGE_TYPE_LOCAL = 'local';
* The {@code messageType} of remote messages.
*/
export const MESSAGE_TYPE_REMOTE = 'remote';
export const DESKTOP_SMALL_WIDTH_THRESHOLD = 580;
export const MOBILE_SMALL_WIDTH_THRESHOLD = 680;

View File

@@ -19,7 +19,6 @@ import {
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import { showToolbox } from '../toolbox/actions';
import { isButtonEnabled } from '../toolbox/functions';
import { SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes';
import { addMessage, clearMessages, toggleChat } from './actions';
@@ -152,10 +151,9 @@ StateListenerRegistry.register(
* @returns {void}
*/
function _addChatMsgListener(conference, store) {
if ((typeof APP !== 'undefined' && !isButtonEnabled('chat'))
|| store.getState()['features/base/config'].iAmRecorder) {
// We don't register anything on web if the chat button is not enabled in interfaceConfig
// or we are in iAmRecorder mode
if (store.getState()['features/base/config'].iAmRecorder) {
// We don't register anything on web if we are in iAmRecorder mode
return;
}

View File

@@ -95,11 +95,14 @@ class NavigationBar extends Component<Props> {
* @returns {Props}
*/
function _mapStateToProps(state) {
const { hideConferenceTimer, hideConferenceSubject } = state['features/base/config'];
return {
_conferenceTimerEnabled:
getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true) && !state['features/base/config'].hideConferenceTimer,
getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true) && !hideConferenceTimer,
_meetingName: getConferenceName(state),
_meetingNameEnabled: getFeatureFlag(state, MEETING_NAME_ENABLED, true),
_meetingNameEnabled:
getFeatureFlag(state, MEETING_NAME_ENABLED, true) && !hideConferenceSubject,
_visible: isToolboxVisible(state)
};
}

View File

@@ -25,6 +25,11 @@ type Props = {
*/
_showParticipantCount: boolean,
/**
* Whether the conference subject should be shown or not.
*/
_showSubject: boolean,
/**
* The subject or the of the conference.
* Falls back to conference name.
@@ -51,11 +56,11 @@ class Subject extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { _hideConferenceTimer, _showParticipantCount, _subject, _visible } = this.props;
const { _hideConferenceTimer, _showParticipantCount, _showSubject, _subject, _visible } = this.props;
return (
<div className = { `subject ${_visible ? 'visible' : ''}` }>
<span className = 'subject-text'>{ _subject }</span>
{ _showSubject && <span className = 'subject-text'>{ _subject }</span>}
{ _showParticipantCount && <ParticipantsCount /> }
{ !_hideConferenceTimer && <ConferenceTimer /> }
</div>
@@ -72,16 +77,19 @@ class Subject extends Component<Props> {
* @returns {{
* _hideConferenceTimer: boolean,
* _showParticipantCount: boolean,
* _showSubject: boolean,
* _subject: string,
* _visible: boolean
* }}
*/
function _mapStateToProps(state) {
const participantCount = getParticipantCount(state);
const { hideConferenceTimer, hideConferenceSubject, hideParticipantsStats } = state['features/base/config'];
return {
_hideConferenceTimer: Boolean(state['features/base/config'].hideConferenceTimer),
_showParticipantCount: participantCount > 2,
_hideConferenceTimer: Boolean(hideConferenceTimer),
_showParticipantCount: participantCount > 2 && !hideParticipantsStats,
_showSubject: !hideConferenceSubject,
_subject: getConferenceName(state),
_visible: isToolboxVisible(state) && participantCount > 1
};

View File

@@ -13,6 +13,7 @@ import { translate } from '../../../base/i18n';
import { Icon, IconMenuDown, IconMenuUp } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { dockToolbox } from '../../../toolbox/actions.web';
import { isButtonEnabled } from '../../../toolbox/functions.web';
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
import { setFilmstripHovered, setFilmstripVisible } from '../../actions';
import { shouldRemoteVideosBeVisible } from '../../functions';
@@ -197,7 +198,7 @@ class Filmstrip extends Component <Props> {
let toolbar = null;
if (!this.props._hideToolbar) {
if (!this.props._hideToolbar && isButtonEnabled('filmstrip')) {
toolbar = this._renderToggleButton();
}

View File

@@ -46,14 +46,14 @@ MiddlewareRegistry.register(({ getState, dispatch }) => next => action => {
recordingController.onWarning = (messageKey, messageParams) => {
dispatch(showNotification({
title: i18next.t('localRecording.localRecording'),
titleKey: 'localRecording.localRecording',
description: i18next.t(messageKey, messageParams)
}, 10000));
};
recordingController.onNotify = (messageKey, messageParams) => {
dispatch(showNotification({
title: i18next.t('localRecording.localRecording'),
titleKey: 'localRecording.localRecording',
description: i18next.t(messageKey, messageParams)
}, 10000));
};

View File

@@ -108,20 +108,20 @@ async function _handleNoAudioSignalNotification({ dispatch, getState }, action)
};
}
const notification = showNotification({
const notification = await dispatch(showNotification({
titleKey: 'toolbar.noAudioSignalTitle',
description: <DialInLink />,
descriptionKey,
customActionNameKey,
customActionHandler
});
dispatch(notification);
}));
dispatch(playSound(NO_AUDIO_SIGNAL_SOUND_ID));
// Store the current notification uid so we can check for this state and hide it in case
// a new track was added, thus changing the context of the notification
dispatch(setNoAudioSignalNotificationUid(notification.uid));
if (notification) {
// Store the current notification uid so we can check for this state and hide it in case
// a new track was added, thus changing the context of the notification
dispatch(setNoAudioSignalNotificationUid(notification.uid));
}
});
}

View File

@@ -37,17 +37,18 @@ MiddlewareRegistry.register(store => next => action => {
}
});
conference.on(
JitsiConferenceEvents.NOISY_MIC, () => {
const notification = showNotification({
JitsiConferenceEvents.NOISY_MIC, async () => {
const notification = await dispatch(showNotification({
titleKey: 'toolbar.noisyAudioInputTitle',
descriptionKey: 'toolbar.noisyAudioInputDesc'
});
}));
dispatch(notification);
dispatch(playSound(NOISY_AUDIO_INPUT_SOUND_ID));
// we store the last notification id so we can hide it if the mic is muted
dispatch(setNoisyAudioInputNotificationUid(notification.uid));
if (notification) {
// we store the last notification id so we can hide it if the mic is muted
dispatch(setNoisyAudioInputNotificationUid(notification.uid));
}
});
break;
}

View File

@@ -76,19 +76,23 @@ export function showErrorNotification(props: Object) {
* @param {Object} props - The props needed to show the notification component.
* @param {number} timeout - How long the notification should display before
* automatically being hidden.
* @returns {{
* type: SHOW_NOTIFICATION,
* props: Object,
* timeout: number,
* uid: number
* }}
* @returns {Function}
*/
export function showNotification(props: Object = {}, timeout: ?number) {
return {
type: SHOW_NOTIFICATION,
props,
timeout,
uid: window.Date.now()
return function(dispatch: Function, getState: Function) {
const { notifications } = getState()['features/base/config'];
const shouldDisplay = !notifications
|| notifications.includes(props.descriptionKey)
|| notifications.includes(props.titleKey);
if (shouldDisplay) {
return dispatch({
type: SHOW_NOTIFICATION,
props,
timeout,
uid: window.Date.now()
});
}
};
}

View File

@@ -75,7 +75,7 @@ export function setLiveStreamKey(streamKey: string) {
* @returns {Function}
*/
export function showPendingRecordingNotification(streamType: string) {
return (dispatch: Function) => {
return async (dispatch: Function) => {
const isLiveStreaming
= streamType === JitsiMeetJS.constants.recording.mode.STREAM;
const dialogProps = isLiveStreaming ? {
@@ -85,15 +85,14 @@ export function showPendingRecordingNotification(streamType: string) {
descriptionKey: 'recording.pending',
titleKey: 'dialog.recording'
};
const showNotificationAction = showNotification({
const notification = await dispatch(showNotification({
isDismissAllowed: false,
...dialogProps
});
}));
dispatch(showNotificationAction);
dispatch(_setPendingRecordingNotificationUid(
showNotificationAction.uid, streamType));
if (notification) {
dispatch(_setPendingRecordingNotificationUid(notification.uid, streamType));
}
};
}
@@ -147,11 +146,11 @@ export function showStartedRecordingNotification(streamType: string, participant
= streamType === JitsiMeetJS.constants.recording.mode.STREAM;
const descriptionArguments = { name: participantName };
const dialogProps = isLiveStreaming ? {
descriptionKey: 'liveStreaming.onBy',
descriptionKey: participantName ? 'liveStreaming.onBy' : 'liveStreaming.on',
descriptionArguments,
titleKey: 'dialog.liveStreaming'
} : {
descriptionKey: 'recording.onBy',
descriptionKey: participantName ? 'recording.onBy' : 'recording.on',
descriptionArguments,
titleKey: 'dialog.recording'
};

View File

@@ -155,13 +155,12 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
if (updatedSessionData.status === ON
&& (!oldSessionData || oldSessionData.status !== ON)) {
if (initiator) {
const initiatorName = initiator && getParticipantDisplayName(getState, initiator.getId());
initiatorName && dispatch(showStartedRecordingNotification(mode, initiatorName));
} else if (typeof recordingLimit === 'object') {
if (typeof recordingLimit === 'object') {
// Show notification with additional information to the initiator.
dispatch(showRecordingLimitNotification(mode));
} else {
dispatch(showStartedRecordingNotification(
mode, initiator && getParticipantDisplayName(getState, initiator.getId())));
}

View File

@@ -4,6 +4,7 @@ import React, { Component } from 'react';
import { translate } from '../../../../base/i18n';
import { IconMicrophoneEmpty, IconVolumeEmpty } from '../../../../base/icons';
import JitsiMeetJS from '../../../../base/lib-jitsi-meet';
import { equals } from '../../../../base/redux';
import { createLocalAudioTracks } from '../../../functions';
@@ -11,6 +12,8 @@ import AudioSettingsHeader from './AudioSettingsHeader';
import MicrophoneEntry from './MicrophoneEntry';
import SpeakerEntry from './SpeakerEntry';
const browser = JitsiMeetJS.util.browser;
export type Props = {
/**
@@ -169,6 +172,14 @@ class AudioSettingsContent extends Component<Props, State> {
* @returns {void}
*/
async _setTracks() {
if (browser.isSafari()) {
// It appears that at the time of this writing, creating audio tracks blocks the browser's main thread for
// long time on safari. Wasn't able to confirm which part of track creation does the blocking exactly, but
// not creating the tracks seems to help and makes the UI much more responsive.
return;
}
this._disposeTracks(this.state.audioTracks);
const audioTracks = await createLocalAudioTracks(
@@ -244,9 +255,11 @@ class AudioSettingsContent extends Component<Props, State> {
{this.state.audioTracks.map((data, i) =>
this._renderMicrophoneEntry(data, i),
)}
<AudioSettingsHeader
IconComponent = { IconVolumeEmpty }
text = { t('settings.speakers') } />
{ outputDevices.length > 0 && (
<AudioSettingsHeader
IconComponent = { IconVolumeEmpty }
text = { t('settings.speakers') } />)
}
{outputDevices.map((data, i) =>
this._renderSpeakerEntry(data, i),
)}

View File

@@ -140,7 +140,7 @@ export default class MicrophoneEntry extends Component<Props, State> {
*
* @inheritdoc
*/
compmonentWillUnmount() {
componentWillUnmount() {
this._stopListening(this.props.jitsiTrack);
}
@@ -150,7 +150,7 @@ export default class MicrophoneEntry extends Component<Props, State> {
* @inheritdoc
*/
render() {
const { children, hasError, isSelected } = this.props;
const { children, hasError, isSelected, jitsiTrack } = this.props;
return (
<div
@@ -161,10 +161,11 @@ export default class MicrophoneEntry extends Component<Props, State> {
isSelected = { isSelected }>
{children}
</AudioSettingsEntry>
<Meter
{ Boolean(jitsiTrack) && <Meter
className = 'audio-preview-meter-mic'
isDisabled = { hasError }
level = { this.state.level } />
}
</div>
);
}

View File

@@ -59,6 +59,9 @@ export default class JitsiStreamPresenterEffect {
this._videoElement.autoplay = true;
this._videoElement.srcObject = videoStream;
// autoplay is not enough to start the video on Safari, it's fine to call play() on other platforms as well
this._videoElement.play();
// set the style attribute of the div to make it invisible
videoDiv.style.display = 'none';
@@ -132,6 +135,10 @@ export default class JitsiStreamPresenterEffect {
this._desktopElement.height = parseInt(height, 10);
this._desktopElement.autoplay = true;
this._desktopElement.srcObject = desktopStream;
// autoplay is not enough to start the video on Safari, it's fine to call play() on other platforms as well
this._desktopElement.play();
this._canvas.width = parseInt(width, 10);
this._canvas.height = parseInt(height, 10);
this._videoFrameTimerWorker = new Worker(timerWorkerScript, { name: 'Presenter effect worker' });

View File

@@ -40,20 +40,20 @@ MiddlewareRegistry.register(store => next => action => {
}
});
conference.on(
JitsiConferenceEvents.TALK_WHILE_MUTED, () => {
const notification = showNotification({
JitsiConferenceEvents.TALK_WHILE_MUTED, async () => {
const notification = await dispatch(showNotification({
titleKey: 'toolbar.talkWhileMutedPopup',
customActionNameKey: 'notify.unmute',
customActionHandler: () => dispatch(setAudioMuted(false))
});
dispatch(notification);
}));
dispatch(playSound(TALK_WHILE_MUTED_SOUND_ID));
// we store the last start muted notification id that we showed,
// so we can hide it when unmuted mic is detected
dispatch(setCurrentNotificationUid(notification.uid));
if (notification) {
// we store the last start muted notification id that we showed,
// so we can hide it when unmuted mic is detected
dispatch(setCurrentNotificationUid(notification.uid));
}
});
break;
}

View File

@@ -166,8 +166,9 @@ class OverflowMenu extends PureComponent<Props, State> {
styles.expandMenuContainer
] }>
<TouchableOpacity onPress = { this._onToggleMenu }>
{ /* $FlowFixMeProps */ }
<IconDragHandle style = { this.props._bottomSheetStyles.expandIcon } />
{ /* $FlowFixMe */ }
<IconDragHandle
fill = { this.props._bottomSheetStyles.buttons.iconStyle.color } />
</TouchableOpacity>
</View>
);

View File

@@ -470,6 +470,7 @@ class Toolbox extends Component<Props, State> {
*/
_doToggleRaiseHand() {
const { _localParticipantID, _raisedHand } = this.props;
const newRaisedStatus = !_raisedHand;
this.props.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without
@@ -480,8 +481,10 @@ class Toolbox extends Component<Props, State> {
id: _localParticipantID,
local: true,
raisedHand: !_raisedHand
raisedHand: newRaisedStatus
}));
APP.API.notifyRaiseHandUpdated(_localParticipantID, newRaisedStatus);
}
/**

View File

@@ -1,7 +1,6 @@
// @flow
import { hasAvailableDevices } from '../base/devices';
import { isMobileBrowser } from '../base/environment/utils';
declare var interfaceConfig: Object;
@@ -49,10 +48,8 @@ export function isToolboxVisible(state: Object) {
visible
} = state['features/toolbox'];
const { audioSettingsVisible, videoSettingsVisible } = state['features/settings'];
const { isOpen } = state['features/chat'];
const isMobileChatOpen = isMobileBrowser() && isOpen;
return Boolean(!isMobileChatOpen && !iAmSipGateway && (timeoutID || visible || alwaysVisible
return Boolean(!iAmSipGateway && (timeoutID || visible || alwaysVisible
|| audioSettingsVisible || videoSettingsVisible));
}

View File

@@ -69,17 +69,16 @@ export function potentialTranscriberJoined(participantId: string) {
* @returns {Function}
*/
export function showPendingTranscribingNotification() {
return (dispatch: Function) => {
const showNotificationAction = showNotification({
return async (dispatch: Function) => {
const notification = await dispatch(showNotification({
descriptionKey: 'transcribing.pending',
isDismissAllowed: false,
titleKey: 'dialog.transcribing'
});
}));
dispatch(showNotificationAction);
dispatch(setPendingTranscribingNotificationUid(
showNotificationAction.uid));
if (notification) {
dispatch(setPendingTranscribingNotificationUid(notification.uid));
}
};
}

View File

@@ -82,6 +82,12 @@ export function shouldDisplayTileView(state: Object = {}) {
return false;
}
const { disableTileView } = state['features/base/config'];
if (disableTileView) {
return false;
}
const { tileViewEnabled } = state['features/video-layout'];
if (tileViewEnabled !== undefined) {

View File

@@ -0,0 +1,202 @@
if module:get_host_type() ~= "component" then
error("proxy_component should be loaded as component", 0);
end
local jid_split = require "util.jid".split;
local jid_bare = require "util.jid".bare;
local jid_prep = require "util.jid".prep;
local st = require "util.stanza";
local array = require "util.array";
local target_address = module:get_option_string("target_address");
sessions = array{};
local sessions = sessions;
local function handle_target_presence(stanza)
local type = stanza.attr.type;
module:log("debug", "received presence from destination: %s", type)
local _, _, resource = jid_split(stanza.attr.from);
if type == "error" then
-- drop all known sessions
for k in pairs(sessions) do
sessions[k] = nil
end
module:log(
"debug",
"received error presence, dropping all target sessions",
resource
)
elseif type == "unavailable" then
for k in pairs(sessions) do
if sessions[k] == resource then
sessions[k] = nil
module:log(
"debug",
"dropped target session: %s",
resource
)
break
end
end
elseif not type then
-- available
local found = false;
for k in pairs(sessions) do
if sessions[k] == resource then
found = true;
break
end
end
if not found then
module:log(
"debug",
"registered new target session: %s",
resource
)
sessions:push(resource)
end
end
end
local function handle_from_target(stanza)
local type = stanza.attr.type
module:log(
"debug",
"non-presence stanza from target: name = %s, type = %s",
stanza.name,
type
)
if stanza.name == "iq" then
if type == "error" or type == "result" then
-- de-NAT message
local _, _, denatted_to_unprepped = jid_split(stanza.attr.to);
local denatted_to = jid_prep(denatted_to_unprepped);
if not denatted_to then
module:log(
"debug",
"cannot de-NAT stanza, invalid to: %s",
denatted_to_unprepped
)
return
end
local denatted_from = module:get_host();
module:log(
"debug",
"de-NAT-ed stanza: from: %s -> %s, to: %s -> %s",
stanza.attr.from,
denatted_from,
stanza.attr.to,
denatted_to
)
stanza.attr.from = denatted_from
stanza.attr.to = denatted_to
module:send(stanza)
else
-- FIXME: we dont support NATing outbund requests atm.
module:send(st.error_reply(stanza, "cancel", "feature-not-implemented"))
end
elseif stanza.name == "message" then
-- not implemented yet, we need a way to ensure that routing doesnt
-- break
module:send(st.error_reply(stanza, "cancel", "feature-not-implemented"))
end
end
local function handle_to_target(stanza)
local type = stanza.attr.type;
module:log(
"debug",
"stanza to target: name = %s, type = %s",
stanza.name, type
)
if stanza.name == "presence" then
if type ~= "error" then
module:send(st.error_reply(stanza, "cancel", "bad-request"))
return
end
elseif stanza.name == "iq" then
if type == "get" or type == "set" then
if #sessions == 0 then
-- no sessions available to send to
module:log("debug", "no sessions to send to!")
module:send(st.error_reply(stanza, "cancel", "service-unavailable"))
return
end
-- find a target session
local target_session = sessions:random()
local target = target_address .. "/" .. target_session
-- encode sender JID in resource
local natted_from = module:get_host() .. "/" .. stanza.attr.from;
module:log(
"debug",
"NAT-ed stanza: from: %s -> %s, to: %s -> %s",
stanza.attr.from,
natted_from,
stanza.attr.to,
target
)
stanza.attr.from = natted_from
stanza.attr.to = target
module:send(stanza)
end
-- FIXME: handle and forward result/error correctly
elseif stanza.name == "message" then
-- not implemented yet, we need a way to ensure that routing doesnt
-- break
module:send(st.error_reply(stanza, "cancel", "feature-not-implemented"))
end
end
local function stanza_handler(event)
local origin, stanza = event.origin, event.stanza
module:log("debug", "received stanza from %s session", origin.type)
local bare_from = jid_bare(stanza.attr.from);
local _, _, to = jid_split(stanza.attr.to);
if bare_from == target_address then
-- from our target, to whom?
if not to then
-- directly to component
if stanza.name == "presence" then
handle_target_presence(stanza)
else
module:send(st.error_reply(stanza, "cancel", "bad-request"))
return true
end
else
-- to someone else
handle_from_target(stanza)
end
else
handle_to_target(stanza)
end
return true
end
module:hook("iq/bare", stanza_handler, -1);
module:hook("message/bare", stanza_handler, -1);
module:hook("presence/bare", stanza_handler, -1);
module:hook("iq/full", stanza_handler, -1);
module:hook("message/full", stanza_handler, -1);
module:hook("presence/full", stanza_handler, -1);
module:hook("iq/host", stanza_handler, -1);
module:hook("message/host", stanza_handler, -1);
module:hook("presence/host", stanza_handler, -1);
module:log("debug", "loaded proxy on %s", module:get_host())
subscription_request = st.presence({
type = "subscribe",
to = target_address,
from = module:get_host()}
)
module:send(subscription_request)

View File

@@ -0,0 +1,165 @@
-----------------------------------------------------------
-- mod_roster_command: Manage rosters through prosodyctl
-- version 0.02
-----------------------------------------------------------
-- Copyright (C) 2011 Matthew Wild
-- Copyright (C) 2011 Adam Nielsen
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
-----------------------------------------------------------
if module.host ~= "*" then
module:log("error", "Do not load this module in Prosody, for correct usage see: https://modules.prosody.im/mod_roster_command.html");
return;
end
-- Workaround for lack of util.startup...
local prosody = _G.prosody;
local hosts = prosody.hosts;
prosody.bare_sessions = prosody.bare_sessions or {};
_G.bare_sessions = _G.bare_sessions or prosody.bare_sessions;
local usermanager = require "core.usermanager";
local rostermanager = require "core.rostermanager";
local storagemanager = require "core.storagemanager";
local jid = require "util.jid";
local warn = require"util.prosodyctl".show_warning;
-- Make a *one-way* subscription. User will see when contact is online,
-- contact will not see when user is online.
function subscribe(user_jid, contact_jid)
local user_username, user_host = jid.split(user_jid);
local contact_username, contact_host = jid.split(contact_jid);
if not hosts[user_host] then
warn("The host '%s' is not configured for this server.", user_host);
return;
end
if hosts[user_host].users.name == "null" then
storagemanager.initialize_host(user_host);
usermanager.initialize_host(user_host);
end
-- Update user's roster to say subscription request is pending. Bare hosts (e.g. components) don't have rosters.
if user_username ~= nil then
rostermanager.set_contact_pending_out(user_username, user_host, contact_jid);
end
if hosts[contact_host] then
if contact_host ~= user_host and hosts[contact_host].users.name == "null" then
storagemanager.initialize_host(contact_host);
usermanager.initialize_host(contact_host);
end
-- Update contact's roster to say subscription request is pending...
rostermanager.set_contact_pending_in(contact_username, contact_host, user_jid);
-- Update contact's roster to say subscription request approved...
rostermanager.subscribed(contact_username, contact_host, user_jid);
-- Update user's roster to say subscription request approved. Bare hosts (e.g. components) don't have rosters.
if user_username ~= nil then
rostermanager.process_inbound_subscription_approval(user_username, user_host, contact_jid);
end
end
end
-- Make a mutual subscription between jid1 and jid2. Each JID will see
-- when the other one is online.
function subscribe_both(jid1, jid2)
subscribe(jid1, jid2);
subscribe(jid2, jid1);
end
-- Unsubscribes user from contact (not contact from user, if subscribed).
function unsubscribe(user_jid, contact_jid)
local user_username, user_host = jid.split(user_jid);
local contact_username, contact_host = jid.split(contact_jid);
if not hosts[user_host] then
warn("The host '%s' is not configured for this server.", user_host);
return;
end
if hosts[user_host].users.name == "null" then
storagemanager.initialize_host(user_host);
usermanager.initialize_host(user_host);
end
-- Update user's roster to say subscription is cancelled...
rostermanager.unsubscribe(user_username, user_host, contact_jid);
if hosts[contact_host] then
if contact_host ~= user_host and hosts[contact_host].users.name == "null" then
storagemanager.initialize_host(contact_host);
usermanager.initialize_host(contact_host);
end
-- Update contact's roster to say subscription is cancelled...
rostermanager.unsubscribed(contact_username, contact_host, user_jid);
end
end
-- Cancel any subscription in either direction.
function unsubscribe_both(jid1, jid2)
unsubscribe(jid1, jid2);
unsubscribe(jid2, jid1);
end
-- Set the name shown and group used in the contact list
function rename(user_jid, contact_jid, contact_nick, contact_group)
local user_username, user_host = jid.split(user_jid);
if not hosts[user_host] then
warn("The host '%s' is not configured for this server.", user_host);
return;
end
if hosts[user_host].users.name == "null" then
storagemanager.initialize_host(user_host);
usermanager.initialize_host(user_host);
end
-- Load user's roster and find the contact
local roster = rostermanager.load_roster(user_username, user_host);
local item = roster[contact_jid];
if item then
if contact_nick then
item.name = contact_nick;
end
if contact_group then
item.groups = {}; -- Remove from all current groups
item.groups[contact_group] = true;
end
rostermanager.save_roster(user_username, user_host, roster);
end
end
function remove(user_jid, contact_jid)
unsubscribe_both(user_jid, contact_jid);
local user_username, user_host = jid.split(user_jid);
local roster = rostermanager.load_roster(user_username, user_host);
roster[contact_jid] = nil;
rostermanager.save_roster(user_username, user_host, roster);
end
function module.command(arg)
local command = arg[1];
if not command then
warn("Valid subcommands: (un)subscribe(_both) | rename");
return 0;
end
table.remove(arg, 1);
if command == "subscribe" then
subscribe(arg[1], arg[2]);
return 0;
elseif command == "subscribe_both" then
subscribe_both(arg[1], arg[2]);
return 0;
elseif command == "unsubscribe" then
unsubscribe(arg[1], arg[2]);
return 0;
elseif command == "unsubscribe_both" then
unsubscribe_both(arg[1], arg[2]);
return 0;
elseif command == "remove" then
remove(arg[1], arg[2]);
return 0;
elseif command == "rename" then
rename(arg[1], arg[2], arg[3], arg[4]);
return 0;
else
warn("Unknown command: %s", command);
return 1;
end
return 0;
end

View File

@@ -0,0 +1,47 @@
# HG changeset patch
# User Boris Grozev <boris@jitsi.org>
# Date 1609874100 21600
# Tue Jan 05 13:15:00 2021 -0600
# Node ID f646babfc401494ff33f2126ef6c4df541ebf846
# Parent 456b9f608fcf9667cfba1bd7bf9eba2151af50d0
mod_roster_command: Fix subscription when the "user JID" is a bare domain.
Do not attempt to update the roster when the user is bare domain (e.g. a
component), since they don't have rosters and the attempt results in an error:
$ prosodyctl mod_roster_command subscribe proxy.example.com contact@example.com
xxxxxxxxxxFailed to execute command: Error: /usr/lib/prosody/core/rostermanager.lua:104: attempt to concatenate local 'username' (a nil value)
stack traceback:
/usr/lib/prosody/core/rostermanager.lua:104: in function 'load_roster'
/usr/lib/prosody/core/rostermanager.lua:305: in function 'set_contact_pending_out'
mod_roster_command.lua:44: in function 'subscribe'
diff -r 456b9f608fcf -r f646babfc401 mod_roster_command/mod_roster_command.lua
--- a/mod_roster_command/mod_roster_command.lua Tue Jan 05 13:49:50 2021 +0000
+++ b/mod_roster_command/mod_roster_command.lua Tue Jan 05 13:15:00 2021 -0600
@@ -40,8 +40,10 @@
storagemanager.initialize_host(user_host);
usermanager.initialize_host(user_host);
end
- -- Update user's roster to say subscription request is pending...
- rostermanager.set_contact_pending_out(user_username, user_host, contact_jid);
+ -- Update user's roster to say subscription request is pending. Bare hosts (e.g. components) don't have rosters.
+ if user_username ~= nil then
+ rostermanager.set_contact_pending_out(user_username, user_host, contact_jid);
+ end
if hosts[contact_host] then
if contact_host ~= user_host and hosts[contact_host].users.name == "null" then
storagemanager.initialize_host(contact_host);
@@ -51,8 +53,10 @@
rostermanager.set_contact_pending_in(contact_username, contact_host, user_jid);
-- Update contact's roster to say subscription request approved...
rostermanager.subscribed(contact_username, contact_host, user_jid);
- -- Update user's roster to say subscription request approved...
- rostermanager.process_inbound_subscription_approval(user_username, user_host, contact_jid);
+ -- Update user's roster to say subscription request approved. Bare hosts (e.g. components) don't have rosters.
+ if user_username ~= nil then
+ rostermanager.process_inbound_subscription_approval(user_username, user_host, contact_jid);
+ end
end
end

View File

@@ -46,20 +46,14 @@ def twaManifest = [
enableSiteSettingsShortcut: 'true',
]
// Use the number of seconds/10 since Jan 1 2019 as the versionCode.
// This lets us upload a new build at most every 10 seconds for the
// next ~680 years.
// https://stackoverflow.com/a/38643838
def vcode = (int) (((new Date().getTime() / 1000) - 1546297200) / 10)
android {
compileSdkVersion 29
defaultConfig {
applicationId "org.jitsi.meet"
minSdkVersion 19
targetSdkVersion 29
versionCode vcode
versionName "20.5.0"
versionCode 2000000000
versionName "1.0.0"
// The name for the application
resValue "string", "appName", twaManifest.name
@@ -71,12 +65,12 @@ android {
def launchUrl = "https://" + twaManifest.hostName + twaManifest.launchUrl
resValue "string", "launchUrl", launchUrl
// The URL the Web Manifest for the Progressive Web App that the TWA points to. This
// is used by Chrome OS to open the Web version of the PWA instead of the TWA, as it
// will probably give a better user experience for non-mobile devices.
resValue "string", "webManifestUrl", 'https://meet.jit.si/static/pwa/manifest.json'
resValue "string", "webManifestUrl", 'https://meet.jit.si/manifest.json'
// The hostname is used when building the intent-filter, so the TWA is able to
// handle Intents to open https://svgomg.firebaseapp.com.
@@ -106,13 +100,13 @@ android {
// compile. If not set, the navigation bar color defaults to #000000 - black.
resValue "color", "navigationColorDark", twaManifest.navigationColorDark
// This attribute sets the navbar divider color for the TWA. It can be either
// set here or in `res/values/colors.xml`. Setting in both places is an error and the app
// This attribute sets the navbar divider color for the TWA. It can be either
// set here or in `res/values/colors.xml`. Setting in both places is an error and the app
// will not compile. If not set, the divider color defaults to #00000000 - transparent.
resValue "color", "navigationDividerColor", twaManifest.navigationDividerColor
// This attribute sets the dark navbar divider color for the TWA. It can be either
// set here or in `res/values/colors.xml`. Setting in both places is an error and the
// This attribute sets the dark navbar divider color for the TWA. It can be either
// set here or in `res/values/colors.xml`. Setting in both places is an error and the
//app will not compile. If not set, the divider color defaults to #000000 - black.
resValue "color", "navigationDividerColorDark", twaManifest.navigationDividerColorDark
@@ -192,11 +186,11 @@ task generateShorcutsFile {
preBuild.dependsOn(generateShorcutsFile)
repositories {
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.0.0'
implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.0.0'
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1 +1 @@
{"android_package_name":"org.jitsi.meet","prefer_related_applications":true,"related_applications":[{"id":"org.jitsi.meet","platform":"chromeos_play"}],"short_name":"Jitsi Meet","name":"Jitsi Meet","icons":[{"src":"icons/icon192.png","type":"image/png","sizes":"192x192"},{"src":"icons/icon512.png","type":"image/png","sizes":"512x512"},{"src":"icons/iconMask.png","sizes":"512x512","type":"image/png","purpose":"maskable"}],"start_url":"/","background_color":"#17A0DB","display":"standalone","scope":"/","theme_color":"#17A0DB"}
{"android_package_name":"org.jitsi.meet","prefer_related_applications":true,"related_applications":[{"id":"org.jitsi.meet","platform":"chromeos_play"}],"short_name":"Jitsi Meet","name":"Jitsi Meet","icons":[{"src":"static/pwa/icons/icon192.png","type":"image/png","sizes":"192x192"},{"src":"static/pwa/icons/icon512.png","type":"image/png","sizes":"512x512"},{"src":"static/pwa/icons/iconMask.png","sizes":"512x512","type":"image/png","purpose":"maskable"}],"start_url":"/","background_color":"#17A0DB","display":"standalone","scope":"/","theme_color":"#17A0DB"}

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#66A8DD</color>
</resources>

View File

@@ -1 +1,16 @@
<!--
Copyright 2019 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<shortcuts xmlns:android='http://schemas.android.com/apk/res/android' />

View File

@@ -1,6 +1,5 @@
#Wed Oct 14 15:52:23 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip

View File

@@ -13,15 +13,15 @@
"enableNotifications": false,
"startUrl": "/",
"iconUrl": "https://meet.jit.si/static/pwa/icons/icon512.png",
"maskableIconUrl": "https://meet.jit.si/static/pwa/icons/iconMask.png",
"splashScreenFadeOutDuration": 300,
"appVersionName": "20.5.0",
"appVersionCode": 1,
"appVersionName": "1.0.0",
"appVersionCode": 2000000000,
"shortcuts": [],
"generatorApp": "bubblewrap-cli",
"webManifestUrl": "https://meet.jit.si/static/pwa/manifest.json",
"webManifestUrl": "https://meet.jit.si/manifest.json",
"fallbackType": "customtabs",
"features": {},
"enableSiteSettingsShortcut": true,
"isChromeOSOnly": true,
"appVersion": "20.5.0"
"appVersion": "1.0.0"
}