Compare commits

...

82 Commits

Author SHA1 Message Date
Avram Tudor
e73c3b6697 feat(recordings) send recordings metadata
* ref: centralise conference metadata updates

* feat(recordings) send recordings metadata
2024-02-08 21:38:06 +02:00
Avram Tudor
5a6b1d0b47 code review 2024-02-08 17:41:55 +02:00
Avram Tudor
da9cded75b fix(recording) ensure recording label is shown when transcription is on 2024-02-08 17:41:55 +02:00
Avram Tudor
9b61ad3616 fix(recording) do not show advanced options for users with local recording rights 2024-02-08 17:41:55 +02:00
Avram Tudor
583725bf31 fix(recording) do not show stop recording option in some wrong cases 2024-02-08 17:41:55 +02:00
Avram Tudor
9e2244210d ref: add selector to check if meeting is transcribed 2024-02-08 17:41:55 +02:00
Avram Tudor
361b82a1ed ref: add selector for checking cloud recording running status 2024-02-08 17:41:55 +02:00
Saúl Ibarra Corretgé
0ed25cda7e fix(ci) use the right CocoaPods version 2024-02-08 15:08:15 +01:00
Calinteodor
73ee1205eb feat(premeeting/prejoin): Control room name visibility (#14339)
feat(premeeting/prejoin): Control room name visibility (#14339)
2024-02-08 15:56:05 +02:00
damencho
21f2c60638 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1769.0.0+ff9f57c5...v1770.0.0+2d430a18
2024-02-07 14:53:46 -06:00
damencho
fd062c40fb fix: Use same format for anonymous jids as token module. 2024-02-07 14:53:36 -06:00
damencho
b87e6abc11 feat: Sends event on not allowed error.
Updates amplitude to include the update about sending events on exiting the page.
2024-02-07 13:08:35 -06:00
Avram Tudor
9f25726706 fix(recording) fix recording suggestion not being shown in some cases (#14333)
Initial implementation did not account for cases where participants become moderators
2024-02-07 15:36:43 +02:00
Calinteodor
d5ee7f3069 feat(mobile/polyfills): strophe.js for react native (#14329)
* chore(deps, abab): polyfill using dependency for strophe in react native
2024-02-06 18:28:35 +02:00
damencho
ba1102100a feat(visitors): Adds conference duration for visitors. 2024-02-06 10:16:30 -06:00
damencho
e1ce83d0c3 feat: Use room data form for conference duration.
Deprecates current component for conference duration. Will wait a bit for mobile to catch up and then we can delete the code.

chore(deps) lib-jitsi-meet@latest
jitsi/lib-jitsi-meet@v1767.0.0+178f9bbf...v1769.0.0+ff9f57c5
2024-02-06 10:16:30 -06:00
damencho
78cf510c0b feat: Moves common process_host_module to util. 2024-02-06 10:16:30 -06:00
Mihaela Dumitru
3f657c3ded feat(whiteboard) add native implementation (#14327) 2024-02-06 16:22:53 +02:00
Saúl Ibarra Corretgé
2035cd7e62 fix(recording) avoid having duplicate notification sounds 2024-02-06 11:14:12 +01:00
Saúl Ibarra Corretgé
6207e95cad fix(recording) fix default value to auto-transcribe-on-record 2024-02-06 11:14:12 +01:00
Calinteodor
40b63a187a feat(conference): remove one2one check for diplaying participants name (#14324)
* feat(conference): remove one2one check for diplaying participants name
2024-02-06 10:54:27 +02:00
Saúl Ibarra Corretgé
6c40250e18 fix(conference) avoid adding extra endpoint message listeners 2024-02-06 07:40:40 +01:00
Saúl Ibarra Corretgé
0268374b88 fix(conference) move endpointMessageReceived action
It's used for a myrias of things, not just subtitles.
2024-02-06 07:40:40 +01:00
Jaya Allamsetty
b1b60ec143 fix(config): Fix the typo in Av1 support flag. 2024-02-05 13:27:29 -05:00
Дамян Минков
82c58178b3 feat(visitors): Adds notification for promotion requests. (#14316)
* feat(visitors): Adds notification for promotion requests.

* squash: fixes lint.
2024-02-02 14:58:24 -06:00
Jaya Allamsetty
46d15a9c5c chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1764.0.0+ba21a236...v1767.0.0+178f9bbf
2024-02-02 14:58:09 -06:00
Hristo Terezov
248908f476 fix(lobby): Hide enable option if there is a visitor 2024-02-02 14:20:16 -06:00
Hristo Terezov
4a5ba0f05d fix(visitors): Disable polls. 2024-02-02 12:50:01 -06:00
damencho
b23f3b9e17 feat(visitors): Whitelist trusted domains when promoting. 2024-02-02 12:01:59 -06:00
Дамян Минков
b6b77f55f1 feat: Drops some debug prints. (#14311)
* feat: Drops some debug prints.

* squash: Fix linter errors.
2024-02-02 11:11:50 -06:00
damencho
b106c20fa2 feat: Adds indication for visitors messages to event. 2024-02-01 21:06:25 -06:00
Hristo Terezov
fab61d8c32 fix(reactions): Show when received even if UI is disabled 2024-02-01 18:08:19 -06:00
Hristo Terezov
9ac614cc4b fix(shortcut): Unregister for altKey shortcuts. 2024-02-01 18:08:19 -06:00
Hristo Terezov
a3bb1a3459 feat(visitors): Hide reactions. 2024-02-01 18:08:19 -06:00
Avram Tudor
34abd279be feat(recording) add notification to suggest recording at meeting startup (#14296)
* feat(recording) add notification to suggest recording at meeting startup

* code review changes

* update strings

* fix mobile

* fix lint
2024-02-01 17:08:41 +02:00
Saúl Ibarra Corretgé
ad6554a789 fix(transcriptions) obey skipInterimTranscriptions for chunk events 2024-02-01 10:43:10 +01:00
Saúl Ibarra Corretgé
2be8377009 fix(transcriptions) avoid generating duplicated events 2024-02-01 10:43:10 +01:00
damencho
0a0b0a760e fix: Fixes promoting the only moderator in a non vpass meeting. 2024-01-31 17:53:16 -06:00
Дамян Минков
fec6de4536 fix(visitors): Lowers visitor's hand on leave. (#14302)
* fix(visitors): Lowers visitor's hand on leave.

* squash: Drop not needed nick.

That is used only when raising hand.
2024-01-31 11:49:16 -06:00
damencho
29b2a519b3 fix(visitors): Handles promotion rejection msg.
If not handled an error is returned that the stanza is not handled (service-unavailable error) and displayed in UI.
2024-01-31 07:37:43 -06:00
Hristo Terezov
831d39d447 fix(visitors): hide highlight button. 2024-01-30 17:57:05 -06:00
damencho
35bc430549 fix(visitors): Fixes clearing promotion requests.
We were modifying DEFAULT_STATE and later the request magically appears after the state is cleared, like joining and leaving breakout rooms.
2024-01-30 17:56:16 -06:00
damencho
77927f3f2e chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1763.0.0+8a88bba7...v1764.0.0+ba21a236
2024-01-30 16:34:08 -05:00
Jaya Allamsetty
7f6d55f5f0 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1762.0.0+841b3eb8...v1763.0.0+8a88bba7
2024-01-30 13:32:31 -05:00
Edgars Voroboks
32f4767edd fix(lang) Update Latvian translation 2024-01-30 09:26:11 +01:00
damencho
ba88fc0279 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1761.0.0+f470b5f4...v1762.0.0+841b3eb8
2024-01-29 15:34:33 -06:00
Saúl Ibarra Corretgé
ba7ed83d48 feat(recording) stop transcription when recording is stopped 2024-01-29 15:09:28 +01:00
Saúl Ibarra Corretgé
5a96050973 feat(recording) always show advanced options in dialog 2024-01-29 15:09:28 +01:00
Saúl Ibarra Corretgé
7e8c1fd99a feat(recording) show recording icon when transcribing 2024-01-29 15:09:28 +01:00
Saúl Ibarra Corretgé
6e6433e2d9 feat(transcribing) emit recording notifications for transcriptions
Both audible and visual.

They are only emitted if there is no video recording on.
2024-01-29 15:09:28 +01:00
Saúl Ibarra Corretgé
4ff2422e3c feat(subtitles) drop disableStartForAll config option
It wasn't related to transcriptions but to subtitles, really.

Since we are not going to show subtitles just because transcriptions are
enabled, this setting no longer makes sense.
2024-01-29 15:09:28 +01:00
Saúl Ibarra Corretgé
cfbcfdc857 feat(recording) update labels with transcription status 2024-01-29 15:09:28 +01:00
Saúl Ibarra Corretgé
3074b1741f feat(conference) drop transcribing label 2024-01-29 15:09:28 +01:00
Saúl Ibarra Corretgé
00503d163a feat(transcribing) drop transcribing notifications 2024-01-29 15:09:28 +01:00
Christoph Settgast
f847a3e2e0 fix(deeplinking): allow translating all strings on deeplinking pages (#14286) 2024-01-28 13:26:53 +01:00
Christoph Settgast
93d0ee710b fix(deeplinking): allow partial config of deeplinking and defaults for the remaining (#14285) 2024-01-28 13:26:16 +01:00
Christoph Settgast
0102efd2d0 fix(deeplinking): skip deeplinking within electron (#14284)
otherwise we are stuck in a loop inside the desktop app

Signed-off-by: Christoph Settgast <csett86_git@quicksands.de>
2024-01-27 16:34:33 +01:00
Christoph Settgast
8afdebca08 feat(deeplinking): offer electron app for download, configurable (#14261)
Offer the default app, but make it configurable for own branded
desktop clients

Signed-off-by: Christoph Settgast <csett86_git@quicksands.de>
2024-01-26 22:04:44 +01:00
Göktuğ Aşcı
d2bfb464ba lang: add translation tr (#14282) 2024-01-26 13:52:56 -06:00
Jaya Allamsetty
5f3233b9a6 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1760.0.0+7895cb25...v1761.0.0+f470b5f4
2024-01-26 10:31:29 -05:00
Hristo Terezov
3c32d9c552 feat(LargeVideo): Add logging. 2024-01-25 09:31:38 -06:00
Hristo Terezov
f51d8d54e4 fix(LargeVideo): Add retries for play. 2024-01-25 09:31:38 -06:00
Saúl Ibarra Corretgé
ac26dce0a5 feat(android) list HDMI connections as speakers 2024-01-25 10:59:21 +01:00
Jaya Allamsetty
7a23625752 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1759.0.0+fc2f2490...v1760.0.0+7895cb25
2024-01-24 16:47:05 -05:00
Corey Maler
4cb4a4db00 fix(participant) prevent gravatar request during loading of config (#14255) 2024-01-24 13:32:59 +01:00
Sveinn í Felli
05e484c4aa fix(lang) Update main-is.json (#14270)
Updated Icelandic translation
2024-01-22 20:39:00 +01:00
Дамян Минков
5871e50a20 * feat: Stores vpaas check in room object.
* feat: Clear queues on destroy for muc rate limit join/leave.

* feat: Stores vpaas check in room object.

* squash: Replace one regexp with starts_with.
2024-01-22 11:48:35 -06:00
Damien Fetis
95ad04b19c fix(lang) update french translation (#14269) 2024-01-22 17:59:46 +01:00
damencho
36fab239cc fix: Updates checking for granted feature. 2024-01-22 09:28:48 -06:00
Saúl Ibarra Corretgé
92492dc482 feat(ci,ios) check if Pod file is in sync when running CI 2024-01-22 13:49:42 +01:00
Saúl Ibarra Corretgé
51c3658205 fix(ios) sync Podfile 2024-01-22 12:56:17 +01:00
Saúl Ibarra Corretgé
418d333caf feat(rn,invite) add ability to disable dial-in options 2024-01-22 12:56:17 +01:00
Christoph Settgast
7f3f2f7034 lang: typo in German translation 2024-01-21 18:06:30 -06:00
Christoph Settgast
bd3012f110 feat(deeplinking): Desktop deeplinking configurable (default off) and using jitsi-meet-electron (#14259)
As we have the jitsi-meet-electron app, lets allow deployments to use it.

Allow deployments to enable desktop deeplinking without the need to re-implement _openDesktopApp()
Disable it by default to keep the current behaviour (deeplinking on mobile on, on desktop off)

This feature is meant as purely opt-in.

Signed-off-by: Christoph Settgast <csett86_git@quicksands.de>
2024-01-21 21:02:16 +01:00
Saúl Ibarra Corretgé
f8cef330f7 feat(android) set target SDK to 34 (#14129)
* feat(android) set target SDK to 34 and fixed mediaProjection service type
2024-01-19 16:52:39 +02:00
Saúl Ibarra Corretgé
e00b6be06c feat(face-landmarks) disable on mobile web browsers 2024-01-18 15:50:47 +01:00
Дамян Минков
9b7da0be2d feat: Drops sending facelandmarks to speakerstats. (#14253)
* feat: Drops sending facelandmarks to speakerstats.

* squash: Fixes lint.
2024-01-18 08:34:14 -06:00
Calin-Teodor
3effbca700 feat(ios): add missing config to .xcodeproj 2024-01-18 15:06:55 +02:00
Calinteodor
cf1f97f486 feat(rn, ios): build fixes and metro-config updates (#14251)
feat(rn, ios): build fixes and metro-config updates
2024-01-18 14:48:59 +02:00
Дамян Минков
9273022e8f Delete nwjs-integration.
This hasn't been used for long time and is probably no longer working.
2024-01-18 06:27:54 -06:00
Jaya Allamsetty
5d35398b6b chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1758.0.0+e6298b6c...v1759.0.0+fc2f2490
2024-01-17 17:33:31 -05:00
Ilya Shevtsov
60d4b626a5 fix(lang) updated Russian translations (#14213) 2024-01-17 13:38:55 +01:00
200 changed files with 5061 additions and 1832 deletions

View File

@@ -73,6 +73,17 @@ jobs:
node-version: 16
cache: 'npm'
- run: npm install
- name: setup-cocoapods
uses: maxim-lobanov/setup-cocoapods@v1
with:
podfile-path: ios/Podfile.lock
- name: Install Pods
run: |
pod --version
cd ios
pod install --repo-update
- name: Check if the git repository is clean
run: $(exit $(git status --porcelain --untracked-files=no | head -255 | wc -l)) || (echo "Dirty git tree"; git diff; exit 1)
- run: npx react-native bundle --entry-file react/index.native.js --platform ios --bundle-output /tmp/ios.bundle --reset-cache
debian-build:
name: Test Debian packages build

View File

@@ -15,11 +15,13 @@ def vcode = (int) (((new Date().getTime() / 1000) - 1546297200) / 10)
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
packagingOptions {
exclude 'lib/*/libhermes*.so'
jniLibs {
excludes += ['lib/*/libhermes*.so']
}
}
defaultConfig {
applicationId 'org.jitsi.meet'
versionCode vcode
@@ -72,12 +74,13 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
namespace 'org.jitsi.meet'
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.5.1'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.13'
if (!rootProject.ext.libreBuild) {
// Sync with react-native-google-signin

View File

@@ -1,6 +1,5 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jitsi.meet"
android:installLocation="auto">
<application
android:allowBackup="true"

View File

@@ -10,7 +10,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
classpath 'com.android.tools.build:gradle:7.4.2'
classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
}
@@ -19,9 +19,9 @@ buildscript {
ext {
kotlinVersion = "1.7.0"
buildToolsVersion = "33.0.2"
compileSdkVersion = 33
compileSdkVersion = 34
minSdkVersion = 24
targetSdkVersion = 33
targetSdkVersion = 34
supportLibVersion = "28.0.0"
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.

View File

@@ -30,6 +30,7 @@ android {
}
}
}
namespace 'org.jitsi.meet.sdk'
}
dependencies {

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.jitsi.meet.sdk">
xmlns:tools="http://schemas.android.com/tools">
<!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
@@ -13,6 +12,8 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-feature
android:glEsVersion="0x00020000"
@@ -48,6 +49,10 @@
<service
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
android:foregroundServiceType="mediaPlayback" />
<service
android:name="org.jitsi.meet.sdk.JitsiMeetMediaProjectionService"
android:foregroundServiceType="mediaProjection" />
<provider
@@ -66,4 +71,4 @@
</application>
</manifest>
</manifest>

View File

@@ -87,6 +87,7 @@ class AudioDeviceHandlerGeneric implements
devices.add(AudioModeModule.DEVICE_EARPIECE);
break;
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
case AudioDeviceInfo.TYPE_HDMI:
devices.add(AudioModeModule.DEVICE_SPEAKER);
break;
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:

View File

@@ -0,0 +1,42 @@
package org.jitsi.meet.sdk;
import android.content.Context;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = JitsiMeetMediaProjectionModule.NAME)
class JitsiMeetMediaProjectionModule
extends ReactContextBaseJavaModule {
public static final String NAME = "JitsiMeetMediaProjectionModule";
public JitsiMeetMediaProjectionModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public void launch() {
Context context = getReactApplicationContext();
JitsiMeetMediaProjectionService.launch(context);
}
@ReactMethod
public void abort() {
Context context = getReactApplicationContext();
JitsiMeetMediaProjectionService.abort(context);
}
@NonNull
@Override
public String getName() {
return NAME;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* 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.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.IBinder;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
* responsible for presenting an ongoing notification when a conference is in progress.
* The service will help keep the app running while in the background.
*
* See: https://developer.android.com/guide/components/services
*/
public class JitsiMeetMediaProjectionService extends Service {
private static final String TAG = JitsiMeetMediaProjectionService.class.getSimpleName();
public static void launch(Context context) {
OngoingNotification.createOngoingConferenceNotificationChannel();
Intent intent = new Intent(context, JitsiMeetMediaProjectionService.class);
ComponentName componentName;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
componentName = context.startForegroundService(intent);
} else {
componentName = context.startService(intent);
}
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
return;
}
if (componentName == null) {
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetMediaProjectionService.class);
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
Notification notification = OngoingNotification.buildOngoingConferenceNotification(null);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(OngoingNotification.NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -23,6 +23,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -94,8 +95,11 @@ public class JitsiMeetOngoingConferenceService extends Service
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(OngoingNotification.NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
}
}
OngoingConferenceTracker.getInstance().addListener(this);

View File

@@ -73,7 +73,7 @@ class OngoingNotification {
notificationManager.createNotificationChannel(channel);
}
static Notification buildOngoingConferenceNotification(boolean isMuted) {
static Notification buildOngoingConferenceNotification(Boolean isMuted) {
Context context = ReactInstanceManagerHolder.getCurrentActivity();
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
@@ -92,7 +92,7 @@ class OngoingNotification {
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setContentText(isMuted != null ? context.getString(R.string.ongoing_notification_text) : context.getString(R.string.ongoing_notification_action_screenshare))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setOngoing(true)
@@ -103,6 +103,10 @@ class OngoingNotification {
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
if (isMuted == null) {
return builder.build();
}
NotificationCompat.Action hangupAction = createAction(context, JitsiMeetOngoingConferenceService.Action.HANGUP, R.string.ongoing_notification_action_hang_up);
JitsiMeetOngoingConferenceService.Action toggleAudioAction = isMuted

View File

@@ -67,6 +67,7 @@ class ReactInstanceManagerHolder {
new DropboxModule(reactContext),
new ExternalAPIModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new JitsiMeetMediaProjectionModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),
new SplashScreenModule(reactContext),

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ongoing_notification_title">Текущая встреча</string>
<string name="ongoing_notification_text">Нажмите, чтобы вернуться к встрече.</string>
<string name="ongoing_notification_action_hang_up">Отключиться</string>
<string name="ongoing_notification_action_mute">Отключить звук</string>
<string name="ongoing_notification_action_unmute">Включить звук</string>
<string name="ongoing_notification_channel_name">Ongoing Conference Notifications</string>
</resources>

View File

@@ -5,6 +5,7 @@
<string name="ongoing_notification_text">You are currently in a meeting. Tap to return to it.</string>
<string name="ongoing_notification_action_hang_up">Hang up</string>
<string name="ongoing_notification_action_mute">Mute</string>
<string name="ongoing_notification_action_screenshare">You are currently screen-sharing. Tap to return to the meeting.</string>
<string name="ongoing_notification_action_unmute">Unmute</string>
<string name="ongoing_notification_channel_name">Ongoing Conference Notifications</string>
</resources>

View File

@@ -38,6 +38,7 @@ import {
dataChannelClosed,
dataChannelOpened,
e2eRttChanged,
endpointMessageReceived,
kickedOut,
lockStateChanged,
nonParticipantMessageReceived,
@@ -162,7 +163,6 @@ import { isScreenAudioShared } from './react/features/screen-share/functions';
import { toggleScreenshotCaptureSummary } from './react/features/screenshot-capture/actions';
import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect';
import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
import { endpointMessageReceived } from './react/features/subtitles/actions.any';
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
import { muteLocal } from './react/features/video-menu/actions.any';
import { iAmVisitor } from './react/features/visitors/functions';
@@ -1824,28 +1824,24 @@ export default {
room.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(...args) => {
APP.store.dispatch(endpointMessageReceived(...args));
if (args && args.length >= 2) {
const [ sender, eventData ] = args;
if (eventData.name === ENDPOINT_TEXT_MESSAGE_NAME) {
APP.API.notifyEndpointTextMessageReceived({
senderInfo: {
jid: sender._jid,
id: sender._id
},
eventData
});
}
(participant, data) => {
APP.store.dispatch(endpointMessageReceived(participant, data));
if (data?.name === ENDPOINT_TEXT_MESSAGE_NAME) {
APP.API.notifyEndpointTextMessageReceived({
senderInfo: {
jid: participant.getJid(),
id: participant.getId()
},
eventData: data
});
}
});
room.on(
JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
(...args) => {
APP.store.dispatch(nonParticipantMessageReceived(...args));
APP.API.notifyNonParticipantMessageReceived(...args);
(id, data) => {
APP.store.dispatch(nonParticipantMessageReceived(id, data));
APP.API.notifyNonParticipantMessageReceived(id, data);
});
room.on(

View File

@@ -85,7 +85,7 @@ var config = {
// disableE2EE: false,
// Enables supports for AV1 codec.
// enableAv1: false,
// enableAv1Support: false,
// Enables XMPP WebSocket (as opposed to BOSH) for the given amount of users.
// mobileXmppWsThreshold: 10, // enable XMPP WebSockets on mobile for 10% of the users
@@ -324,6 +324,13 @@ var config = {
// 'https://jitsi-meet.example.com/subfolder/static/oauth.html',
// },
// configuration for all things recording related. Existing settings will be migrated here in the future.
// recordings: {
// // If true, shows a notification at the start of the meeting with a call to action button
// // to start recording (for users who can do so).
// // suggestRecording: true,
// },
// recordingService: {
// // When integrations like dropbox are enabled only that will be shown,
// // by enabling fileRecordingsServiceEnabled, we show both the integrations
@@ -413,9 +420,6 @@ var config = {
// // ./src/react/features/transcribing/transcriber-langs.json.
// preferredLanguage: 'en-US',
// // Disable start transcription for all participants.
// disableStartForAll: false,
// // Enables automatic turning on transcribing when recording is started
// autoTranscribeOnRecord: false,
// },
@@ -1197,9 +1201,17 @@ var config = {
// https://firebase.google.com/docs/dynamic-links/create-manually
// deeplinking: {
//
// // The desktop deeplinking config.
// // The desktop deeplinking config, disabled by default.
// desktop: {
// appName: 'Jitsi Meet'
// appName: 'Jitsi Meet',
// appScheme: 'jitsi-meet,
// download: {
// linux:
// 'https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet-x86_64.AppImage',
// macos: 'https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet.dmg',
// windows: 'https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet.exe'
// },
// enabled: false
// },
// // If true, any checks to handoff to another application will be prevented
// // and instead the app will continue to display in the current browser.
@@ -1421,7 +1433,6 @@ var config = {
// 'conference-timer',
// 'participants-count',
// 'e2ee',
// 'transcribing',
// 'video-quality',
// 'insecure-room',
// 'highlight-moment',

View File

@@ -104,7 +104,7 @@
}
}
.reactions-animations-container {
.reactions-animations-overflow-container {
position: absolute;
width: 20%;
bottom: 0;
@@ -117,6 +117,13 @@
position: relative;
}
.reactions-animations-container {
left: 50%;
bottom: 0px;
display: inline-block;
position: absolute;
}
$reactionCount: 20;
@function random($min, $max) {

View File

@@ -36,6 +36,7 @@ $flagsImagePath: "../images/";
@import 'modals/invite/info';
@import 'modals/screen-share/share-audio';
@import 'modals/screen-share/share-screen-warning';
@import 'modals/whiteboard';
@import 'videolayout_default';
@import 'subject';
@import 'popup_menu';

View File

@@ -0,0 +1,7 @@
.whiteboard {
.excalidraw-wrapper {
height: 100vh;
width: 100vw;
}
}

View File

@@ -51,7 +51,6 @@ VirtualHost "jitmeet.example.com"
}
av_moderation_component = "avmoderation.jitmeet.example.com"
speakerstats_component = "speakerstats.jitmeet.example.com"
conference_duration_component = "conferenceduration.jitmeet.example.com"
end_conference_component = "endconference.jitmeet.example.com"
-- we need bosh
modules_enabled = {
@@ -130,9 +129,6 @@ Component "focus.jitmeet.example.com" "client_proxy"
Component "speakerstats.jitmeet.example.com" "speakerstats_component"
muc_component = "conference.jitmeet.example.com"
Component "conferenceduration.jitmeet.example.com" "conference_duration_component"
muc_component = "conference.jitmeet.example.com"
Component "endconference.jitmeet.example.com" "end_conference"
muc_component = "conference.jitmeet.example.com"

View File

@@ -38,9 +38,9 @@ apt install token-generator
mkdir -p /etc/jitsi/meet/jaas
VPASS_COOKIE=$(echo -n ${JAAS_KEY_ID}| cut -d/ -f1)
VPAAS_COOKIE=$(echo -n ${JAAS_KEY_ID}| cut -d/ -f1)
cp /usr/share/jitsi-meet-web-config/nginx-jaas.conf /etc/jitsi/meet/jaas
sed -i "s/jaas_magic_cookie/${VPASS_COOKIE}/g" /etc/jitsi/meet/jaas/nginx-jaas.conf
sed -i "s/jaas_magic_cookie/${VPAAS_COOKIE}/g" /etc/jitsi/meet/jaas/nginx-jaas.conf
cp /usr/share/jitsi-meet-web-config/8x8.vc-config.js /etc/jitsi/meet/jaas/
echo "set \$config_js_location /etc/jitsi/meet/jaas/8x8.vc-config.js;" >> /etc/jitsi/meet/jaas/jaas-vars

1
globals.d.ts vendored
View File

@@ -21,6 +21,7 @@ declare global {
JitsiMeetElectron?: any;
PressureObserver?: any;
PressureRecord?: any;
ReactNativeWebView?: any;
// selenium tests handler
_sharedVideoPlayer: any;
alwaysOnTop: { api: any };

3
globals.native.d.ts vendored
View File

@@ -19,6 +19,9 @@ interface IWindow {
location: ILocation;
PressureObserver?: any;
PressureRecord?: any;
ReactNativeWebView?: any;
TextDecoder?: any;
TextEncoder?: any;
self: any;
top: any;

2
ios/.xcode.env Normal file
View File

@@ -0,0 +1,2 @@
export NODE_BINARY=$(command -v node)
export ENTRY_FILE="${PROJECT_DIR}/../../index.ios.js"

View File

@@ -447,10 +447,10 @@ PODS:
- React-Core
- react-native-splash-screen (3.3.0):
- React-Core
- react-native-video (6.0.0-alpha.7):
- react-native-video (6.0.0-alpha.11):
- React-Core
- react-native-video/Video (= 6.0.0-alpha.7)
- react-native-video/Video (6.0.0-alpha.7):
- react-native-video/Video (= 6.0.0-alpha.11)
- react-native-video/Video (6.0.0-alpha.11):
- PromisesSwift
- React-Core
- react-native-webrtc (118.0.0):
@@ -827,7 +827,7 @@ SPEC CHECKSUMS:
Amplitude: 834c7332dfb9640a751e21c13efb22a07c0c12d4
amplitude-react-native: 0ed8cab759aafaa94961b82122bf56297da607ad
AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570
boost: a7c83b31436843459a1961bfd74b96033dc77234
boost: 7dcd2de282d72e344012f7d6564d024930a6a440
CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: dc178b8748748c036ef9493a5d59d6d1f91a36ce
@@ -855,7 +855,7 @@ SPEC CHECKSUMS:
ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCTRequired: f30c3213569b1dc43659ecc549a6536e1e11139e
RCTTypeSafety: e1ed3137728804fa98bce30b70e3da0b8e23054e
React: 54070abee263d5773486987f1cf3a3616710ed52
@@ -880,7 +880,7 @@ SPEC CHECKSUMS:
react-native-safe-area-context: 9697629f7b2cda43cf52169bb7e0767d330648c2
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
react-native-video: 967eead48aaa42c25a9e1d65c3b1ab30762a88df
react-native-video: 472b7c366eaaaa0207e546d9a50410df89790bcf
react-native-webrtc: c8d9ad3c152105b2720ca2851d04b49659551992
react-native-webview: 8baa0f5c6d336d6ba488e942bcadea5bf51f050a
React-NativeModulesApple: 4225ac31a26696c02c54b471052b3e85e74a9a0c

View File

@@ -1024,7 +1024,7 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
@@ -1088,7 +1088,7 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";

View File

@@ -774,7 +774,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
@@ -841,7 +841,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";

View File

@@ -209,7 +209,7 @@
"appNotInstalled": "Sie benötigen die „{{app}}“-App, um der Konferenz auf dem Smartphone beizutreten.",
"description": "Nichts passiert? Wir haben versucht, die Konferenz in {{app}} zu öffnen. Versuchen Sie es erneut oder treten Sie der Konferenz in {{app}} im Web bei.",
"descriptionNew": "Nichts passiert? Wir haben versucht, die Konferenz in {{app}} zu öffnen. <br /><br /> Versuchen Sie es erneut oder treten Sie der Konferenz im Web bei.",
"descriptionWithoutWeb": "Ist nichts passiert? Wir haben versucht, Ihre Besprechung in der „{{app}}“-Desktop-App zu starten.",
"descriptionWithoutWeb": "Ist nichts passiert? Wir haben versucht, Ihre Konferenz in der „{{app}}“-Desktop-App zu starten.",
"downloadApp": "App herunterladen",
"downloadMobileApp": "Aus dem App Store herunterladen",
"ifDoNotHaveApp": "Wenn Sie die App noch nicht haben:",
@@ -219,11 +219,13 @@
"joinInBrowser": "Im Browser",
"launchMeetingLabel": "Wie möchten Sie an der Konferenz teilnehmen?",
"launchWebButton": "Im Web öffnen",
"noDesktopApp": "Sie haben die App noch nicht installiert?",
"noMobileApp": "Sie haben die App noch nicht installiert?",
"termsAndConditions": "Indem Sie fortfahren, stimmen Sie underen<a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>Nutzungsbedingungen</a> zu.",
"or": "oder",
"termsAndConditions": "Indem Sie fortfahren, stimmen Sie unseren <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>Nutzungsbedingungen</a> zu.",
"title": "Die Konferenz wird in {{app}} geöffnet …",
"titleNew": "Konferenz starten ...",
"tryAgainButton": "Erneut mit der nativen Applikation versuchen",
"tryAgainButton": "Erneut versuchen",
"unsupportedBrowser": "Sie verwenden einen Browser, der noch nicht unterstützt wird."
},
"defaultLink": "Bsp.: {{url}}",
@@ -534,7 +536,7 @@
"conferenceURL": "Link:",
"copyNumber": "Nummer kopieren",
"country": "Land",
"dialANumber": "Um am Meeting teilzunehmen, müssen Sie eine dieser Nummern wählen und dann die PIN eingeben.",
"dialANumber": "Um an der Konferenz teilzunehmen, müssen Sie eine dieser Nummern wählen und dann die PIN eingeben.",
"dialInConferenceID": "PIN:",
"dialInNotSupported": "Entschuldigung, leider wird das Einwählen derzeit nicht unterstützt.",
"dialInNumber": "Einwählen:",

View File

@@ -1,5 +1,8 @@
{
"addPeople": {
"accessibilityLabel": {
"meetingLink": "Lien de la réunion: {{url}}"
},
"add": "Inviter",
"addContacts": "Inviter vos contacts",
"contacts": "contacts",
@@ -39,6 +42,18 @@
"audioOnly": {
"audioOnly": "Bande passante faible"
},
"bandwidthSettings": {
"assumedBandwidthBps": "p. ex. 10000000 pour 10 Mbps ",
"assumedBandwidthBpsWarning": "Des valeurs élevées peuvent provoquer des problèmes réseaux.",
"customValue": "valeur personnalisée",
"customValueEffect": "Positionner la valeur actuelle en bps",
"leaveEmpty": "Laisser vide",
"leaveEmptyEffect": "Autoriser l'utilisation de l'estimation",
"possibleValues": "Valeurs possibles",
"setAssumedBandwidthBps": "Bande passante estimée",
"title": "Réglage de bande passante",
"zeroEffect": "Désactiver la vidéo"
},
"breakoutRooms": {
"actions": {
"add": "Ajouter salle annexe",
@@ -48,15 +63,22 @@
"leaveBreakoutRoom": "Quitter la salle annexe",
"more": "Plus",
"remove": "Supprimer",
"rename": "Renommer",
"renameBreakoutRoom": "Renommer la salle annexe",
"sendToBreakoutRoom": "Envoyer le participant dans:"
},
"breakoutList": "Liste des salles annexes",
"buttonLabel": "Salles annexes",
"defaultName": "Salle annexe #{{index}}",
"hideParticipantList": "Masquer la liste des participants",
"mainRoom": "Salle principale",
"notifications": {
"joined": "Entrée en salle annexe \"{{name}}\"",
"joinedMainRoom": "Retour à la salle principalem",
"joinedTitle": "Salles annexes"
}
},
"showParticipantList": "Afficher la liste des participants",
"title": "Salles annexes"
},
"calendarSync": {
"addMeetingURL": "Ajouter un lien de conférence",
@@ -118,7 +140,7 @@
"buttonText": "Installer l'extension Chrome",
"buttonTextEdge": "Installer l’extension Edge",
"close": "Fermer",
"dontShowAgain": "Ne plus me montrer ceci",
"dontShowAgain": "Ne plus m'afficher ceci",
"installExtensionText": "Installer l'extension pour l'intégration de Google Calendar et Office 365"
},
"connectingOverlay": {
@@ -149,13 +171,14 @@
"connectedTo": "Connecté à :",
"e2eeVerified": "E2EE vérifié",
"framerate": "Images par seconde :",
"less": "Cacher les détails",
"less": "Masquer les détails",
"localaddress": "Adresse locale :",
"localaddress_plural": "Adresses locales :",
"localport": "Port local :",
"localport_plural": "Ports locaux :",
"maxEnabledResolution": "débit max",
"more": "Montrer les détails",
"more": "Afficher les détails",
"no": "non",
"packetloss": "Perte de paquets :",
"participant_id": "ID du participant:",
"quality": {
@@ -174,7 +197,8 @@
"status": "Connexion :",
"transport": "Transport :",
"transport_plural": "Transports :",
"video_ssrc": "Video SSRC :"
"video_ssrc": "Video SSRC :",
"yes": "oui"
},
"dateUtils": {
"earlier": "Plus tôt",
@@ -237,9 +261,12 @@
"Share": "Partager",
"Submit": "Soumettre",
"WaitForHostMsg": "La conférence n'a pas encore commencé. Si vous en êtes l'hôte, veuillez vous authentifier. Sinon, veuillez attendre son arrivée.",
"WaitingForHostButton": "Attendre l'hôte",
"WaitingForHostTitle": "En attente de l'hôte ...",
"Yes": "Oui",
"accessibilityLabel": {
"Cancel": "Annuler (quiter la popup)",
"Ok": "Ok (Sauvegarder et quiter la popup)",
"close": "Fermer la popup",
"liveStreaming": "Diffusion en direct",
"sharingTabs": "Options de partage"
@@ -248,6 +275,8 @@
"addMeetingNote": "Ajouter une note à cette conférence",
"addOptionalNote": "Ajouter une note (optionnel):",
"allow": "Autoriser",
"allowToggleCameraDialog": "Autorisez-vous {{initiatorName}} à changer votre mode de caméra ?",
"allowToggleCameraTitle": "Autoriser-vous le changement de mode de caméra ?",
"alreadySharedVideoMsg": "Un autre participant est en train de partager sa vidéo. Cette conférence ne permet de partager qu'une seule vidéo à la fois.",
"alreadySharedVideoTitle": "Une seule vidéo partagée est autorisée à la fois",
"applicationWindow": "Fenêtre d'application",
@@ -288,8 +317,8 @@
"gracefulShutdown": "Notre service est actuellement en maintenance. Veuillez réessayer plus tard.",
"grantModeratorDialog": "Êtes-vous sûr de vouloir rendre ce participant modérateur ?",
"grantModeratorTitle": "Nommer modérateur",
"hide": "Cacher",
"hideShareAudioHelper": "Ne pas montrer ce dialogue à nouveau",
"hide": "Masquer",
"hideShareAudioHelper": "Ne pas afficher ce dialogue à nouveau",
"incorrectPassword": "Nom d'utilisateur ou mot de passe incorrect",
"incorrectRoomLockPassword": "Mot de passe incorrect",
"internalError": "Oups ! Quelque chose s'est mal passée. L'erreur suivante s'est produite : {{error}}",
@@ -308,6 +337,7 @@
"lockRoom": "Ajouter un $t(lockRoomPassword) à la réunion ",
"lockTitle": "Échec du verrouillage",
"login": "Connexion",
"loginQuestion": "Voulez-vous vraiment vous connecter et quitter la conférence ?",
"logoutQuestion": "Voulez-vous vraiment vous déconnecter et arrêter la conférence ?",
"logoutTitle": "Déconnexion",
"maxUsersLimitReached": "Le nombre maximal de participants est atteint. Le conférence est complète. Merci de contacter l'organisateur de la réunion ou réessayer plus tard !",
@@ -351,8 +381,6 @@
"permissionCameraRequiredError": "L'autorisation caméra est nécessaire pour participer aux réunions avec vidéo. Merci de l'accorder dans les paramètres",
"permissionErrorTitle": "Permission nécessaire",
"permissionMicRequiredError": "L'autorisation microphone est nécessaire pour participer aux réunions avec son. Merci de l'accorder dans les paramètres",
"popupError": "Votre navigateur bloque les fenêtres pop-up. Veuillez autoriser les fenêtres pop-up dans les paramètres de votre navigateur.",
"popupErrorTitle": "Pop-up bloquée",
"readMore": "plus",
"recentlyUsedObjects": "Vos objets récemment utilisés",
"recording": "Enregistrement",
@@ -369,6 +397,8 @@
"removePassword": "Supprimer le $t(lockRoomPassword)",
"removeSharedVideoMsg": "Voulez-vous vraiment supprimer votre vidéo partagée ?",
"removeSharedVideoTitle": "Supprimer la vidéo partagée",
"renameBreakoutRoomLabel": "Nom de la salle annexe",
"renameBreakoutRoomTitle": "Renommer la salle annexe",
"reservationError": "Erreur du système de réservation",
"reservationErrorMsg": "Code d'erreur: {{code}}, message: {{msg}}",
"retry": "Réessayer",
@@ -388,8 +418,10 @@
"sendPrivateMessageTitle": "Envoyer en privé ?",
"serviceUnavailable": "Service indisponible",
"sessTerminated": "Appel terminé",
"sessTerminatedReason": "L'appel a été terminé",
"sessionRestarted": "L'appel est relancé par la passerelle",
"shareAudio": "Continuer",
"shareAudioAltText": "Pour partager le contenu voulu, naviguer vers \"Onglet du Navigateur\", sélectionner le contenu, activer le bouton \"partager l’audio\" et enfin cliquer sur le bouton \"partager\"",
"shareAudioTitle": "Comment partager le son",
"shareAudioWarningD1": "vous devez cesser le partage d'écran avant de partager votre son.",
"shareAudioWarningD2": "viys devez partager votre écran à nouveau et cocher l'ootion \"Partager l'audio\".",
@@ -419,12 +451,30 @@
"thankYou": "Merci d'avoir utilisé {{appName}} !",
"token": "jeton",
"tokenAuthFailed": "Désolé, vous n'êtes pas autorisé à rejoindre cette conversation.",
"tokenAuthFailedReason": {
"audInvalid": "Valeur `aud` invalide. Cela doit être `jitsi`.",
"contextNotFound": "L'objet `context` est absent du payload.",
"expInvalid": "Valeur `exp` invalide.",
"featureInvalid": "Fonctionnalité invalide: {{feature}}, probablement pas encore implémentée.",
"featureValueInvalid": "Valeur invalide pour la fonctionnalité: {{feature}}.",
"featuresNotFound": "L'objet `feature` est absent du payload.",
"headerNotFound": "Header non trouvé.",
"issInvalid": "Valeur `iss` invalide. Cela doit être `chat`.",
"kidMismatch": "Key ID (kid) ne correspond pas au sub.",
"kidNotFound": "Key ID (kid) manquant.",
"nbfFuture": "La valeur `nbf` est dans le futur.",
"nbfInvalid": "Valeur `nbf` invalide.",
"payloadNotFound": "Payload non trouvé",
"tokenExpired": "Jeton expiré"
},
"tokenAuthFailedTitle": "Échec de l'authentification",
"tokenAuthFailedWithReasons": "Désolé, vous n’êtes pas autorisé à rejoindre l’appel. La raison possible : {{reason}}.",
"tokenAuthUnsupported": "Token URL n'est pas supporté.",
"transcribing": "Transcription",
"unlockRoom": "Supprimer le $t(lockRoomPassword) de la réunion",
"user": "Utilisateur",
"userIdentifier": "Identifiant utilisateur",
"userPassword": "mot de passe utilisateur",
"userPassword": "Mot de passe utilisateur",
"verifyParticipantConfirm": "Ils correspondent",
"verifyParticipantDismiss": "Ils ne correspondent pas",
"verifyParticipantQuestion": "EXPÉRIMENTAL: Demander au participant {{participantName}} s'il voit le même contenu dans le même ordre. ",
@@ -433,6 +483,10 @@
"viewUpgradeOptions": "Voir les options de mise à jour",
"viewUpgradeOptionsContent": "Pour obtenir un accès illimité à des capacités premium comme l'enregistrement, les transcriptions, diffusion RTMP et plus, vous devez mettre à jour votre plan.",
"viewUpgradeOptionsTitle": "Vous avez découvert une capacité premium !",
"whiteboardLimitContent": "Désolé, la limite d’utilisateur du tableau blanc a été atteinte.",
"whiteboardLimitReference": "Pour plus d’informations merci de visiter",
"whiteboardLimitReferenceUrl": "notre site web",
"whiteboardLimitTitle": "Utilisation du tableau blanc limitée",
"yourEntireScreen": "Votre écran entier"
},
"documentSharing": {
@@ -445,6 +499,9 @@
"title": "Intégrer cette réunion"
},
"feedback": {
"accessibilityLabel": {
"yourChoice": "Votre choix: {{rating}}"
},
"average": "Moyen",
"bad": "Mauvais",
"detailsLabel": "Dites nous en plus à ce sujet.",
@@ -505,6 +562,7 @@
"password": "$t(lockRoomPasswordUppercase) :",
"reachedLimit": "Vous avez atteint la limite de votre abonnement.",
"sip": "adresse SIP",
"sipAudioOnly": "Adresse SIP en audio uniquement",
"title": "Partager",
"tooltip": "Partager le lien et les informations de connexion pour cette conférence",
"upgradeOptions": "Veuillez vérifier les options de mise à niveau"
@@ -544,7 +602,7 @@
},
"largeVideo": {
"screenIsShared": "Vous êtes en train de partager votre écran",
"showMeWhatImSharing": "Me montrer ce que je partage"
"showMeWhatImSharing": "M'afficher ce que je partage"
},
"liveStreaming": {
"busy": "Nous tentons de libérer des ressources de diffusion. Veuillez réessayez dans quelques minutes.",
@@ -609,13 +667,13 @@
"knockingParticipantList": "Liste des participants en attente",
"lobbyChatStartedNotification": "Un modérateur dialogue en salle d'attente avec {{attendee}}",
"lobbyChatStartedTitle": "Un modérateur dialogue en salle d'attente avec vous.",
"lobbyClosed": "La salle d'attente a été fermée.",
"nameField": "Saisissez votre nom",
"notificationLobbyAccessDenied": "{{targetParticipantName}} a été refusé par {{originParticipantName}}",
"notificationLobbyAccessGranted": "{{targetParticipantName}} a été accepté par {{originParticipantName}}",
"notificationLobbyDisabled": "Le mode salle d'attente a été désactivé par {{originParticipantName}}",
"notificationLobbyEnabled": "Le mode salle d'attente a été activé par {{originParticipantName}}",
"notificationTitle": "Salle d'attente",
"passwordField": "Veuillez saisir le mot de passe de la réunion",
"passwordJoinButton": "Rejoindre",
"title": "Salle d'attente",
"toggleLabel": "Activer la salle d'attente"
@@ -648,6 +706,8 @@
"sessionToken": "Token de la session",
"start": "Démarrer l'enregistrement",
"stop": "Arrêter l'enregistrement",
"stopping": "Arrêt de l'enregistrement",
"wait": "Patienter pendant la sauvegarde de votre enregistrement.",
"yes": "Oui"
},
"lockRoomPassword": "mot de passe",
@@ -669,8 +729,11 @@
"connectedTwoMembers": "{{first}} et {{second}} ont rejoint la réunion",
"dataChannelClosed": "Qualité vidéo dégradée",
"dataChannelClosedDescription": "Le canal de communication avec le Bridge a été interrompu, la qualité vidéo se trouve limitée à sa valeur la plus faible.",
"disabledIframe": "L'intégration Iframe est uniquement destinée à des démos, cet appel se terminera dans {{timeout}} minutes.",
"disabledIframeSecondary": "L'intégration Iframe de {{domaine}} est uniquement destinée à des démos, cet appel se terminera dans {{timeout}} minutes.",
"disconnected": "déconnecté",
"displayNotifications": "Afficher les notifications pour",
"dontRemindMe": "Ne pas me le rappeler",
"focus": "Focus de conférence",
"focusFail": "{{component}} n'est pas disponible - réessayez dans {{ms}} sec",
"gifsMenu": "GIPHY",
@@ -679,6 +742,7 @@
"invitedOneMember": "{{name}} a été invité(e)",
"invitedThreePlusMembers": "{{name}} et {{count}} autres ont été invités",
"invitedTwoMembers": "{{first}} et {{second}} ont été invités",
"joinMeeting": "Rejoindre",
"kickParticipant": "{{kicked}} a été expulsé par {{kicker}}",
"leftOneMember": "{{name}} a quitté la réunion",
"leftThreePlusMembers": "{{name}} et beaucoup d'autres ont quitté la réunion",
@@ -713,7 +777,6 @@
"newDeviceCameraTitle": "Nouvelle caméra détectée",
"noiseSuppressionDesktopAudioDescription": "La suppression de bruit ne peut pas être activée en même temps que la partage audio du système, veuillez le désactiver et réessayer.",
"noiseSuppressionFailedTitle": "Échec du démarrage de la suppression de bruit",
"noiseSuppressionNoTrackDescription": "Veuillez activer votre microphone d'abord.",
"noiseSuppressionStereoDescription": "La suppression de bruit d’une source stéréo n’est pas encore supportée.",
"oldElectronClientDescription1": "Vous semblez utiliser une ancienne version du client Jitsi Meet qui présente des failles de sécurité connues. Veuillez vous assurer de mettre à jour vers notre ",
"oldElectronClientDescription2": "dernière build",
@@ -743,7 +806,9 @@
"videoUnmuteBlockedDescription": "Le rétablissement de la vidéo a été bloqué temporairement en raison de limites système.",
"videoUnmuteBlockedTitle": "Rétablissement de la caméra bloqué !",
"viewLobby": "Voir la salle d'attente",
"waitingParticipants": "{{waitingParticipants}} personnes"
"waitingParticipants": "{{waitingParticipants}} personnes",
"whiteboardLimitDescription": "Veuillez sauvegarder votre progression, car la limite d’utilisation du tableau blanc sera bientôt atteinte et celui-ci sera fermé.",
"whiteboardLimitTitle": "Utiilisation du tableau blanc"
},
"participantsPane": {
"actions": {
@@ -754,6 +819,7 @@
"askUnmute": "Demander de réactiver le micro",
"audioModeration": "Rouvrir leur micro",
"blockEveryoneMicCamera": "Bloquer tous les micros et caméras",
"breakoutRooms": "Salles annexes",
"invite": "Inviter quelqu'un",
"moreModerationActions": "Options de modération supplémentaires",
"moreModerationControls": "Options de modération supplémentaires",
@@ -771,6 +837,7 @@
"headings": {
"lobby": "Salle d'attente ({{count}})",
"participantsList": "Participants de la réunion ({{count}})",
"visitorRequests": "(Demande {{count}} )",
"visitors": "Visiteurs {{count}}",
"waitingLobby": "Dans la salle d'attente ({{count}})"
},
@@ -805,8 +872,8 @@
"results": {
"changeVote": "Changer le vote",
"empty": "Il n'y a pas encore de sondages dans cette réunion. Démarrez un sondage ici !",
"hideDetailedResults": "Cacher les détails",
"showDetailedResults": "Montrer les détails",
"hideDetailedResults": "Masquer les détails",
"showDetailedResults": "Afficher les détails",
"vote": "Voter"
}
},
@@ -864,9 +931,11 @@
"lookGood": "Il semble que votre microphone fonctionne correctement",
"or": "ou",
"premeeting": "Pré-séance",
"proceedAnyway": "Continuer quand même",
"screenSharingError": "Erreur de partage d'écran:",
"showScreen": "Activer l'écran de pré-séance",
"startWithPhone": "Commencez avec l'audio du téléphone",
"unsafeRoomConsent": "Je comprends les risques et je veux quand même rejoindre cette réunion",
"videoOnlyError": "Erreur vidéo:",
"videoTrackError": "Impossible de créer une piste vidéo.",
"viewAllNumbers": "voir tous les numéros"
@@ -929,7 +998,6 @@
"limitNotificationDescriptionNative": "En raison d'une forte demande, votre enregistrement sera limité à {{limit}} min. Pour des enregistrements illimités, essayez <3> {{app}} </3>.",
"limitNotificationDescriptionWeb": "En raison d'une forte demande, votre enregistrement sera limité à {{limit}} min. Pour des enregistrements illimités, essayez <a href={{url}} rel='noopener noreferrer' target='_blank'> {{app}} </a>.",
"linkGenerated": "Nous avons généré un lien à votre enregistrement.",
"live": "DIRECT",
"localRecordingNoNotificationWarning": "Le démarrage de l’enregistrement ne sera pas annoncé aux autres participants. Vous devrez les informer par vous-même que la réunion sera enregistrée.",
"localRecordingNoVideo": "La vidéo n'est pas en cours d’enregistrement",
"localRecordingStartWarning": "Assurez-vous d’arrêter l’enregistrement vidéo avant de quitter la réunion afin de pouvoir le sauvegarder.",
@@ -946,13 +1014,15 @@
"onBy": "{{name}} a démarré l'enregistrement",
"onlyRecordSelf": "Enregistrer seulement mon audio et ma vidéo.",
"pending": "Préparation de l'enregistrement de la réunion ...",
"rec": "REC",
"recordAudioAndVideo": "Enregistrer l'audio et la vidéo",
"recordTranscription": "Enregistrer la transcription",
"saveLocalRecording": "Sauvegarder l’enregistrement local (Beta)",
"serviceDescription": "Votre enregistrement sera enregistré par le service dédié",
"serviceDescriptionCloud": "Enregistrement Cloud",
"serviceDescriptionCloudInfo": "Les conférences enregistrées sont automatiquement supprimées 24h après leur heure d'enregistrement.",
"serviceName": "Service d'enregistrement",
"sessionAlreadyActive": "Cette session est déjà en cours d'enregistrement ou de diffusion.",
"showAdvancedOptions": "Afficher les options avancées",
"signIn": "Se connecter",
"signOut": "Se déconnecter",
"surfaceError": "Veuillez sélectionner l’onglet courant.",
@@ -968,8 +1038,14 @@
"security": {
"about": "Vous pouvez ajouter un mot de passe à votre réunion. Les participants devront fournir le mot de passe avant de pouvoir rejoindre la réunion.",
"aboutReadOnly": "Les modérateurs peuvent ajouter un mot de passe à la réunion. Les participants devront fournir le mot de passe avant de pouvoir rejoindre la réunion.",
"insecureRoomNameWarning": "Le nom de la salle est peu sûr. Des participants non désirés peuvent rejoindre votre réunion. Pensez à sécuriser votre réunion en cliquant sur le bouton de sécurité.",
"title": "Options de sécurité"
"insecureRoomNameWarningNative": "Le nom de la réunion n’est pas sûr. Des participants non voulus pourraient rejoindre cette réunion. {{recommendAction}} En apprendre plus sur la sécurisation des réunions.",
"insecureRoomNameWarningWeb": "Le nom de la réunion n’est pas sûr. Des participants non voulus pourraient rejoindre cette réunion. {{recommendAction}} En apprendre plus sur la sécurisation des réunions <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">here</a>.",
"title": "Options de sécurité",
"unsafeRoomActions": {
"meeting": "Envisagez de sécuriser votre réunion en utilisant le bouton options de sécurité.",
"prejoin": "Envisagez d'utiliser un nom plus unique",
"welcome": "Envisagez d'utiliser un nom plus unique ou choisissez en un parmi ceux suggérés"
}
},
"settings": {
"audio": "Audio",
@@ -1002,7 +1078,7 @@
"participantKnocking": "un participant en salle d’attente",
"participantLeft": "un participant quitte",
"playSounds": "Jouer un son quand",
"reactions": "il y a une réaction à la réunion",
"reactions": "Il y a une réaction à la réunion",
"sameAsSystem": "Identique au système ({{label}})",
"selectAudioOutput": "Sortie audio",
"selectCamera": "Caméra",
@@ -1023,6 +1099,7 @@
"alertOk": "D'accord",
"alertTitle": "Avertissement",
"alertURLText": "L'URL du serveur est invalide",
"apply": "Appliquer",
"buildInfoSection": "Informations de build",
"conferenceSection": "Conférence",
"disableCallIntegration": "Désactiver l'intégration d'appels native",
@@ -1033,12 +1110,14 @@
"displayNamePlaceholderText": "Ex : Jean Dupond",
"email": "Email",
"emailPlaceholderText": "email@example..com",
"gavatarMessage": "Si votre email est associé à un compte Gravatar, nous allons l’utiliser pour afficher votre image de profil.",
"goTo": "Aller à",
"header": "Paramètres",
"help": "Aide",
"links": "Liens",
"privacy": "Confidentialité",
"profileSection": "Profil",
"sdkVersion": "Version du SDK",
"serverURL": "URL du serveur",
"showAdvanced": "Afficher les paramètres avancés",
"startCarModeInLowBandwidthMode": "Commencer le mode voiture en mode bande passante réduite",
@@ -1091,17 +1170,26 @@
"audioOnly": "Activer / Désactiver le mode voix uniquement",
"audioRoute": "Sélectionner la source audio",
"boo": "Hou",
"breakoutRoom": "Rejoindre/quitter une salle annexe",
"breakoutRooms": "Rejoindre / quitter une salle annexe",
"callQuality": "Ajuster la qualité vidéo",
"carmode": "Mode voiture",
"cc": "Activer / Désactiver les sous-titres",
"chat": "Afficher / Masquer la discussion instantanée",
"clap": "Applaudir",
"closeChat": "Fermer la discussion instantanée",
"closeMoreActions": "Fermer le menu plus d'actions",
"closeParticipantsPane": "Fermer le panneau des participants",
"collapse": "Plier",
"document": "Activer / Désactiver le document partagé",
"documentClose": "Fermer le document partagé",
"documentOpen": "Ouvrir le document partagé",
"download": "Télécharger nos applications",
"embedMeeting": "Intégrer la réunion",
"endConference": "Terminer la réunion pour tout le monde",
"enterFullScreen": "Passer en mode plein écran",
"enterTileView": "Passer en vue mosaïque",
"exitFullScreen": "Quitter le mode plein écran",
"exitTileView": "Quitter la vue mosaïque",
"expand": "Développer",
"feedback": "Laisser des commentaires",
"fullScreen": "Activer / Désactiver le plein écran",
@@ -1110,6 +1198,7 @@
"hangup": "Quitter la conversation",
"heading": "Barre d'outils",
"help": "Aide",
"hideWhiteboard": "Masquer le tableau blanc",
"invite": "Inviter des participants",
"kick": "Expulser le participant",
"laugh": "Rire",
@@ -1119,6 +1208,7 @@
"lobbyButton": "Activer / Désactiver le mode salle d'attente",
"localRecording": "Activer / Désactiver les contrôles d'enregistrement local",
"lockRoom": "Activer / Désactiver le mot de passe de la réunion",
"lowerHand": "Baisser la main",
"moreActions": "Activer / Désactiver le menu d'actions supplémentaires",
"moreActionsMenu": "Menu d'actions supplémentaires",
"moreOptions": "Voir plus d'options",
@@ -1127,12 +1217,15 @@
"muteEveryoneElse": "Couper le micro de tous les autres",
"muteEveryoneElsesVideoStream": "Couper la caméra de tous les autres",
"muteEveryonesVideoStream": "Couper la caméra de tout le monde",
"muteGUMPending": "Connection de votre microphone",
"noiseSuppression": "Suppression du bruit",
"openChat": "Ouvrir le chat",
"participants": "Participants",
"pip": "Activer / Désactiver le mode Picture in Picture",
"privateMessage": "Envoyer un message privé",
"profile": "Éditer votre profil",
"raiseHand": "Lever la main",
"reactions": "Réactions",
"reactionsMenu": "Ouvrir / fermer le menu réactions",
"recording": "Activer / Désactiver l'enregistrement",
"remoteMute": "Couper le micro du participant",
@@ -1146,15 +1239,20 @@
"sharedvideo": "Démarrer / Arrêter le partage de vidéo",
"shortcuts": "Afficher / Masquer les raccourcis",
"show": "Afficher en premier plan",
"showWhiteboard": "Afficher le tableux blanc",
"silence": "Silence",
"speakerStats": "Afficher / Cacher les statistiques de parole",
"speakerStats": "Afficher / Masquer les statistiques de parole",
"stopScreenSharing": "Arrêter le partage d'écran",
"stopSharedVideo": "Arrêter la vidéo partagée",
"surprised": "Surpris",
"tileView": "Activer / Désactiver la vue mosaïque",
"toggleCamera": "Changer de caméra",
"toggleFilmstrip": "Afficher ou masquer les vignettes vidéo",
"unmute": "Rétablir le son",
"videoblur": "Activer / désactiver le floutage",
"videomute": "Activer / Couper la vidéo",
"whiteboard": "Afficher / Cacher le tableau blanc"
"videomuteGUMPending": "Connexion de votre caméra",
"videounmute": "Démarrer la vidéo"
},
"addPeople": "Ajouter des personnes à votre appel",
"audioOnlyOff": "Désactiver le mode bande passante réduite",
@@ -1167,6 +1265,7 @@
"chat": "Ouvrir / Fermer le chat",
"clap": "Applaudir",
"closeChat": "Fermer le chat",
"closeParticipantsPane": "Fermer le panneau des participants",
"closeReactionsMenu": "Fermer le menu réactions",
"disableNoiseSuppression": "Arrêter la suppression du bruit",
"disableReactionSounds": "Vous pouvez interdire les réactions sonores à cette réunion",
@@ -1185,7 +1284,7 @@
"giphy": "Activer/désactiver le menu GIPHY",
"hangup": "Quitter",
"help": "Aide",
"hideWhiteboard": "Cacher le tableau blanc",
"hideWhiteboard": "Masquer le tableau blanc",
"invite": "Inviter des participants",
"joinBreakoutRoom": "Rejoindre salle annexe",
"laugh": "Rire",
@@ -1203,6 +1302,7 @@
"mute": "Muet / Actif",
"muteEveryone": "Couper le micro à tout le monde",
"muteEveryonesVideo": "Couper la caméra de tout le monde",
"muteGUMPending": "Connection de votre microphone",
"noAudioSignalDesc": "Si vous n'avez pas délibérément coupé le son des paramètres du système ou du matériel, envisagez de changer de périphérique utilisé.",
"noAudioSignalDescSuggestion": "Si vous n'avez pas délibérément coupé le son des paramètres du système ou du matériel, pensez à utiliser le périphérique suivant :",
"noAudioSignalDialInDesc": "Vous pouvez également appeler en utilisant :",
@@ -1225,6 +1325,7 @@
"reactionLike": "Envoyer réaction approuver",
"reactionSilence": "Envoyer réaction silence",
"reactionSurprised": "Envoyer réaction surprise",
"reactions": "Reactions",
"security": "Options de sécurité",
"selectBackground": "Sélectionner un arrière-plan",
"shareRoom": "Inviter quelqu'un",
@@ -1244,8 +1345,11 @@
"talkWhileMutedPopup": "Vous voulez parler ? Votre micro est coupé.",
"tileViewToggle": "Activer / Désactiver la vue mosaïque",
"toggleCamera": "Changer de caméra",
"unmute": "Rétablir le son",
"videoSettings": "Paramètres vidéo",
"videomute": "Démarrer / Arrêter la caméra"
"videomute": "Arrêter la caméra",
"videomuteGUMPending": "Connexion de votre caméra",
"videounmute": "Démarrer la caméra"
},
"transcribing": {
"ccButtonTooltip": "Activer / Désactiver les sous-titres",
@@ -1253,7 +1357,8 @@
"expandedLabel": "La transcription est actuellement activée",
"failedToStart": "Échec de démarrage de la transcription",
"labelToolTip": "La transcription de la réunion est en cours",
"off": "La transcription désactivée",
"off": "La transcription est désactivée",
"on": "La transcription est activée",
"pending": "Préparation de la transcription de la réunion ...",
"sourceLanguageDesc": "Actuellement, la langue de la réunion est sélectionnée à <b>{{sourceLanguage}}</b>. <br/> Vous pouvez la changer à partir de ",
"sourceLanguageHere": "ici",
@@ -1316,7 +1421,7 @@
"domuteVideoOfOthers": "Couper la caméra des autres",
"flip": "Miroir",
"grantModerator": "Donner des droits de modérateur",
"hideSelfView": "Cacher l'affichage de votre propre vidéo",
"hideSelfView": "Masquer l'affichage de votre propre vidéo",
"kick": "Exclure",
"mirrorVideo": "Inverser ma vidéo",
"moderator": "Modérateur",
@@ -1326,13 +1431,17 @@
"remoteControl": "Démarrer / Arrêter le contrôle à distance",
"screenSharing": "Cette personne partage son écran",
"show": "Afficher en premier plan",
"showSelfView": "Montrer votre propre vidéo",
"showSelfView": "Afficher votre propre vidéo",
"unpinFromStage": "Ne plus garder affiché",
"verify": "Vérifier le participant",
"videoMuted": "Caméra coupée",
"videomute": "Le participant a arrêté la caméra"
},
"virtualBackground": {
"accessibilityLabel": {
"currentBackground": "Arrière-plan actuel: {{background}}",
"selectBackground": "Sélectionner un arrière-plan"
},
"addBackground": "Ajouter un arrière-plan",
"apply": "Appliquer",
"backgroundEffectError": "Erreur dans l'application de l'effet d'arrière-plan.",
@@ -1356,7 +1465,14 @@
"webAssemblyWarning": "WebAssembly non supporté",
"webAssemblyWarningDescription": "WebAssembly invalidé ou non supporté par ce navigateur"
},
"visitorsLabel": "Nombre de visiteurs: {{count}}",
"visitors": {
"chatIndicator": "(visiteur)",
"labelTooltip": "Nombre de Visiteurs",
"notification": {
"description": "Pour participer lever la main.",
"title": "Vous êtes visiteur dans cette réunion"
}
},
"volumeSlider": "Curseur de volume",
"welcomepage": {
"accessibilityLabel": {

File diff suppressed because it is too large Load Diff

View File

@@ -219,7 +219,9 @@
"joinInBrowser": "Pievienojieties pārlūkā",
"launchMeetingLabel": "Kā vēlaties pievienoties šai sapulcei?",
"launchWebButton": "Palaist tīmekļa pārlūkā",
"noDesktopApp": "Vai jums nav lietotnes?",
"noMobileApp": "Vai jums nav lietotnes?",
"or": "vai",
"termsAndConditions": "Turpinot jūs piekrītat mūsu <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>pakalpojumu sniegšanas noteikumiem.</a>",
"title": "Notiek jūsu sapulces palaišana lietotnē {{app}}...",
"titleNew": "Notiek jūsu sapulces palaišana ...",
@@ -837,6 +839,7 @@
"headings": {
"lobby": "Vestibils ({{count}})",
"participantsList": "Sapulces dalībnieki ({{count}})",
"visitorRequests": " (pieprasījumi {{count}})",
"visitors": "Apmeklētāji ({{count}})",
"waitingLobby": "Gaida vestibilā ({{count}})"
},
@@ -997,7 +1000,6 @@
"limitNotificationDescriptionNative": "Lielā pieprasījuma dēļ jūsu ieraksts tiks ierobežots līdz {{limit}} min. Lai iegūtu neierobežotu ierakstu skaitu, izmēģiniet <3>{{app}}</3>.",
"limitNotificationDescriptionWeb": "Lielā pieprasījuma dēļ jūsu ieraksts tiks ierobežots līdz {{limit}} min. Lai iegūtu neierobežotu ierakstu skaitu, izmēģiniet <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"linkGenerated": "Mēs esam izveidojuši saiti uz jūsu ierakstu.",
"live": "ĒTERĀ",
"localRecordingNoNotificationWarning": "Ieraksts netiks izziņots citiem dalībniekiem. Jums būs jāpaziņo viņiem, ka sapulce tiek ierakstīta.",
"localRecordingNoVideo": "Video netiek ierakstīts",
"localRecordingStartWarning": "Pirms iziešanas no sapulces, lūdzu, apturiet ierakstīšanu, lai to saglabātu.",
@@ -1014,14 +1016,16 @@
"onBy": "{{name}} ieslēdza ierakstu",
"onlyRecordSelf": "Ierakstīt tikai manas audio un video straumes",
"pending": "Gatavojas ierakstīt sapulci...",
"rec": "Notiek ieraksts",
"recordAudioAndVideo": "Ierakstīt audio un video",
"recordTranscription": "Ierakstīt transkripciju",
"saveLocalRecording": "Ieraksta faila saglabāšana lokāli (beta)",
"serviceDescription": "Jūsu ierakstu saglabās attiecīgais pakalpojums",
"serviceDescriptionCloud": "Mākoņa ierakstīšana",
"serviceDescriptionCloud": "Ierakstīšana mākonī",
"serviceDescriptionCloudInfo": "Ierakstītās sapulces tiek automātiski dzēstas 24 stundas pēc to ierakstīšanas laika.",
"serviceName": "Ieraksta pakalpojums",
"sessionAlreadyActive": "Šī sesija jau tiek ierakstīta vai straumēta tiešraidē.",
"signIn": "Ierakstīties",
"showAdvancedOptions": "Papildus iespējas",
"signIn": "Pierakstīties",
"signOut": "Izrakstīties",
"surfaceError": "Lūdzu, izvēlieties pašreizējo cilni.",
"title": "Ieraksts",
@@ -1351,12 +1355,9 @@
},
"transcribing": {
"ccButtonTooltip": "Iesl./izsl. subtitrus",
"error": "Transkripcijas kļūme. Lūdzu, mēģiniet vēlāk.",
"expandedLabel": "Transkripcija ieslēgta",
"expandedLabel": "Transkripcija ir ieslēgta",
"failedToStart": "Neizdevās sākt transkripciju",
"labelToolTip": "Notiek sapulces transkripcija.",
"off": "Transkripcija izslēgta",
"pending": "Gatavojas veikt sapulces transkripciju...",
"labelToolTip": "Notiek sapulces transkripcija",
"sourceLanguageDesc": "Pašlaik sapulces valoda ir iestatīta uz <b>{{sourceLanguage}}</b>. <br/> Varat to mainīt no ",
"sourceLanguageHere": "šeit",
"start": "Iesl. subtitru rādīšanu",

View File

@@ -49,13 +49,18 @@
"remove": "Удалить",
"sendToBreakoutRoom": "Отправить участника к:"
},
"breakoutList": "Сессионные залы",
"buttonLabel": "Сессионные залы",
"defaultName": "Сессионный зал #{{index}}",
"hideParticipantList": "Скрыть список участников",
"mainRoom": "Главная комната",
"notifications": {
"joined": "Вход в сессионный зал \"{{name}}\"",
"joinedMainRoom": "Вход в главную комнату",
"joinedTitle": "Сессионные залы"
}
},
"showParticipantList": "Показать участников",
"title": "Сессионные залы"
},
"calendarSync": {
"addMeetingURL": "Добавить ссылку конференции",
@@ -372,6 +377,7 @@
"sendPrivateMessageTitle": "Отправить личное сообщение?",
"serviceUnavailable": "Служба недоступна",
"sessTerminated": "Связь прервана",
"sessTerminatedReason": "Встреча прервана",
"sessionRestarted": "Вызов перезапущен из-за проблемы с подключением.",
"shareAudio": "Продолжить",
"shareAudioTitle": "Как поделиться аудио",

View File

@@ -222,6 +222,7 @@
"Share": "Paylaş",
"Submit": "Gönder",
"WaitForHostMsg": "Toplantısı henüz başlamadı. Toplantı sahibi sizseniz, lütfen kimlik doğrulaması yapın. Değilseniz lütfen toplantı sahibinin gelmesini bekleyin.",
"WaitingForHostButton": "Toplantı sahibini bekle",
"WaitingForHostTitle": "Toplantı sahibi bekleniyor ...",
"Yes": "Evet",
"accessibilityLabel": {

View File

@@ -219,7 +219,9 @@
"joinInBrowser": "Join in browser",
"launchMeetingLabel": "How do you want to join this meeting?",
"launchWebButton": "Launch in web",
"noDesktopApp": "You don’t have the app?",
"noMobileApp": "You don’t have the app?",
"or": "OR",
"termsAndConditions": "By continuing you agree to our <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>terms & conditions.</a>",
"title": "Launching your meeting in {{app}}...",
"titleNew": "Launching your meeting ...",
@@ -558,6 +560,7 @@
"noNumbers": "No dial-in numbers.",
"noPassword": "None",
"noRoom": "No room was specified to dial-in into.",
"noWhiteboard": "Could not load the whiteboard.",
"numbers": "Dial-in Numbers",
"password": "$t(lockRoomPasswordUppercase): ",
"reachedLimit": "You have reached the limit of your plan.",
@@ -565,7 +568,8 @@
"sipAudioOnly": "SIP audio only address",
"title": "Share",
"tooltip": "Share link and dial-in info for this meeting",
"upgradeOptions": "Please check the upgrade options on"
"upgradeOptions": "Please check the upgrade options on",
"whiteboardError": "Error loading the whiteboard. Please try again later."
},
"inlineDialogFailure": {
"msg": "We stumbled a bit.",
@@ -615,7 +619,7 @@
"errorAPI": "An error occurred while accessing your YouTube broadcasts. Please try logging in again.",
"errorLiveStreamNotEnabled": "Live Streaming is not enabled on {{email}}. Please enable live streaming or log into an account with live streaming enabled.",
"expandedOff": "The live streaming has stopped",
"expandedOn": "The meeting is currently being streamed to YouTube.",
"expandedOn": "The meeting is currently being live streamed",
"expandedPending": "The live streaming is being started...",
"failedToStart": "Live Streaming failed to start",
"getStreamKeyManually": "We weren’t able to fetch any live streams. Try getting your live stream key from YouTube.",
@@ -800,6 +804,9 @@
"startSilentTitle": "You joined with no audio output!",
"suboptimalBrowserWarning": "We are afraid your meeting experience isn't going to be that great here. We are looking for ways to improve this, but until then please try using one of the <a href='{{recommendedBrowserPageLink}}' target='_blank'>fully supported browsers</a>.",
"suboptimalExperienceTitle": "Browser Warning",
"suggestRecordingAction": "Start",
"suggestRecordingDescription": "Would you like to start a recording?",
"suggestRecordingTitle": "Record this meeting",
"unmute": "Unmute",
"videoMutedRemotelyDescription": "You can always turn it on again.",
"videoMutedRemotelyTitle": "Your video has been turned off by {{participantDisplayName}}",
@@ -985,7 +992,7 @@
"error": "Recording failed. Please try again.",
"errorFetchingLink": "Error fetching recording link.",
"expandedOff": "Recording has stopped",
"expandedOn": "The meeting is currently being recorded.",
"expandedOn": "The meeting is currently being recorded",
"expandedPending": "Recording is being started...",
"failedToStart": "Recording failed to start",
"fileSharingdescription": "Share the recording link with the meeting participants",
@@ -1353,13 +1360,9 @@
},
"transcribing": {
"ccButtonTooltip": "Start / Stop subtitles",
"error": "Transcribing failed. Please try again.",
"expandedLabel": "Transcribing is currently on",
"failedToStart": "Transcribing failed to start",
"labelToolTip": "The meeting is being transcribed",
"off": "Transcribing stopped",
"on": "Transcribing started",
"pending": "Preparing to transcribe the meeting...",
"sourceLanguageDesc": "Currently the meeting language is set to <b>{{sourceLanguage}}</b>. <br/> You can change it from ",
"sourceLanguageHere": "here",
"start": "Start showing subtitles",
@@ -1529,6 +1532,7 @@
"whiteboard": {
"accessibilityLabel": {
"heading": "Whiteboard"
}
},
"screenTitle": "Whiteboard"
}
}

View File

@@ -1,35 +1,29 @@
/* eslint-disable */
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
/**
* Metro configuration for React Native
* https://github.com/facebook/react-native
* Metro configuration
* https://reactnative.dev/docs/metro
*
* @format
* @type {import('metro-config').MetroConfig}
*/
const { getDefaultConfig } = require('metro-config');
module.exports = (async () => {
const {
resolver: {
sourceExts,
assetExts
}
} = await getDefaultConfig();
return {
transformer: {
babelTransformerPath: require.resolve('react-native-svg-transformer'),
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
resolver: {
assetExts: assetExts.filter(ext => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg']
}
const {
resolver: {
sourceExts,
assetExts
}
})();
} = getDefaultConfig();
const config = {
transformer: {
babelTransformerPath: require.resolve('react-native-svg-transformer')
},
resolver: {
assetExts: assetExts.filter(ext => ext !== 'svg'),
sourceExts: [ ...sourceExts, 'svg' ]
}
};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);

View File

@@ -113,7 +113,7 @@ import { isAudioMuteButtonDisabled } from '../../react/features/toolbox/function
import { setTileView, toggleTileView } from '../../react/features/video-layout/actions.any';
import { muteAllParticipants } from '../../react/features/video-menu/actions';
import { setVideoQuality } from '../../react/features/video-quality/actions';
import { toggleWhiteboard } from '../../react/features/whiteboard/actions.any';
import { toggleWhiteboard } from '../../react/features/whiteboard/actions.web';
import { getJitsiMeetTransport } from '../transport';
import {

View File

@@ -229,6 +229,9 @@ export default class LargeVideoManager {
preUpdate.then(() => {
const { id, stream, videoType, resolve } = this.newStreamData;
this.newStreamData = null;
const state = APP.store.getState();
const shouldHideSelfView = getHideSelfView(state);
const localId = getLocalParticipant(state)?.id;
@@ -239,8 +242,6 @@ export default class LargeVideoManager {
// the video container type (Etherpad, SharedVideo, VideoContainer).
const isVideoContainer = LargeVideoManager.isVideoContainer(videoType);
this.newStreamData = null;
logger.debug(`Scheduled large video update for ${id}`);
this.state = videoType;
// eslint-disable-next-line no-shadow
@@ -287,10 +288,6 @@ export default class LargeVideoManager {
|| streamingStatusActive
);
this.videoTrack?.jitsiTrack?.getVideoType() === VIDEO_TYPE.DESKTOP
&& logger.debug(`Remote track ${videoTrack?.jitsiTrack}, isVideoMuted=${isVideoMuted},`
+ ` streamingStatusActive=${streamingStatusActive}, isVideoRenderable=${isVideoRenderable}`);
const isAudioOnly = APP.conference.isAudioOnly();
// Multi-stream is not supported on plan-b endpoints even if its is enabled via config.js. A virtual
@@ -303,6 +300,10 @@ export default class LargeVideoManager {
= isVideoContainer
&& ((isAudioOnly && videoType !== VIDEO_TYPE.DESKTOP) || !isVideoRenderable || legacyScreenshare);
logger.debug(`scheduleLargeVideoUpdate: Remote track ${videoTrack?.jitsiTrack}, isVideoMuted=${
isVideoMuted}, streamingStatusActive=${streamingStatusActive}, isVideoRenderable=${
isVideoRenderable}, showAvatar=${showAvatar}`);
let promise;
// do not show stream if video is muted

View File

@@ -26,6 +26,13 @@ const FADE_DURATION_MS = 300;
const logger = Logger.getLogger(__filename);
/**
* List of container events that we are going to process for the large video.
*
* NOTE: Currently used only for logging for debug purposes.
*/
const containerEvents = [ 'abort', 'canplaythrough', 'ended', 'error', 'stalled', 'suspend', 'waiting' ];
/**
* Returns an array of the video dimensions, so that it keeps it's aspect
* ratio and fits available area with it's larger dimension. This method
@@ -242,11 +249,18 @@ export class VideoContainer extends LargeContainer {
this.wrapperParent = document.getElementById('largeVideoElementsContainer');
this.avatarHeight = document.getElementById('dominantSpeakerAvatarContainer').getBoundingClientRect().height;
this.video.onplaying = function(event) {
logger.debug('Large video is playing!');
if (typeof resizeContainer === 'function') {
resizeContainer(event);
}
};
containerEvents.forEach(event => {
this.video.addEventListener(event, () => {
logger.debug(`${event} handler was called for the large video.`);
});
});
/**
* A Set of functions to invoke when the video element resizes.
*
@@ -255,6 +269,7 @@ export class VideoContainer extends LargeContainer {
this._resizeListeners = new Set();
this.video.onresize = this._onResize.bind(this);
this._play = this._play.bind(this);
}
/**
@@ -455,6 +470,30 @@ export class VideoContainer extends LargeContainer {
this._resizeListeners.delete(callback);
}
/**
* Plays the large video element.
*
* @param {number} retries - Number of retries to play the large video if play fails.
* @returns {void}
*/
_play(retries = 0) {
this.video.play()
.then(() => {
logger.debug(`Successfully played large video after ${retries + 1} retries!`);
})
.catch(e => {
if (retries < 3) {
logger.debug(`Error while trying to playing the large video. Will retry after 1s. Retries: ${
retries}. Error: ${e}`);
window.setTimeout(() => {
this._play(retries + 1);
}, 1000);
} else {
logger.error(`Error while trying to playing the large video after 3 retries: ${e}`);
}
});
}
/**
* Update video stream.
* @param {string} userID
@@ -464,6 +503,8 @@ export class VideoContainer extends LargeContainer {
setStream(userID, stream, videoType) {
this.userId = userID;
if (this.stream === stream && !stream?.forceStreamToReattach) {
logger.debug(`SetStream on the large video for user ${userID} ignored: the stream is not changed!`);
// Handles the use case for the remote participants when the
// videoType is received with delay after turning on/off the
// desktop sharing.
@@ -488,22 +529,28 @@ export class VideoContainer extends LargeContainer {
this.videoType = videoType;
if (!stream) {
logger.debug('SetStream on the large video is called without a stream argument!');
return;
}
if (this.video) {
logger.debug(`Attaching a remote track to the large video for user ${userID}`);
stream.attach(this.video).catch(error => {
logger.error(`Attaching the remote track ${stream} has failed with `, error);
logger.error(`Attaching the remote track ${stream} to large video has failed with `, error);
});
// Ensure large video gets play() called on it when a new stream is attached to it. This is necessary in the
// case of Safari as autoplay doesn't kick-in automatically on Safari 15 and newer versions.
browser.isWebKitBased() && this.video.play();
browser.isWebKitBased() && this._play();
const flipX = stream.isLocal() && this.localFlipX && !this.isScreenSharing();
this.video.style.transform = flipX ? 'scaleX(-1)' : 'none';
this._updateBackground();
} else {
logger.debug(`SetStream on the large video won't attach a track for ${
userID} because no large video element was found!`);
}
}

View File

@@ -154,6 +154,8 @@ const VideoLayout = {
updateLargeVideo(id, forceUpdate, forceStreamToReattach = false) {
if (!largeVideo) {
logger.debug(`Ignoring large video update with user id ${id}: large video not initialized yet!`);
return;
}
const currentContainer = largeVideo.getCurrentContainer();

1214
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,7 @@
"@react-native-community/netinfo": "11.1.0",
"@react-native-community/slider": "4.4.3",
"@react-native-google-signin/google-signin": "10.1.0",
"@react-native/metro-config": "0.72.9",
"@react-navigation/bottom-tabs": "6.5.8",
"@react-navigation/elements": "1.3.18",
"@react-navigation/material-top-tabs": "6.6.3",
@@ -47,7 +48,8 @@
"@vladmandic/human": "2.6.5",
"@vladmandic/human-models": "2.5.9",
"@xmldom/xmldom": "0.8.7",
"amplitude-js": "8.2.1",
"abab": "2.0.6",
"amplitude-js": "8.21.9",
"base64-js": "1.5.1",
"bc-css-flags": "3.0.0",
"clipboard-copy": "4.0.1",
@@ -65,7 +67,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1758.0.0+e6298b6c/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1770.0.0+2d430a18/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -102,7 +104,7 @@
"react-native-svg-transformer": "1.1.0",
"react-native-tab-view": "3.5.2",
"react-native-url-polyfill": "2.0.0",
"react-native-video": "6.0.0-alpha.7",
"react-native-video": "6.0.0-alpha.11",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "118.0.0",
"react-native-webview": "13.5.1",
@@ -115,6 +117,7 @@
"redux-thunk": "2.4.1",
"seamless-scroll-polyfill": "2.1.8",
"semver": "7.5.4",
"text-encoding": "0.7.0",
"tss-react": "4.4.4",
"util": "0.12.1",
"uuid": "8.3.2",
@@ -130,7 +133,7 @@
"@babel/preset-env": "7.21.5",
"@babel/preset-react": "7.16.0",
"@jitsi/eslint-config": "4.1.5",
"@types/amplitude-js": "8.16.2",
"@types/amplitude-js": "8.16.5",
"@types/audioworklet": "0.0.29",
"@types/dom-screen-wake-lock": "1.0.1",
"@types/js-md5": "0.4.3",

View File

@@ -328,6 +328,22 @@ export function createNetworkInfoEvent({ isOnline, networkType, details }:
};
}
/**
* Creates a "not allowed error" event.
*
* @param {string} reason - The reason for the error.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createNotAllowedErrorEvent(reason: string) {
return {
action: 'not.allowed.error',
attributes: {
reason
}
};
}
/**
* Creates an "offer/answer failure" event.
*

View File

@@ -35,6 +35,12 @@ export default class AmplitudeHandler extends AbstractHandler {
this._enabled = false;
};
// Forces sending all events on exit (flushing) via sendBeacon
const onExitPage = () => {
// @ts-ignore
amplitude.getInstance().sendEvents();
};
if (navigator.product === 'ReactNative') {
amplitude.getInstance().init(amplitudeAPPKey);
fixDeviceID(amplitude.getInstance()).then(() => {
@@ -50,7 +56,8 @@ export default class AmplitudeHandler extends AbstractHandler {
includeReferrer: true,
includeUtm,
saveParamsReferrerOncePerSession: false,
onError
onError,
onExitPage
};
// @ts-ignore

View File

@@ -51,5 +51,6 @@ import '../video-layout/middleware';
import '../video-quality/middleware';
import '../videosipgw/middleware';
import '../visitors/middleware';
import '../whiteboard/middleware.any';
import './middleware';

View File

@@ -13,5 +13,6 @@ import '../mobile/react-native-sdk/middleware';
import '../mobile/watchos/middleware';
import '../share-room/middleware';
import '../shared-video/middleware';
import '../whiteboard/middleware.native';
import './middlewares.any';

View File

@@ -22,6 +22,6 @@ import '../talk-while-muted/middleware';
import '../toolbox/middleware';
import '../face-landmarks/middleware';
import '../gifs/middleware';
import '../whiteboard/middleware';
import '../whiteboard/middleware.web';
import './middlewares.any';

View File

@@ -55,3 +55,4 @@ import '../video-layout/reducer';
import '../video-quality/reducer';
import '../videosipgw/reducer';
import '../visitors/reducer';
import '../whiteboard/reducer';

View File

@@ -16,7 +16,6 @@ import '../noise-suppression/reducer';
import '../screenshot-capture/reducer';
import '../talk-while-muted/reducer';
import '../virtual-background/reducer';
import '../whiteboard/reducer';
import '../web-hid/reducer';
import './reducers.any';

View File

@@ -178,6 +178,18 @@ export const DATA_CHANNEL_OPENED = 'DATA_CHANNEL_OPENED';
*/
export const DATA_CHANNEL_CLOSED = 'DATA_CHANNEL_CLOSED';
/**
* The type of (redux) action which indicates that an endpoint message
* sent by another participant to the data channel is received.
*
* {
* type: ENDPOINT_MESSAGE_RECEIVED,
* participant: Object,
* data: Object
* }
*/
export const ENDPOINT_MESSAGE_RECEIVED = 'ENDPOINT_MESSAGE_RECEIVED';
/**
* The type of action which signals that the user has been kicked out from
* the conference.
@@ -333,3 +345,13 @@ export const SET_START_MUTED_POLICY = 'SET_START_MUTED_POLICY';
* }
*/
export const SET_ASSUMED_BANDWIDTH_BPS = 'SET_ASSUMED_BANDWIDTH_BPS';
/**
* The type of (redux) action which updated the conference metadata.
*
* {
* type: UPDATE_CONFERENCE_METADATA,
* metadata: Object
* }
*/
export const UPDATE_CONFERENCE_METADATA = 'UPDATE_CONFERENCE_METADATA';

View File

@@ -1,7 +1,6 @@
import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { IReduxState, IStore } from '../../app/types';
import { endpointMessageReceived } from '../../subtitles/actions.any';
import { setIAmVisitor } from '../../visitors/actions';
import { iAmVisitor } from '../../visitors/functions';
import { overwriteConfig } from '../config/actions';
@@ -48,6 +47,7 @@ import {
DATA_CHANNEL_CLOSED,
DATA_CHANNEL_OPENED,
E2E_RTT_CHANGED,
ENDPOINT_MESSAGE_RECEIVED,
KICKED_OUT,
LOCK_STATE_CHANGED,
NON_PARTICIPANT_MESSAGE_RECEIVED,
@@ -61,7 +61,8 @@ import {
SET_PENDING_SUBJECT_CHANGE,
SET_ROOM,
SET_START_MUTED_POLICY,
SET_START_REACTIONS_MUTED
SET_START_REACTIONS_MUTED,
UPDATE_CONFERENCE_METADATA
} from './actionTypes';
import {
AVATAR_URL_COMMAND,
@@ -79,7 +80,7 @@ import {
sendLocalParticipant
} from './functions';
import logger from './logger';
import { IJitsiConference } from './reducer';
import { IConferenceMetadata, IJitsiConference } from './reducer';
/**
* Adds conference (event) listeners.
@@ -275,6 +276,21 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
})));
}
/**
* Action for updating the conference metadata.
*
* @param {IConferenceMetadata} metadata - The metadata object.
* @returns {{
* type: UPDATE_CONFERENCE_METADATA,
* metadata: IConferenceMetadata
* }}
*/
export function updateConferenceMetadata(metadata: IConferenceMetadata | null) {
return {
type: UPDATE_CONFERENCE_METADATA,
metadata
};
}
/**
* Create an action for when the end-to-end RTT against a specific remote participant has changed.
@@ -630,6 +646,25 @@ export function dataChannelClosed(code: number, reason: string) {
};
}
/**
* Signals that a participant sent an endpoint message on the data channel.
*
* @param {Object} participant - The participant details sending the message.
* @param {Object} data - The data carried by the endpoint message.
* @returns {{
* type: ENDPOINT_MESSAGE_RECEIVED,
* participant: Object,
* data: Object
* }}
*/
export function endpointMessageReceived(participant: Object, data: Object) {
return {
type: ENDPOINT_MESSAGE_RECEIVED,
participant,
data
};
}
/**
* Action to end a conference for all participants.
*

View File

@@ -6,6 +6,7 @@ import { MIN_ASSUMED_BANDWIDTH_BPS } from '../../../../modules/API/constants';
import {
ACTION_PINNED,
ACTION_UNPINNED,
createNotAllowedErrorEvent,
createOfferAnswerFailedEvent,
createPinnedEvent
} from '../../analytics/AnalyticsEvents';
@@ -25,7 +26,7 @@ import { overwriteConfig } from '../config/actions';
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../connection/actionTypes';
import { connect, connectionDisconnected, disconnect } from '../connection/actions';
import { validateJwt } from '../jwt/functions';
import { JitsiConferenceErrors, JitsiConnectionErrors } from '../lib-jitsi-meet';
import { JitsiConferenceErrors, JitsiConferenceEvents, JitsiConnectionErrors } from '../lib-jitsi-meet';
import { PARTICIPANT_UPDATED, PIN_PARTICIPANT } from '../participants/actionTypes';
import { PARTICIPANT_ROLE } from '../participants/constants';
import {
@@ -34,6 +35,7 @@ import {
getPinnedParticipant
} from '../participants/functions';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import StateListenerRegistry from '../redux/StateListenerRegistry';
import { TRACK_ADDED, TRACK_REMOVED } from '../tracks/actionTypes';
import { getLocalTracks } from '../tracks/functions.any';
@@ -54,7 +56,8 @@ import {
conferenceWillLeave,
createConference,
setLocalSubject,
setSubject
setSubject,
updateConferenceMetadata
} from './actions';
import { CONFERENCE_LEAVE_REASONS } from './constants';
import {
@@ -65,6 +68,7 @@ import {
restoreConferenceOptions
} from './functions';
import logger from './logger';
import { IConferenceMetadata } from './reducer';
/**
* Handler for before unload event.
@@ -124,6 +128,24 @@ MiddlewareRegistry.register(store => next => action => {
return next(action);
});
/**
* Set up state change listener to perform maintenance tasks when the conference
* is left or failed.
*/
StateListenerRegistry.register(
state => getCurrentConference(state),
(conference, { dispatch }, previousConference): void => {
if (conference && !previousConference) {
conference.on(JitsiConferenceEvents.METADATA_UPDATED, (metadata: IConferenceMetadata) => {
dispatch(updateConferenceMetadata(metadata));
});
}
if (conference !== previousConference) {
dispatch(updateConferenceMetadata(null));
}
});
/**
* Makes sure to leave a failed conference in order to release any allocated
* resources like peer connections, emit participant left events, etc.
@@ -197,6 +219,12 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
break;
}
case JitsiConferenceErrors.NOT_ALLOWED_ERROR: {
const [ msg ] = error.params;
sendAnalytics(createNotAllowedErrorEvent(msg));
break;
}
case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
sendAnalytics(createOfferAnswerFailedEvent());
break;

View File

@@ -27,7 +27,8 @@ import {
SET_PENDING_SUBJECT_CHANGE,
SET_ROOM,
SET_START_MUTED_POLICY,
SET_START_REACTIONS_MUTED
SET_START_REACTIONS_MUTED,
UPDATE_CONFERENCE_METADATA
} from './actionTypes';
import { isRoomValid } from './functions';
@@ -39,10 +40,23 @@ const DEFAULT_STATE = {
leaving: undefined,
locked: undefined,
membersOnly: undefined,
metadata: undefined,
password: undefined,
passwordRequired: undefined
};
export interface IConferenceMetadata {
recording?: {
isTranscribingEnabled: boolean;
};
whiteboard?: {
collabDetails: {
roomId: string;
roomKey: string;
};
};
}
export interface IJitsiConference {
addCommandListener: Function;
addLobbyMessageListener: Function;
@@ -141,6 +155,7 @@ export interface IConferenceState {
localSubject?: string;
locked?: string;
membersOnly?: IJitsiConference;
metadata?: IConferenceMetadata;
obfuscatedRoom?: string;
obfuscatedRoomSource?: string;
p2p?: Object;
@@ -247,6 +262,12 @@ ReducerRegistry.register<IConferenceState>('features/base/conference',
startAudioMutedPolicy: action.startAudioMutedPolicy,
startVideoMutedPolicy: action.startVideoMutedPolicy
};
case UPDATE_CONFERENCE_METADATA:
return {
...state,
metadata: action.metadata
};
}
return state;

View File

@@ -131,19 +131,30 @@ export interface IMobileDynamicLink {
export interface IDeeplinkingPlatformConfig {
appName: string;
appScheme: string;
}
export interface IDeeplinkingMobileConfig extends IDeeplinkingPlatformConfig {
appPackage?: string;
appScheme: string;
downloadLink: string;
dynamicLink?: IMobileDynamicLink;
fDroidUrl?: string;
}
export interface IDesktopDownloadConfig {
linux?: string;
macos?: string;
windows?: string;
}
export interface IDeeplinkingDesktopConfig extends IDeeplinkingPlatformConfig {
download?: IDesktopDownloadConfig;
enabled: boolean;
}
export interface IDeeplinkingConfig {
android?: IDeeplinkingMobileConfig;
desktop?: IDeeplinkingPlatformConfig;
desktop?: IDeeplinkingDesktopConfig;
disabled?: boolean;
hideLogo?: boolean;
ios?: IDeeplinkingMobileConfig;
@@ -519,6 +530,9 @@ export interface IConfig {
sharingEnabled?: boolean;
};
recordingSharingUrl?: string;
recordings?: {
suggestRecording?: boolean;
};
remoteVideoMenu?: {
disableGrantModerator?: boolean;
disableKick?: boolean;
@@ -583,7 +597,6 @@ export interface IConfig {
transcribingEnabled?: boolean;
transcription?: {
autoTranscribeOnRecord?: boolean;
disableStartForAll?: boolean;
enabled?: boolean;
preferredLanguage?: string;
translationLanguages?: Array<string>;

View File

@@ -201,6 +201,7 @@ export default [
'remoteVideoMenu',
'roomPasswordNumberOfDigits',
'readOnlyName',
'recordings',
'replaceParticipant',
'resolution',
'salesforceUrl',

View File

@@ -5,8 +5,8 @@ import { NOTIFY_CLICK_MODE } from '../../toolbox/constants';
import {
IConfig,
IDeeplinkingConfig,
IDeeplinkingDesktopConfig,
IDeeplinkingMobileConfig,
IDeeplinkingPlatformConfig,
NotifyClickButton,
ToolbarButton
} from './configType';
@@ -94,13 +94,21 @@ export function areAudioLevelsEnabled(state: IReduxState): boolean {
* @returns {void}
*/
export function _setDeeplinkingDefaults(deeplinking: IDeeplinkingConfig) {
const {
desktop = {} as IDeeplinkingPlatformConfig,
android = {} as IDeeplinkingMobileConfig,
ios = {} as IDeeplinkingMobileConfig
} = deeplinking;
deeplinking.desktop = deeplinking.desktop || {} as IDeeplinkingDesktopConfig;
deeplinking.android = deeplinking.android || {} as IDeeplinkingMobileConfig;
deeplinking.ios = deeplinking.ios || {} as IDeeplinkingMobileConfig;
const { android, desktop, ios } = deeplinking;
desktop.appName = desktop.appName || 'Jitsi Meet';
desktop.appScheme = desktop.appScheme || 'jitsi-meet';
desktop.download = desktop.download || {};
desktop.download.windows = desktop.download.windows
|| 'https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet.exe';
desktop.download.macos = desktop.download.macos
|| 'https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet.dmg';
desktop.download.linux = desktop.download.linux
|| 'https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet-x86_64.AppImage';
ios.appName = ios.appName || 'Jitsi Meet';
ios.appScheme = ios.appScheme || 'org.jitsi.meet';

View File

@@ -14,8 +14,8 @@ import {
import {
IConfig,
IDeeplinkingConfig,
IDeeplinkingDesktopConfig,
IDeeplinkingMobileConfig,
IDeeplinkingPlatformConfig,
IMobileDynamicLink,
ToolbarButton
} from './configType';
@@ -295,7 +295,7 @@ function _translateInterfaceConfig(oldValue: IConfig) {
} else {
const disabled = Boolean(oldValue.disableDeepLinking);
const deeplinking: IDeeplinkingConfig = {
desktop: {} as IDeeplinkingPlatformConfig,
desktop: {} as IDeeplinkingDesktopConfig,
hideLogo: false,
disabled,
android: {} as IDeeplinkingMobileConfig,

View File

@@ -90,6 +90,12 @@ export const HELP_BUTTON_ENABLED = 'help.enabled';
*/
export const INVITE_ENABLED = 'invite.enabled';
/**
* Flag indicating if dial-in invite functionality should be enabled.
* Default: enabled (true).
*/
export const INVITE_DIAL_IN_ENABLED = 'invite-dial-in.enabled';
/**
* Flag indicating if recording should be enabled in iOS.
* Default: disabled (false).

View File

@@ -683,7 +683,7 @@ function _participantJoinedOrUpdated(store: IStore, next: Function, action: AnyA
// Only run this if the config is populated, otherwise we preload external resources
// even if disableThirdPartyRequests is set to true in config
if (Object.keys(getState()['features/base/config']).length) {
if (getState()['features/base/config']?.hosts) {
const { disableThirdPartyRequests } = getState()['features/base/config'];
if (!disableThirdPartyRequests && (avatarURL || email || id || name)) {

View File

@@ -5,6 +5,7 @@ import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../../app/types';
import DeviceStatus from '../../../../prejoin/components/web/preview/DeviceStatus';
import { isRoomNameEnabled } from '../../../../prejoin/functions';
import Toolbox from '../../../../toolbox/components/web/Toolbox';
import { getConferenceName } from '../../../conference/functions';
import { PREMEETING_BUTTONS, THIRD_PARTY_PREJOIN_BUTTONS } from '../../../config/constants';
@@ -219,7 +220,7 @@ const PreMeetingScreen = ({
* @returns {Object}
*/
function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
const { hiddenPremeetingButtons, hideConferenceSubject } = state['features/base/config'];
const { hiddenPremeetingButtons } = state['features/base/config'];
const toolbarButtons = getToolbarButtons(state);
const premeetingButtons = (ownProps.thirdParty
? THIRD_PARTY_PREJOIN_BUTTONS
@@ -237,7 +238,7 @@ function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
? premeetingButtons
: premeetingButtons.filter(b => isToolbarButtonEnabled(b, toolbarButtons)),
_premeetingBackground: premeetingBackground,
_roomName: (hideConferenceSubject ? undefined : getConferenceName(state)) ?? ''
_roomName: isRoomNameEnabled(state) ? getConferenceName(state) : ''
};
}

View File

@@ -10,6 +10,8 @@ if (userAgent.match(/Android/i)) {
OS = 'macos';
} else if (userAgent.match(/Windows/i)) {
OS = 'windows';
} else if (userAgent.match(/Linux/i)) {
OS = 'linux';
}
/**

View File

@@ -1,3 +1,5 @@
import { NativeModules, Platform } from 'react-native';
import { IReduxState, IStore } from '../../app/types';
import { setPictureInPictureEnabled } from '../../mobile/picture-in-picture/functions';
import { showNotification } from '../../notifications/actions';
@@ -12,6 +14,8 @@ import { VIDEO_MUTISM_AUTHORITY } from '../media/constants';
import { addLocalTrack, replaceLocalTrack } from './actions.any';
import { getLocalDesktopTrack, getTrackState, isLocalVideoTrackDesktop } from './functions.native';
const { JitsiMeetMediaProjectionModule } = NativeModules;
export * from './actions.any';
/**
@@ -31,7 +35,10 @@ export function toggleScreensharing(enabled: boolean, _ignore1?: boolean, _ignor
if (!isSharing) {
_startScreenSharing(dispatch, state);
Platform.OS === 'android' && JitsiMeetMediaProjectionModule.launch();
}
Platform.OS === 'android' && JitsiMeetMediaProjectionModule.abort();
} else {
dispatch(setScreenshareMuted(true));
dispatch(setVideoMuted(false, VIDEO_MUTISM_AUTHORITY.SCREEN_SHARE));
@@ -77,7 +84,7 @@ async function _startScreenSharing(dispatch: IStore['dispatch'], state: IReduxSt
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}
} catch (error: any) {
console.log('ERROR creating ScreeSharing stream ', error);
console.log('ERROR creating screen-sharing stream ', error);
setPictureInPictureEnabled(true);
}

View File

@@ -1,3 +1,5 @@
import base64js from 'base64-js';
import { timeoutPromise } from './timeoutPromise';
/**
@@ -43,3 +45,37 @@ export function doGetJSON(url: string, retry?: boolean, options?: Object) {
return fetchPromise;
}
/**
* Encodes strings to Base64URL.
*
* @param {any} data - The byte array to encode.
* @returns {string}
*/
export const encodeToBase64URL = (data: string): string => base64js
.fromByteArray(new window.TextEncoder().encode(data))
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
/**
* Decodes strings from Base64URL.
*
* @param {string} data - The byte array to decode.
* @returns {string}
*/
export const decodeFromBase64URL = (data: string): string => {
let s = data;
// Convert from Base64URL to Base64.
if (s.length % 4 === 2) {
s += '==';
} else if (s.length % 4 === 3) {
s += '=';
}
s = s.replace(/-/g, '+').replace(/_/g, '/');
// Convert Base64 to a byte array.
return new window.TextDecoder().decode(base64js.toByteArray(s));
};

View File

@@ -6,6 +6,7 @@ import { getFeatureFlag } from '../../../base/flags/functions';
import { translate } from '../../../base/i18n/functions';
import { IconChatUnread, IconMessage } from '../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { arePollsDisabled } from '../../../conference/functions.any';
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { getUnreadPollCount } from '../../../polls/functions';
@@ -65,11 +66,10 @@ class ChatButton extends AbstractButton<IProps> {
*/
function _mapStateToProps(state: IReduxState, ownProps: any) {
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { disablePolls } = state['features/base/config'];
const { visible = enabled } = ownProps;
return {
_isPollsDisabled: disablePolls,
_isPollsDisabled: arePollsDisabled(state),
// The toggled icon should also be available for new polls
_unreadMessageCount: getUnreadCount(state) || getUnreadPollCount(state),

View File

@@ -8,6 +8,7 @@ import { IconMessage, IconReply } from '../../../base/icons/svg';
import { getParticipantById } from '../../../base/participants/functions';
import { IParticipant } from '../../../base/participants/types';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { arePollsDisabled } from '../../../conference/functions.any';
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { handleLobbyChatInitialized, openChat } from '../../actions.native';
@@ -96,11 +97,10 @@ class PrivateMessageButton extends AbstractButton<IProps, any> {
*/
export function _mapStateToProps(state: IReduxState, ownProps: any) {
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { disablePolls } = state['features/base/config'];
const { visible = enabled, isLobbyMessage, participantID } = ownProps;
return {
_isPollsDisabled: disablePolls,
_isPollsDisabled: arePollsDisabled(state),
_participant: getParticipantById(state, participantID),
_isLobbyMessage: isLobbyMessage,
visible

View File

@@ -7,6 +7,7 @@ import { translate } from '../../../base/i18n/functions';
import { getLocalParticipant } from '../../../base/participants/functions';
import { withPixelLineHeight } from '../../../base/styles/functions.web';
import Tabs from '../../../base/ui/components/web/Tabs';
import { arePollsDisabled } from '../../../conference/functions.any';
import PollsPane from '../../../polls/components/web/PollsPane';
import { sendMessage, setIsPollsTabFocused, toggleChat } from '../../actions.web';
import { CHAT_SIZE, CHAT_TABS, SMALL_WIDTH_THRESHOLD } from '../../constants';
@@ -316,12 +317,11 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { isOpen, isPollsTabFocused, messages, nbUnreadMessages } = state['features/chat'];
const { nbUnreadPolls } = state['features/polls'];
const _localParticipant = getLocalParticipant(state);
const { disablePolls } = state['features/base/config'];
return {
_isModal: window.innerWidth <= SMALL_WIDTH_THRESHOLD,
_isOpen: isOpen,
_isPollsEnabled: !disablePolls,
_isPollsEnabled: !arePollsDisabled(state),
_isPollsTabFocused: isPollsTabFocused,
_messages: messages,
_nbUnreadMessages: nbUnreadMessages,

View File

@@ -2,7 +2,7 @@ import { AnyAction } from 'redux';
import { IReduxState, IStore } from '../app/types';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
import { CONFERENCE_JOINED, ENDPOINT_MESSAGE_RECEIVED } from '../base/conference/actionTypes';
import { getCurrentConference } from '../base/conference/functions';
import { IJitsiConference } from '../base/conference/reducer';
import { openDialog } from '../base/dialog/actions';
@@ -29,7 +29,6 @@ import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes';
import { pushReactions } from '../reactions/actions.any';
import { ENDPOINT_REACTION_NAME } from '../reactions/constants';
import { getReactionMessageFromBuffer, isReactionsEnabled } from '../reactions/functions.any';
import { endpointMessageReceived } from '../subtitles/actions.any';
import { showToolbox } from '../toolbox/actions';
@@ -93,14 +92,6 @@ MiddlewareRegistry.register(store => next => action => {
_addChatMsgListener(action.conference, store);
break;
case OPEN_CHAT:
unreadCount = 0;
if (typeof APP !== 'undefined') {
APP.API.notifyChatUpdated(unreadCount, true);
}
break;
case CLOSE_CHAT: {
const isPollTabOpen = getState()['features/chat'].isPollsTabFocused;
@@ -116,6 +107,38 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case ENDPOINT_MESSAGE_RECEIVED: {
const state = store.getState();
if (!isReactionsEnabled(state)) {
return;
}
const { participant, data } = action;
if (data?.name === ENDPOINT_REACTION_NAME) {
store.dispatch(pushReactions(data.reactions));
_handleReceivedMessage(store, {
id: participant.getId(),
message: getReactionMessageFromBuffer(data.reactions),
privateMessage: false,
lobbyChat: false,
timestamp: data.timestamp
}, false, true);
}
break;
}
case OPEN_CHAT:
unreadCount = 0;
if (typeof APP !== 'undefined') {
APP.API.notifyChatUpdated(unreadCount, true);
}
break;
case SET_IS_POLL_TAB_FOCUSED: {
dispatch(resetNbUnreadPollsMessages());
break;
@@ -253,35 +276,6 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
}
);
conference.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(...args: any) => {
const state = store.getState();
if (!isReactionsEnabled(state)) {
return;
}
// @ts-ignore
store.dispatch(endpointMessageReceived(...args));
if (args && args.length >= 2) {
const [ { _id }, eventData ] = args;
if (eventData.name === ENDPOINT_REACTION_NAME) {
store.dispatch(pushReactions(eventData.reactions));
_handleReceivedMessage(store, {
id: _id,
message: getReactionMessageFromBuffer(eventData.reactions),
privateMessage: false,
lobbyChat: false,
timestamp: eventData.timestamp
}, false, true);
}
}
});
conference.on(
JitsiConferenceEvents.CONFERENCE_ERROR, (errorType: string, error: Error) => {
errorType === JitsiConferenceErrors.CHAT_ERROR && _handleChatError(store, error);

View File

@@ -17,7 +17,6 @@ import { IReduxState, IStore } from '../../../app/types';
import { CONFERENCE_BLURRED, CONFERENCE_FOCUSED } from '../../../base/conference/actionTypes';
import { FULLSCREEN_ENABLED, PIP_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import { getParticipantCount } from '../../../base/participants/functions';
import Container from '../../../base/react/components/native/Container';
import LoadingIndicator from '../../../base/react/components/native/LoadingIndicator';
import TintedView from '../../../base/react/components/native/TintedView';
@@ -101,11 +100,6 @@ interface IProps extends AbstractProps {
*/
_fullscreenEnabled: boolean;
/**
* The indicator which determines if the conference type is one to one.
*/
_isOneToOneConference: boolean;
/**
* The indicator which determines if the participants pane is open.
*/
@@ -370,7 +364,6 @@ class Conference extends AbstractConference<IProps, State> {
_aspectRatio,
_connecting,
_filmstripVisible,
_isOneToOneConference,
_largeVideoParticipantId,
_reducedUI,
_shouldDisplayTileView,
@@ -426,13 +419,11 @@ class Conference extends AbstractConference<IProps, State> {
<Captions onPress = { this._onClick } />
{
_shouldDisplayTileView || (
!_isOneToOneConference
&& <Container style = { styles.displayNameContainer }>
<DisplayNameLabel
participantId = { _largeVideoParticipantId } />
</Container>
)
_shouldDisplayTileView
|| <Container style = { styles.displayNameContainer }>
<DisplayNameLabel
participantId = { _largeVideoParticipantId } />
</Container>
}
{ !_shouldDisplayTileView && <LonelyMeetingExperience /> }
@@ -573,7 +564,6 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { backgroundColor } = state['features/dynamic-branding'];
const { startCarMode } = state['features/base/settings'];
const { enabled: audioOnlyEnabled } = state['features/base/audio-only'];
const participantCount = getParticipantCount(state);
const brandingStyles = backgroundColor ? {
backgroundColor
} : undefined;
@@ -587,7 +577,6 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
_connecting: isConnecting(state),
_filmstripVisible: isFilmstripVisible(state),
_fullscreenEnabled: getFeatureFlag(state, FULLSCREEN_ENABLED, true),
_isOneToOneConference: Boolean(participantCount === 2),
_isParticipantsPaneOpen: isOpen,
_largeVideoParticipantId: state['features/large-video'].participantId,
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),

View File

@@ -1,11 +1,10 @@
import React, { Component } from 'react';
import { TouchableOpacity, View, ViewStyle } from 'react-native';
import TranscribingLabel from '../../../transcribing/components/TranscribingLabel.native';
import VideoQualityLabel from '../../../video-quality/components/VideoQualityLabel.native';
import InsecureRoomNameLabel from './InsecureRoomNameLabel';
import { LABEL_ID_INSECURE_ROOM_NAME, LABEL_ID_QUALITY, LABEL_ID_TRANSCRIBING, LabelHitSlop } from './constants';
import { LABEL_ID_INSECURE_ROOM_NAME, LABEL_ID_QUALITY, LabelHitSlop } from './constants';
import styles from './styles';
interface IProps {
@@ -33,13 +32,6 @@ class Labels extends Component<IProps> {
<View
pointerEvents = 'box-none'
style = { styles.indicatorContainer as ViewStyle }>
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = {
this.props.createOnPress(LABEL_ID_TRANSCRIBING)
} >
<TranscribingLabel />
</TouchableOpacity>
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = {

View File

@@ -4,11 +4,12 @@ import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { getConferenceName, getConferenceTimestamp } from '../../../base/conference/functions';
import { CONFERENCE_TIMER_ENABLED, MEETING_NAME_ENABLED } from '../../../base/flags/constants';
import { CONFERENCE_TIMER_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import AudioDeviceToggleButton from '../../../mobile/audio-mode/components/AudioDeviceToggleButton';
import PictureInPictureButton from '../../../mobile/picture-in-picture/components/PictureInPictureButton';
import ParticipantsPaneButton from '../../../participants-pane/components/native/ParticipantsPaneButton';
import { isRoomNameEnabled } from '../../../prejoin/functions';
import ToggleCameraButton from '../../../toolbox/components/native/ToggleCameraButton';
import { isToolboxVisible } from '../../../toolbox/functions.native';
import ConferenceTimer from '../ConferenceTimer';
@@ -16,6 +17,7 @@ import ConferenceTimer from '../ConferenceTimer';
import Labels from './Labels';
import styles from './styles';
interface IProps {
/**
@@ -35,9 +37,9 @@ interface IProps {
_meetingName: string;
/**
* Whether displaying the current meeting name is enabled or not.
* Whether displaying the current room name is enabled or not.
*/
_meetingNameEnabled: boolean;
_roomNameEnabled: boolean;
/**
* True if the navigation bar should be visible.
@@ -75,7 +77,7 @@ const TitleBar = (props: IProps) => {
</View>
}
{
props._meetingNameEnabled
props._roomNameEnabled
&& <View style = { styles.roomNameView as ViewStyle }>
<Text
numberOfLines = { 1 }
@@ -107,15 +109,14 @@ const TitleBar = (props: IProps) => {
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState) {
const { hideConferenceTimer, hideConferenceSubject } = state['features/base/config'];
const { hideConferenceTimer } = state['features/base/config'];
const startTimestamp = getConferenceTimestamp(state);
return {
_conferenceTimerEnabled:
Boolean(getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true) && !hideConferenceTimer && startTimestamp),
_meetingName: getConferenceName(state),
_meetingNameEnabled:
getFeatureFlag(state, MEETING_NAME_ENABLED, true) && !hideConferenceSubject,
_roomNameEnabled: isRoomNameEnabled(state),
_visible: isToolboxVisible(state)
};
}

View File

@@ -2,7 +2,6 @@ import React from 'react';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import RecordingExpandedLabel from '../../../recording/components/native/RecordingExpandedLabel';
import TranscribingExpandedLabel from '../../../transcribing/components/TranscribingExpandedLabel.native';
import VideoQualityExpandedLabel from '../../../video-quality/components/VideoQualityExpandedLabel.native';
import InsecureRoomNameExpandedLabel from './InsecureRoomNameExpandedLabel';
@@ -23,7 +22,6 @@ export const EXPANDED_LABEL_TIMEOUT = 5000;
export const LABEL_ID_QUALITY = 'quality';
export const LABEL_ID_RECORDING = 'recording';
export const LABEL_ID_STREAMING = 'streaming';
export const LABEL_ID_TRANSCRIBING = 'transcribing';
export const LABEL_ID_INSECURE_ROOM_NAME = 'insecure-room-name';
export const LABEL_ID_RAISED_HANDS_COUNT = 'raised-hands-count';
export const LABEL_ID_VISITORS_COUNT = 'visitors-count';
@@ -58,9 +56,6 @@ export const EXPANDED_LABELS: {
},
alwaysOn: true
},
[LABEL_ID_TRANSCRIBING]: {
component: TranscribingExpandedLabel
},
[LABEL_ID_INSECURE_ROOM_NAME]: {
component: InsecureRoomNameExpandedLabel
},

View File

@@ -23,6 +23,7 @@ import { getOverlayToRender } from '../../../overlay/functions.web';
import ParticipantsPane from '../../../participants-pane/components/web/ParticipantsPane';
import Prejoin from '../../../prejoin/components/web/Prejoin';
import { isPrejoinPageVisible } from '../../../prejoin/functions';
import ReactionAnimations from '../../../reactions/components/web/ReactionsAnimations';
import { toggleToolboxVisible } from '../../../toolbox/actions.any';
import { fullScreenChanged, showToolbox } from '../../../toolbox/actions.web';
import JitsiPortal from '../../../toolbox/components/web/JitsiPortal';
@@ -260,6 +261,7 @@ class Conference extends AbstractConference<IProps, any> {
{ _showLobby && <LobbyScreen />}
</div>
<ParticipantsPane />
<ReactionAnimations />
</div>
);
}

View File

@@ -9,7 +9,6 @@ import HighlightButton from '../../../recording/components/Recording/web/Highlig
import RecordingLabel from '../../../recording/components/web/RecordingLabel';
import { showToolbox } from '../../../toolbox/actions.web';
import { isToolboxVisible } from '../../../toolbox/functions.web';
import TranscribingLabel from '../../../transcribing/components/TranscribingLabel.web';
import VideoQualityLabel from '../../../video-quality/components/VideoQualityLabel.web';
import VisitorsCountLabel from '../../../visitors/components/web/VisitorsCountLabel';
import ConferenceTimer from '../ConferenceTimer';
@@ -83,10 +82,6 @@ const COMPONENTS: Array<{
Component: RaisedHandsCountLabel,
id: 'raised-hands-count'
},
{
Component: TranscribingLabel,
id: 'transcribing'
},
{
Component: VideoQualityLabel,
id: 'video-quality'

View File

@@ -1,5 +1,6 @@
import { IStateful } from '../base/app/types';
import { toState } from '../base/redux/functions';
import { iAmVisitor } from '../visitors/functions';
/**
@@ -15,3 +16,19 @@ export function shouldDisplayNotifications(stateful: IStateful) {
return !calleeInfoVisible;
}
/**
*
* Returns true if polls feature is disabled.
*
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state
* features/base/config.
* @returns {boolean}.
*/
export function arePollsDisabled(stateful: IStateful) {
const state = toState(stateful);
return state['features/base/config']?.disablePolls || iAmVisitor(state);
}

View File

@@ -1,21 +1 @@
import { IStateful } from '../base/app/types';
import { toState } from '../base/redux/functions';
export * from './functions.any';
/**
*
* Returns true if polls feature is disabled.
*
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state
* features/base/config.
* @returns {boolean}.
*/
export function getDisablePolls(stateful: IStateful) {
const state = toState(stateful)['features/base/config'];
return state.disablePolls;
}

View File

@@ -35,6 +35,7 @@ import {
NOTIFICATION_ICON,
NOTIFICATION_TIMEOUT_TYPE
} from '../notifications/constants';
import { showStartRecordingNotification } from '../recording/actions';
import { showSalesforceNotification } from '../salesforce/actions';
import { setToolboxEnabled } from '../toolbox/actions.any';
@@ -149,6 +150,8 @@ function _conferenceJoined({ dispatch, getState }: IStore) {
}
dispatch(showSalesforceNotification());
dispatch(showStartRecordingNotification());
_checkIframe(getState(), dispatch);
}

View File

@@ -1,10 +1,7 @@
import i18next from 'i18next';
import { CONFERENCE_JOINED, KICKED_OUT } from '../base/conference/actionTypes';
import { IJitsiConference } from '../base/conference/reducer';
import { ENDPOINT_MESSAGE_RECEIVED, KICKED_OUT } from '../base/conference/actionTypes';
import { hangup } from '../base/connection/actions.web';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { getParticipantDisplayName } from '../base/participants/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { openAllowToggleCameraDialog, setCameraFacingMode } from '../base/tracks/actions.web';
@@ -14,8 +11,15 @@ import './middleware.any';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONFERENCE_JOINED: {
_addSetCameraFacingModeListener(action.conference);
case ENDPOINT_MESSAGE_RECEIVED: {
const { participant, data } = action;
if (data?.name === CAMERA_FACING_MODE_MESSAGE) {
APP.store.dispatch(openAllowToggleCameraDialog(
/* onAllow */ () => APP.store.dispatch(setCameraFacingMode(data.facingMode)),
/* initiatorId */ participant.getId()
));
}
break;
}
@@ -34,28 +38,3 @@ MiddlewareRegistry.register(store => next => action => {
return next(action);
});
/**
* Registers listener for {@link JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED} that
* will perform various chat related activities.
*
* @param {IJitsiConference} conference - The conference.
* @returns {void}
*/
function _addSetCameraFacingModeListener(conference: IJitsiConference) {
conference.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(...args: any) => {
if (args && args.length >= 2) {
const [ sender, eventData ] = args;
if (eventData.name === CAMERA_FACING_MODE_MESSAGE) {
APP.store.dispatch(openAllowToggleCameraDialog(
/* onAllow */ () => APP.store.dispatch(setCameraFacingMode(eventData.facingMode)),
/* initiatorId */ sender._id
));
}
}
}
);
}

View File

@@ -11,6 +11,7 @@ import { IDeeplinkingConfig } from '../../base/config/configType';
import { getLegalUrls } from '../../base/config/functions.any';
import { isSupportedBrowser } from '../../base/environment/environment';
import { translate, translateToHTML } from '../../base/i18n/functions';
import Platform from '../../base/react/Platform.web';
import { withPixelLineHeight } from '../../base/styles/functions.web';
import Button from '../../base/ui/components/web/Button';
import { BUTTON_TYPES } from '../../base/ui/constants.any';
@@ -85,6 +86,14 @@ const DeepLinkingDesktopPage: React.FC<WithTranslation> = ({ t }) => {
const deeplinkingCfg = useSelector((state: IReduxState) =>
state['features/base/config']?.deeplinking || {} as IDeeplinkingConfig);
const generateDownloadURL = useCallback(() => {
const downloadCfg = deeplinkingCfg.desktop?.download;
if (downloadCfg) {
return downloadCfg[Platform.OS as keyof typeof downloadCfg];
}
}, [ deeplinkingCfg ]);
const legalUrls = useSelector(getLegalUrls);
const { hideLogo, desktop } = deeplinkingCfg;
@@ -134,6 +143,16 @@ const DeepLinkingDesktopPage: React.FC<WithTranslation> = ({ t }) => {
: t(`${_TNS}.descriptionWithoutWeb`, { app: desktop?.appName })
}
</div>
<div className = { styles.descriptionLabel }>
{
t(`${_TNS}.noDesktopApp`)
} &nbsp;
<a href = { generateDownloadURL() }>
{
t(`${_TNS}.downloadApp`)
}
</a>
</div>
<div className = { styles.buttonsContainer }>
<Button
label = { t(`${_TNS}.tryAgainButton`) }

View File

@@ -211,7 +211,7 @@ const DeepLinkingMobilePage: React.FC<WithTranslation> = ({ t }) => {
</a>
{isSupportedMobileBrowser() ? (
<div className = { styles.supportedBrowserContent }>
<div className = { styles.labelOr }>OR</div>
<div className = { styles.labelOr }>{ t(`${_TNS}.or`) }</div>
<a
className = { styles.linkWrapper }
onClick = { onLaunchWeb }

View File

@@ -1,5 +1,6 @@
import { IReduxState } from '../app/types';
import { isMobileBrowser } from '../base/environment/utils';
import { browser } from '../base/lib-jitsi-meet';
import Platform from '../base/react/Platform';
import { URI_PROTOCOL_PATTERN } from '../base/util/uri';
import { isVpaasMeeting } from '../jaas/functions';
@@ -63,6 +64,7 @@ export function getDeepLinkingPage(state: IReduxState) {
if (launchInWeb
|| !room
|| state['features/base/config'].deeplinking?.disabled
|| browser.isElectron()
|| (isVpaasMeeting(state) && (!appScheme || appScheme === 'com.8x8.meet'))) {
return Promise.resolve();
}

View File

@@ -1,3 +1,6 @@
import { IReduxState } from '../app/types';
import { URI_PROTOCOL_PATTERN } from '../base/util/uri';
/**
* Opens the desktop app.
*
@@ -6,5 +9,17 @@
* with false otherwise.
*/
export function _openDesktopApp(_state: Object) {
const state = _state as IReduxState;
const deeplinkingDesktop = state['features/base/config'].deeplinking?.desktop;
if (deeplinkingDesktop?.enabled) {
const { appScheme } = deeplinkingDesktop;
const regex = new RegExp(URI_PROTOCOL_PATTERN, 'gi');
window.location.href = window.location.href.replace(regex, `${appScheme}:`);
return Promise.resolve(true);
}
return Promise.resolve(false);
}

View File

@@ -56,6 +56,7 @@ class SharedDocument extends PureComponent<IProps> {
style = { styles.sharedDocContainer }>
<WebView
hideKeyboardAccessoryView = { true }
incognito = { true }
renderLoading = { this._renderLoading }
source = {{ uri: _documentUrl ?? '' }}
startInLoadingState = { true }

View File

@@ -1,6 +1,7 @@
import 'image-capture';
import './createImageBitmap';
import { IStore } from '../app/types';
import { isMobileBrowser } from '../base/environment/utils';
import { getLocalVideoTrack } from '../base/tracks/functions';
import { getBaseUrl } from '../base/util/helpers';
@@ -88,7 +89,7 @@ class FaceLandmarksDetector {
return;
}
if (navigator.product === 'ReactNative') {
if (isMobileBrowser() || navigator.product === 'ReactNative') {
logger.warn('Unsupported environment for face detection');
return;

View File

@@ -3,19 +3,18 @@ import { AnyAction } from 'redux';
import { IStore } from '../app/types';
import {
CONFERENCE_JOINED,
CONFERENCE_WILL_LEAVE
CONFERENCE_WILL_LEAVE,
ENDPOINT_MESSAGE_RECEIVED
} from '../base/conference/actionTypes';
import { getCurrentConference } from '../base/conference/functions';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { getLocalParticipant, getParticipantCount } from '../base/participants/functions';
import { IParticipant } from '../base/participants/types';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from '../base/tracks/actionTypes';
import FaceLandmarksDetector from './FaceLandmarksDetector';
import { ADD_FACE_LANDMARKS, NEW_FACE_COORDINATES, UPDATE_FACE_COORDINATES } from './actionTypes';
import { FACE_BOX_EVENT_TYPE } from './constants';
import { sendFaceBoxToParticipants, sendFaceExpressionToParticipants, sendFaceExpressionToServer } from './functions';
import { sendFaceBoxToParticipants, sendFaceExpressionToParticipants } from './functions';
MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyAction) => {
@@ -28,27 +27,22 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
FaceLandmarksDetector.init(store);
}
// allow using remote face centering data when local face centering is not enabled
action.conference.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(participant: IParticipant | undefined, eventData: any) => {
if (!participant || !eventData || !participant.getId) {
return;
}
return next(action);
} else if (action.type === ENDPOINT_MESSAGE_RECEIVED) {
// Allow using remote face centering data when local face centering is not enabled.
const { participant, data } = action;
if (eventData.type === FACE_BOX_EVENT_TYPE) {
dispatch({
type: UPDATE_FACE_COORDINATES,
faceBox: eventData.faceBox,
id: participant.getId()
});
}
if (data?.type === FACE_BOX_EVENT_TYPE) {
dispatch({
type: UPDATE_FACE_COORDINATES,
faceBox: data.faceBox,
id: participant.getId()
});
}
return next(action);
}
if (!isEnabled) {
return next(action);
}
@@ -57,7 +51,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
case CONFERENCE_WILL_LEAVE : {
FaceLandmarksDetector.stopDetection(store);
return next(action);
break;
}
case TRACK_ADDED: {
const { jitsiTrack: { isLocal, videoType }, muted } = action.track;
@@ -67,18 +61,18 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
FaceLandmarksDetector.startDetection(store, action.track);
}
return next(action);
break;
}
case TRACK_UPDATED: {
const { jitsiTrack: { isLocal, videoType } } = action.track;
if (videoType !== 'camera' || !isLocal()) {
return next(action);
break;
}
const { muted } = action.track;
if (muted !== undefined) {
if (typeof muted !== 'undefined') {
// addresses video mute state changes
if (muted) {
FaceLandmarksDetector.stopDetection(store);
@@ -87,7 +81,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
}
}
return next(action);
break;
}
case TRACK_REMOVED: {
const { jitsiTrack: { isLocal, videoType } } = action.track;
@@ -96,7 +90,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
FaceLandmarksDetector.stopDetection(store);
}
return next(action);
break;
}
case ADD_FACE_LANDMARKS: {
const state = getState();
@@ -107,9 +101,10 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
sendFaceExpressionToParticipants(conference, faceLandmarks);
}
sendFaceExpressionToServer(conference, faceLandmarks);
// Disabling for now as there is no value of having the data in speakerstats at the server
// sendFaceExpressionToServer(conference, faceLandmarks);
return next(action);
break;
}
case NEW_FACE_COORDINATES: {
const state = getState();
@@ -126,6 +121,8 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
faceBox,
id: localParticipant?.id
});
break;
}
}

View File

@@ -76,6 +76,7 @@ class DialInSummary extends PureComponent<IProps> {
<JitsiScreen
style = { styles.backDrop }>
<WebView
incognito = { true }
onError = { this._onError }
onShouldStartLoadWithRequest = { this._onNavigate }
renderLoading = { this._renderLoading }

View File

@@ -66,7 +66,7 @@ const ConferenceID: React.FC<IProps> = ({ conferenceID, t }) => {
return (
<div className = { styles.container }>
<div className = { styles.descriptionLabel }>
To join the meeting via phone, dial one of these numbers and then enter the pin
{ t('info.dialANumber') }
</div>
<div className = { styles.separator } />
<div className = { styles.pinLabel }>

View File

@@ -571,12 +571,13 @@ export function searchDirectory( // eslint-disable-line max-params
* @param {IReduxState} state - The current state.
* @param {string} inviteUrl - The conference/location URL.
* @param {boolean} useHtml - Whether to return html text.
* @param {boolean} skipDialIn - Whether to skip dial-in options or not.
* @returns {Promise<string>} A {@code Promise} resolving with a
* descriptive text that can be used to invite participants to a meeting.
*/
export function getShareInfoText(state: IReduxState, inviteUrl: string, useHtml?: boolean): Promise<string> {
export function getShareInfoText(
state: IReduxState, inviteUrl: string, useHtml?: boolean, skipDialIn?: boolean): Promise<string> {
let roomUrl = _decodeRoomURI(inviteUrl);
const includeDialInfo = state['features/base/config'] !== undefined;
if (useHtml) {
roomUrl = `<a href="${roomUrl}">${roomUrl}</a>`;
@@ -584,85 +585,72 @@ export function getShareInfoText(state: IReduxState, inviteUrl: string, useHtml?
let infoText = i18next.t('share.mainText', { roomUrl });
if (includeDialInfo) {
const { room } = parseURIString(inviteUrl);
let numbersPromise;
let hasPaymentError = false;
const { room } = parseURIString(inviteUrl);
const { dialInConfCodeUrl, dialInNumbersUrl, hosts } = state['features/base/config'];
const { locationURL = {} } = state['features/base/connection'];
const mucURL = hosts?.muc;
if (state['features/invite'].numbers
&& state['features/invite'].conferenceID) {
numbersPromise = Promise.resolve(state['features/invite']);
} else {
// we are requesting numbers and conferenceId directly
// not using updateDialInNumbers, because custom room
// is specified and we do not want to store the data
// in the state
const { dialInConfCodeUrl, dialInNumbersUrl, hosts }
= state['features/base/config'];
const { locationURL = {} } = state['features/base/connection'];
const mucURL = hosts?.muc;
if (!dialInConfCodeUrl || !dialInNumbersUrl || !mucURL) {
// URLs for fetching dial in numbers not defined
return Promise.resolve(infoText);
}
numbersPromise = Promise.all([
getDialInNumbers(dialInNumbersUrl, room, mucURL), // @ts-ignore
getDialInConferenceID(dialInConfCodeUrl, room, mucURL, locationURL)
]).then(([ numbers, {
conference, id, message } ]) => {
if (!conference || !id) {
return Promise.reject(message);
}
return {
numbers,
conferenceID: id
};
});
}
return numbersPromise.then(
({ conferenceID, numbers }) => {
const phoneNumber = _getDefaultPhoneNumber(numbers) || '';
return `${
i18next.t('info.dialInNumber')} ${
phoneNumber} ${
i18next.t('info.dialInConferenceID')} ${
conferenceID}#\n\n`;
})
.catch(error => {
logger.error('Error fetching numbers or conferenceID', error);
hasPaymentError = error?.status === StatusCode.PaymentRequired;
})
.then(defaultDialInNumber => {
if (hasPaymentError) {
infoText += `${
i18next.t('info.dialInNumber')} ${i18next.t('info.reachedLimit')} ${
i18next.t('info.upgradeOptions')} ${UPGRADE_OPTIONS_TEXT}`;
return infoText;
}
let dialInfoPageUrl = getDialInfoPageURL(state, room);
if (useHtml) {
dialInfoPageUrl
= `<a href="${dialInfoPageUrl}">${dialInfoPageUrl}</a>`;
}
infoText += i18next.t('share.dialInfoText', {
defaultDialInNumber,
dialInfoPageUrl });
return infoText;
});
if (skipDialIn || !dialInConfCodeUrl || !dialInNumbersUrl || !mucURL) {
// URLs for fetching dial in numbers not defined.
return Promise.resolve(infoText);
}
return Promise.resolve(infoText);
let hasPaymentError = false;
// We are requesting numbers and conferenceId directly
// not using updateDialInNumbers, because custom room
// is specified and we do not want to store the data
// in the state.
const numbersPromise = Promise.all([
getDialInNumbers(dialInNumbersUrl, room, mucURL), // @ts-ignore
getDialInConferenceID(dialInConfCodeUrl, room, mucURL, locationURL)
]).then(([ numbers, {
conference, id, message } ]) => {
if (!conference || !id) {
return Promise.reject(message);
}
return {
numbers,
conferenceID: id
};
});
return numbersPromise.then(({ conferenceID, numbers }) => {
const phoneNumber = _getDefaultPhoneNumber(numbers) || '';
return `${
i18next.t('info.dialInNumber')} ${
phoneNumber} ${
i18next.t('info.dialInConferenceID')} ${
conferenceID}#\n\n`;
})
.catch(error => {
logger.error('Error fetching numbers or conferenceID', error);
hasPaymentError = error?.status === StatusCode.PaymentRequired;
})
.then(defaultDialInNumber => {
if (hasPaymentError) {
infoText += `${
i18next.t('info.dialInNumber')} ${i18next.t('info.reachedLimit')} ${
i18next.t('info.upgradeOptions')} ${UPGRADE_OPTIONS_TEXT}`;
return infoText;
}
let dialInfoPageUrl = getDialInfoPageURL(state, room);
if (useHtml) {
dialInfoPageUrl = `<a href="${dialInfoPageUrl}">${dialInfoPageUrl}</a>`;
}
infoText += i18next.t('share.dialInfoText', {
defaultDialInNumber,
dialInfoPageUrl });
return infoText;
});
}
/**

View File

@@ -30,7 +30,7 @@ export const registerShortcut = (shortcut: IKeyboardShortcut): AnyAction => {
*/
export const unregisterShortcut = (character: string, altKey = false): AnyAction => {
return {
altKey,
alt: altKey,
type: UNREGISTER_KEYBOARD_SHORTCUT,
character
};

View File

@@ -21,7 +21,8 @@ import {
SET_PASSWORD_JOIN_FAILED
} from './actionTypes';
import { LOBBY_CHAT_INITIALIZED, MODERATOR_IN_CHAT_WITH_LEFT } from './constants';
import { getKnockingParticipants, getLobbyConfig, getLobbyEnabled } from './functions';
import { getKnockingParticipants, getLobbyConfig, getLobbyEnabled, isEnablingLobbyAllowed } from './functions';
import logger from './logger';
import { IKnockingParticipant } from './types';
/**
@@ -243,7 +244,11 @@ export function toggleLobbyMode(enabled: boolean) {
const conference = getCurrentConference(getState);
if (enabled) {
conference?.enableLobby();
if (isEnablingLobbyAllowed(getState())) {
conference?.enableLobby();
} else {
logger.info('Ignoring enable lobby request because there are visitors in the call already.');
}
} else {
conference?.disableLobby();
}

View File

@@ -1,5 +1,6 @@
import { IReduxState } from '../app/types';
import { getCurrentConference } from '../base/conference/functions';
import { getVisitorsCount } from '../visitors/functions';
import { IKnockingParticipant } from './types';
@@ -92,3 +93,13 @@ export function showLobbyChatButton(
return false;
};
}
/**
* Returns true if enabling lobby is allowed and false otherwise.
*
* @param {IReduxState} state - State object.
* @returns {boolean}
*/
export function isEnablingLobbyAllowed(state: IReduxState) {
return getVisitorsCount(state) <= 0;
}

View File

@@ -6,7 +6,8 @@ import { IStore } from '../app/types';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
import {
CONFERENCE_FAILED,
CONFERENCE_JOINED
CONFERENCE_JOINED,
ENDPOINT_MESSAGE_RECEIVED
} from '../base/conference/actionTypes';
import { conferenceWillJoin } from '../base/conference/actions';
import {
@@ -31,6 +32,7 @@ import {
handleLobbyChatInitialized,
removeLobbyChatParticipant
} from '../chat/actions.any';
import { arePollsDisabled } from '../conference/functions.any';
import { hideNotification, showNotification } from '../notifications/actions';
import {
LOBBY_NOTIFICATION_ID,
@@ -78,6 +80,13 @@ MiddlewareRegistry.register(store => next => action => {
return _conferenceFailed(store, next, action);
case CONFERENCE_JOINED:
return _conferenceJoined(store, next, action);
case ENDPOINT_MESSAGE_RECEIVED: {
const { participant, data } = action;
_maybeSendLobbyNotification(participant, data, store);
break;
}
case KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED: {
// We need the full update result to be in the store already
const result = next(action);
@@ -165,13 +174,6 @@ StateListenerRegistry.register(
dispatch(updateLobbyParticipantOnLeave(id));
});
});
conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, (origin: any, sender: any) =>
_maybeSendLobbyNotification(origin, sender, {
dispatch,
getState
})
);
}
}
);
@@ -201,7 +203,6 @@ function _handleLobbyNotification(store: IStore) {
if (knockingParticipants.length === 1) {
const firstParticipant = knockingParticipants[0];
const { disablePolls } = getState()['features/base/config'];
const showChat = showLobbyChatButton(firstParticipant)(getState());
descriptionKey = 'notify.participantWantsToJoin';
@@ -225,7 +226,7 @@ function _handleLobbyNotification(store: IStore) {
customActionType.splice(1, 0, BUTTON_TYPES.SECONDARY);
customActionHandler.splice(1, 0, () => batch(() => {
dispatch(handleLobbyChatInitialized(firstParticipant.id));
dispatch(openChat({}, disablePolls));
dispatch(openChat({}, arePollsDisabled(getState())));
}));
}
} else {

View File

@@ -16,6 +16,7 @@ import {
CONFERENCE_JOINED,
CONFERENCE_LEFT,
CONFERENCE_WILL_JOIN,
ENDPOINT_MESSAGE_RECEIVED,
SET_ROOM
} from '../../base/conference/actionTypes';
import { JITSI_CONFERENCE_URL_KEY } from '../../base/conference/constants';
@@ -184,6 +185,22 @@ externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
break;
}
case ENDPOINT_MESSAGE_RECEIVED: {
const { participant, data } = action;
if (data?.name === ENDPOINT_TEXT_MESSAGE_NAME) {
sendEvent(
store,
ENDPOINT_TEXT_MESSAGE_RECEIVED,
/* data */ {
message: data.text,
senderId: participant.getId()
});
}
break;
}
case ENTER_PICTURE_IN_PICTURE:
sendEvent(store, type, /* data */ {});
break;
@@ -419,24 +436,6 @@ function _unregisterForNativeEvents() {
function _registerForEndpointTextMessages(store: IStore) {
const conference = getCurrentConference(store.getState());
conference?.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(...args: any[]) => {
if (args && args.length >= 2) {
const [ sender, eventData ] = args;
if (eventData.name === ENDPOINT_TEXT_MESSAGE_NAME) {
sendEvent(
store,
ENDPOINT_TEXT_MESSAGE_RECEIVED,
/* data */ {
message: eventData.text,
senderId: sender._id
});
}
}
});
conference?.on(
JitsiConferenceEvents.MESSAGE_RECEIVED,
(id: string, message: string, timestamp: number) => {

View File

@@ -13,6 +13,7 @@ import { isUnsafeRoomWarningEnabled } from '../../../prejoin/functions';
// @ts-ignore
import WelcomePage from '../../../welcome/components/WelcomePage';
import { isWelcomePageEnabled } from '../../../welcome/functions';
import Whiteboard from '../../../whiteboard/components/native/Whiteboard';
import { _ROOT_NAVIGATION_READY } from '../actionTypes';
import { rootNavigationRef } from '../rootNavigationContainerRef';
import { screen } from '../routes';
@@ -23,7 +24,8 @@ import {
navigationContainerTheme,
preJoinScreenOptions,
unsafeMeetingScreenOptions,
welcomeScreenOptions
welcomeScreenOptions,
whiteboardScreenOptions
} from '../screenOptions';
import ConnectingPage from './ConnectingPage';
@@ -94,6 +96,10 @@ const RootNavigationContainer = ({ dispatch, isUnsafeRoomWarningAvailable, isWel
component = { ConnectingPage }
name = { screen.connecting }
options = { connectingScreenOptions } />
<RootStack.Screen // @ts-ignore
component = { Whiteboard }
name = { screen.conference.whiteboard }
options = { whiteboardScreenOptions } />
<RootStack.Screen
component = { Prejoin }
name = { screen.preJoin }

View File

@@ -16,7 +16,7 @@ import Conference from '../../../../../conference/components/native/Conference';
// @ts-ignore
import CarMode from '../../../../../conference/components/native/carmode/CarMode';
// @ts-ignore
import { getDisablePolls } from '../../../../../conference/functions';
import { arePollsDisabled } from '../../../../../conference/functions';
// @ts-ignore
import SharedDocument from '../../../../../etherpad/components/native/SharedDocument';
// @ts-ignore
@@ -81,7 +81,7 @@ const ConferenceStack = createStackNavigator();
const ConferenceNavigationContainer = () => {
const isPollsDisabled = useSelector(getDisablePolls);
const isPollsDisabled = useSelector(arePollsDisabled);
let ChatScreen;
let chatScreenName;
let chatTitleString;

View File

@@ -22,7 +22,8 @@ export const screen = {
security: 'Security Options',
sharedDocument: 'Shared document',
speakerStats: 'Speaker Stats',
subtitles: 'Subtitles'
subtitles: 'Subtitles',
whiteboard: 'Whiteboard'
},
connecting: 'Connecting',
dialInSummary: 'Dial-In Info',

View File

@@ -188,6 +188,19 @@ export const connectingScreenOptions = {
headerShown: false
};
/**
* Screen options for the whiteboard screen.
*/
export const whiteboardScreenOptions = {
gestureEnabled: true,
headerStyle: {
backgroundColor: BaseTheme.palette.ui01
},
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Screen options for pre-join screen.
*/

View File

@@ -1,6 +1,8 @@
import { DOMParser } from '@xmldom/xmldom';
import { atob, btoa } from 'abab';
import { Platform } from 'react-native';
import BackgroundTimer from 'react-native-background-timer';
import { TextDecoder, TextEncoder } from 'text-encoding';
import 'promise.allsettled/auto'; // Promise.allSettled.
import 'react-native-url-polyfill/auto'; // Complete URL polyfill.
@@ -313,4 +315,23 @@ function _visitNode(node, callback) {
global.sessionStorage = new Storage();
}
global.TextDecoder = TextDecoder;
global.TextEncoder = TextEncoder;
// atob
//
// Required by:
// - Strophe
if (typeof global.atob === 'undefined') {
global.atob = atob;
}
// btoa
//
// Required by:
// - Strophe
if (typeof global.btoa === 'undefined') {
global.btoa = btoa;
}
})(global || window || this); // eslint-disable-line no-invalid-this

View File

@@ -103,6 +103,13 @@ export const RAISE_HAND_NOTIFICATION_ID = 'RAISE_HAND_NOTIFICATION';
*/
export const SALESFORCE_LINK_NOTIFICATION_ID = 'SALESFORCE_LINK_NOTIFICATION';
/**
* The identifier of the lobby notification.
*
* @type {string}
*/
export const VISITORS_PROMOTION_NOTIFICATION_ID = 'VISITORS_PROMOTION_NOTIFICATION';
/**
* Amount of participants beyond which no join notification will be emitted.
*/

View File

@@ -1,11 +1,11 @@
import { IStore } from '../app/types';
import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
import { ENDPOINT_MESSAGE_RECEIVED, NON_PARTICIPANT_MESSAGE_RECEIVED } from '../base/conference/actionTypes';
import { getCurrentConference } from '../base/conference/functions';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { playSound } from '../base/sounds/actions';
import { INCOMING_MSG_SOUND_ID } from '../chat/constants';
import { arePollsDisabled } from '../conference/functions.any';
import { showNotification } from '../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../notifications/constants';
@@ -58,37 +58,38 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const result = next(action);
switch (action.type) {
case CONFERENCE_JOIN_IN_PROGRESS: {
const { conference } = action;
case ENDPOINT_MESSAGE_RECEIVED: {
const { participant, data } = action;
const isNewPoll = data.type === COMMAND_NEW_POLL;
conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(user: any, data: any) => {
const isNewPoll = data.type === COMMAND_NEW_POLL;
_handleReceivePollsMessage({
...data,
senderId: isNewPoll ? user._id : undefined,
voterId: isNewPoll ? undefined : user._id
}, dispatch);
});
conference.on(JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
(id: any, data: any) => {
const isNewPoll = data.type === COMMAND_NEW_POLL;
_handleReceivePollsMessage({
...data,
senderId: isNewPoll ? id : undefined,
voterId: isNewPoll ? undefined : id
}, dispatch);
});
_handleReceivePollsMessage({
...data,
senderId: isNewPoll ? participant.getId() : undefined,
voterId: isNewPoll ? undefined : participant.getId()
}, dispatch, getState);
break;
}
// Middleware triggered when a poll is received
case RECEIVE_POLL: {
case NON_PARTICIPANT_MESSAGE_RECEIVED: {
const { id, data } = action;
const isNewPoll = data.type === COMMAND_NEW_POLL;
_handleReceivePollsMessage({
...data,
senderId: isNewPoll ? id : undefined,
voterId: isNewPoll ? undefined : id
}, dispatch, getState);
break;
}
case RECEIVE_POLL: {
const state = getState();
if (arePollsDisabled(state)) {
break;
}
const isChatOpen: boolean = state['features/chat'].isOpen;
const isPollsTabFocused: boolean = state['features/chat'].isPollsTabFocused;
@@ -109,10 +110,15 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
*
* @param {Object} data - The json data carried by the polls message.
* @param {Function} dispatch - The dispatch function.
* @param {Function} getState - The getState function.
*
* @returns {void}
*/
function _handleReceivePollsMessage(data: any, dispatch: IStore['dispatch']) {
function _handleReceivePollsMessage(data: any, dispatch: IStore['dispatch'], getState: IStore['getState']) {
if (arePollsDisabled(getState())) {
return;
}
switch (data.type) {
case COMMAND_NEW_POLL: {
const { question, answers, pollId, senderId } = data;

View File

@@ -38,7 +38,7 @@ import { navigateRoot } from '../../../mobile/navigation/rootNavigationContainer
import { screen } from '../../../mobile/navigation/routes';
import AudioMuteButton from '../../../toolbox/components/native/AudioMuteButton';
import VideoMuteButton from '../../../toolbox/components/native/VideoMuteButton';
import { isDisplayNameRequired } from '../../functions';
import { isDisplayNameRequired, isRoomNameEnabled } from '../../functions';
import { IPrejoinProps } from '../../types';
import { hasDisplayName } from '../../utils';
@@ -58,6 +58,7 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
= useSelector((state: IReduxState) => !getFeatureFlag(state, PREJOIN_PAGE_HIDE_DISPLAY_NAME, false));
const isDisplayNameReadonly = useSelector(isNameReadOnly);
const roomName = useSelector((state: IReduxState) => getConferenceName(state));
const roomNameEnabled = useSelector((state: IReduxState) => isRoomNameEnabled(state));
const participantName = localParticipant?.name;
const [ displayName, setDisplayName ]
= useState(participantName || '');
@@ -168,13 +169,17 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
{
isFocused
&& <View style = { largeVideoContainerStyles as StyleProp<ViewStyle> }>
<View style = { styles.displayRoomNameBackdrop as StyleProp<TextStyle> }>
<Text
numberOfLines = { 1 }
style = { styles.preJoinRoomName as StyleProp<TextStyle> }>
{ roomName }
</Text>
</View>
{
roomNameEnabled && (
<View style = { styles.displayRoomNameBackdrop as StyleProp<TextStyle> }>
<Text
numberOfLines = { 1 }
style = { styles.preJoinRoomName as StyleProp<TextStyle> }>
{ roomName }
</Text>
</View>
)
}
<LargeVideo />
</View>
}
@@ -194,10 +199,15 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
placeholder = { t('dialog.enterDisplayName') }
value = { displayName } />
}
{showDisplayNameError && (
<View style = { styles.errorContainer as StyleProp<TextStyle> }>
<Text style = { styles.error as StyleProp<TextStyle> }>{t('prejoin.errorMissingName')}</Text>
</View>)}
{
showDisplayNameError && (
<View style = { styles.errorContainer as StyleProp<TextStyle> }>
<Text style = { styles.error as StyleProp<TextStyle> }>
{ t('prejoin.errorMissingName') }
</Text>
</View>
)
}
<Button
accessibilityLabel = 'prejoin.joinMeeting'
disabled = { showDisplayNameError }

View File

@@ -1,7 +1,10 @@
import { IReduxState } from '../app/types';
import { getRoomName } from '../base/conference/functions';
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions.any';
import { UNSAFE_ROOM_WARNING } from '../base/flags/constants';
import {
MEETING_NAME_ENABLED,
UNSAFE_ROOM_WARNING
} from '../base/flags/constants';
import { getFeatureFlag } from '../base/flags/functions';
import { isAudioMuted, isVideoMutedByUser } from '../base/media/functions';
import { getLobbyConfig } from '../lobby/functions';
@@ -184,3 +187,14 @@ export function isUnsafeRoomWarningEnabled(state: IReduxState): boolean {
return getFeatureFlag(state, UNSAFE_ROOM_WARNING, enableInsecureRoomNameWarning);
}
/**
* Returns true if the room name is enabled.
*
* @param {IReduxState} state - The state of the app.
* @returns {boolean}
*/
export function isRoomNameEnabled(state: IReduxState): boolean {
return getFeatureFlag(state, MEETING_NAME_ENABLED, true)
|| !state['features/base/config'].hideConferenceSubject;
}

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