Compare commits

...

76 Commits

Author SHA1 Message Date
Boris Grozev
8b8caf56dc fix: Use the iframe configured tenant for iframe tests. 2025-09-19 13:47:23 -05:00
Boris Grozev
039caa4d3b squash: Fix import paths. 2025-09-19 13:33:18 -05:00
Boris Grozev
37252bdf2d ref: Move visitorsLive to the specs/jaas. 2025-09-19 13:26:32 -05:00
Boris Grozev
85f52471b2 ref: Change visitorsLive to use the JaaS utils. 2025-09-19 13:25:41 -05:00
Boris Grozev
13b956dae2 squash: Linting, skip test if WH proxy is required but not configured. 2025-09-19 13:25:09 -05:00
Boris Grozev
77981d825b fix: Fix the tenant used for webhook proxy. 2025-09-19 12:45:35 -05:00
damencho
cc312877f4 fix(muc_displayname): Adds a nil check. 2025-09-19 09:45:15 -05:00
Calin-Teodor
eb8b6159ec feat(notifications/native): fix case for no title notifications 2025-09-19 13:38:17 +03:00
damencho
f9d8feacd2 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2089.0.0+75c1c6ff...v2091.0.0+518cec5f
2025-09-18 14:29:55 -05:00
damencho
f5668b6e8b feat(visitors): Retries as a visitor when max occupants reached. 2025-09-18 14:29:55 -05:00
Jaya Allamsetty
4219d9ad4d chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2087.0.0+8eccb59f...v2089.0.0+75c1c6ff
2025-09-17 14:55:51 -04:00
Jaya Allamsetty
d68b9b1cad chore(deps) Update @jitsi/logger to 2.1.1 2025-09-16 22:45:59 -04:00
Jaya Allamsetty
8f0b9575c4 ref(logging) Rename logger ids to facilitate proper filtering of logs. 2025-09-16 22:45:59 -04:00
damencho
f780207c22 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2086.0.0+bc389f3b...v2087.0.0+8eccb59f
2025-09-16 13:05:49 -05:00
Jaya Allamsetty
ce19e6d40b fix(logging) Update the logger ids for default log levels 2025-09-16 12:32:19 -05:00
Jaya Allamsetty
b108db832f chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2084.0.0+819cdfbb...v2086.0.0+bc389f3b
2025-09-16 12:32:19 -05:00
damencho
e4283e61dd fix(wait-for-host): Fixes missing param. 2025-09-16 07:04:16 -05:00
Calinteodor
50e2458124 fix(chat): disable reactions for reaction messages (#16425)
*Removed the ability to react to reactions inside the chat panel.
2025-09-16 10:59:13 +03:00
damencho
329df31811 feat: Requires a moderator to start a moderated room without a tenant. 2025-09-15 20:39:05 -05:00
Joan Montané
fce39be9d2 lang: Update Sardinian
* Update main-sc.json

* Fix main-sc.json

---------

Co-authored-by: adrmzz <adrmzz@users.noreply.github.com>
2025-09-15 07:41:14 -05:00
Calinteodor
6c5a9ea199 feat(react-native-sdk): Remove JavaScriptSandboxModule from package 2025-09-11 16:15:15 +03:00
Calinteodor
196192c97f feat(react-native-sdk): Update update_dependencies.js
Removed code that merges package overrides from RNSDK. We no longer use them.
2025-09-11 15:15:02 +03:00
nbeck.indy
71f358c62a fix (lobby): Remove _onSendMessage base method from LobbyChatScreen 2025-09-10 13:51:23 -05:00
Calinteodor
7aa7e76ccd .github: fix CI iOS SDK step
*Add step to install iOS platform related simulators needed by Xcode.
2025-09-10 14:25:34 +03:00
Calin-Teodor
cd77b6bbe4 feat(react-native-sdk): update scripts to add worklets babel plugin deps 2025-09-10 11:30:09 +03:00
Дамян Минков
e94df6799e fix(tests): Fixes error because of not waiting for conference left event.
Try to fix the error we see: 
Error: waitUntil condition failed with the following reason: Command script.callFunction with id 116 (with the following parameter: {"functionDeclaration":"function anonymous(\n) {\nreturn (/* __wdio script__ */()=>typeof APP!==\"undefined\"&&APP.conference?.isJoined()/* __wdio script end__ */).apply(this, arguments);\n}","awaitPromise":true,"arguments":[],"target":{"context":"10352FFE685AC1D0503E1ECA3BFD33B2"}}) timed out

Seems like we do not wait for all checks to happen and start joining again in the middle of switching/checking.
2025-09-09 17:59:43 -05:00
damencho
2e92818b53 fix(lobby): Clear any params set on destroy lobby. 2025-09-09 15:39:49 -05:00
damencho
8a3129f7bf fix(visitors): Fixes checking for group.
moderator_id can have a user id or a group id, that will make all users from that group be moderators.
2025-09-09 12:17:34 -05:00
Jaya Allamsetty
eb03642ea6 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2076.0.0+69f68d36...v2084.0.0+819cdfbb
2025-09-09 10:29:11 -04:00
Calin-Teodor
c436e48956 feat(react-native-sdk): add react-native-worklets-core as a peer dep 2025-09-09 16:47:28 +03:00
Mihaela Dumitru
58db02bab8 feat(visitor) confirm raised hand sent to mods (#16388) 2025-09-09 16:19:39 +03:00
Calinteodor
abc1f3d33b dep(react-native-worklets-core): Replace duktape to align with Android 16kb page size alignment (#16393)
* Replaced duktape lib with react-native-worklets-core and checked for compatibility with Android 16kb page-size requirement.
2025-09-09 12:46:11 +03:00
Matteo
b2166d9874 lang: Update main-it.json (#16363)
- Translated new strings
- Improved translation
2025-09-08 16:04:44 -05:00
Florian
901a13a99a Added hint to desktop sharing frame rate config 2025-09-08 09:48:41 -04:00
Hugo Lavernhe
1e15d9421b feat(settings) Add advanced audio settings checkboxes (#16316)
* Add checkboxes to toggle audio settings

* Sync checkboxes with audio mixer effect

* Add tooltips

* Move previewAudioTrack to redux

* Add translation

* Add audio settings state to redux

* Update docs

* Apply review comments

* Create local track with audio contraints when unmuting

* Refactor functions and naming

* Add enableAdvancedAudioSettings config

* Fix mobile imports

* Add tooltips content

* Update react/features/base/config/functions.any.ts

* Layout checkboxes in a two-column grid

* Fix web imports

* Sort translation alphabetically

* Separate audio mute implementation for mobile and web

* Apply review comments

* squash: Add imports for middleware.any

* squash: fix linter errors

* Remove tooltips

* Lint

* Refactored setting of audio constraints in createLocalTracksF with checks for feature flag and desktop

---------

Co-authored-by: Jaya Allamsetty <54324652+jallamsetty1@users.noreply.github.com>
Co-authored-by: Jaya Allamsetty <jaya.allamsetty@8x8.com>
2025-09-05 16:52:35 -04:00
Дамян Минков
9252bbb036 fix: Fixes log message about meeting id. 2025-09-05 10:42:30 -05:00
Hristo Terezov
f1bae8bc10 feat(chat): Add Open chat button to chat notifications 2025-09-04 16:27:18 -05:00
Дамян Минков
5a54511d2c fix: Fixes missing string for shortcut. 2025-09-04 08:25:12 -05:00
bgrozev
61764273b2 ref: Refactor tests (#16399)
* ref: Inline enterTileView.
* ref: Refactor tileView, remove tileView.LastN.
    The "last n" cases are not related to tile view and are covered in lastN.spec.ts.
* ref: Remove redundant "skipInMeetingChecks: true".
    skipInMeetingChecks is only used in ensureTwoParticipants, ensureThreeParticipants and ensureFourParticipants.
* ref: Move recording test to jaas/, more refactoring.
* ref: Rename and document switchToAPI() and switchInPage().
* ref: Move the tileView into 2way (temp).
2025-09-03 15:31:43 -05:00
Pelle Hanses
e39f38f75b lang: Update swedish (#16396)
Changed to a more business-like tone.
2025-09-03 13:36:54 -05:00
Edgars Voroboks
2d25f48c72 lang: Update Latvian language translation (#16395) 2025-09-03 13:36:48 -05:00
damencho
2cb727fc58 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2051.0.0+ccc06e83...v2076.0.0+69f68d36
2025-09-03 08:07:11 -05:00
damencho
c069c0d7c3 feat(display-name): Handles new display-name extension in messages.
The display name is used in messages when messages are coming from visitors or from the history. The display name is used only when the participant is not available in the meeting to get its name.
2025-09-03 08:07:11 -05:00
damencho
5de69d501d feat(displayname): Adds new feature name-readonly.
This enforces display names from jwt tokens.
2025-09-03 08:07:11 -05:00
damencho
599c88a71d fix: Drops hideDisplayNameForAll. 2025-09-03 08:07:11 -05:00
Mihaela Dumitru
5476321df6 fix(recordings): conditionally render learn more link in consent dialog (#16386) 2025-08-28 17:23:42 +03:00
damencho
076b6a2a7e fix(permissions): Fixes grant moderator after being in lobby. 2025-08-27 17:13:27 -05:00
damencho
8b9df0cd37 fix(av-moderation): Update initial whitelists when auto enabling. 2025-08-27 17:13:19 -05:00
Calin-Teodor
44f5de3db4 feat(recording): explicitly convert visible value to true or false 2025-08-26 17:48:47 +03:00
Calin-Teodor
fb69225d42 feat(notifications): style adjustments 2025-08-26 17:31:02 +03:00
Mihaela Dumitru
32df284277 fix(accessibility) improve file actions with focus management and ARIA roles (#16322) 2025-08-26 16:29:05 +03:00
José Luís Andrade
253679cfb9 lang: Update Portuguese (#16331) 2025-08-25 21:40:45 -05:00
damencho
057c19f4dd feat(metadata): Adds logging when metadata is modified or sent. 2025-08-25 12:56:38 -05:00
damencho
6159a23c55 fix(tests): Fixes participantRoleChanged event handling. 2025-08-25 07:40:47 -05:00
damencho
1685c39c5d fix(tests): Fix passing correct participant options. 2025-08-22 15:09:53 -05:00
damencho
2cecc61b97 fix(tests): Make sure first participant is moderator. 2025-08-22 15:09:53 -05:00
damencho
df2262ae53 feat(tests): Return early if jaas tests not configured. 2025-08-22 15:09:53 -05:00
damencho
d61deab163 feat(tests): Make sure we add a single listener for iframeAPI events. 2025-08-22 15:09:53 -05:00
damencho
e7eab72c0c feat(tests): Clear previous videoConferenceLeft events. 2025-08-22 15:09:53 -05:00
damencho
c1e803c6e3 feat(tests): Increase wait time for webhooks. 2025-08-22 15:09:53 -05:00
damencho
dc1f20e059 fix(localrecording): Local recording is not supported in embedded mode.
It is not available due to cross-origin or not able to start setCaptureHandleConfig in iframe.
error 1: Failed to execute 'showSaveFilePicker' on 'Window': Cross origin sub frames aren't allowed to show a file picker.
error 2: Failed to execute 'setCaptureHandleConfig' on 'MediaDevices': Can only be called from the top-level document.
2025-08-22 06:51:12 -05:00
bgrozev
61ee9af304 test: Add a test for visitors with single sender (PLI). (#16364) 2025-08-21 16:31:03 -05:00
bgrozev
d75de3642e Fix jaas tests (#16360)
* fix: Fix jaas joinMuc(), it remove now redundant calls to hangup().

* fix: Fix jaas passcode tests.

* ref: make joinParticipant private again.
2025-08-20 14:46:52 -05:00
Calinteodor
1ae1729545 chore(android): add top and bottom margin insets for API 35 (#16359)
* Once we started targeting SDK 35 on a device running Android 15 or higher, by default, we display edge-to-edge.
  We can handle overlaps by using insets.
2025-08-20 17:22:33 +03:00
Saúl Ibarra Corretgé
8cea505417 fix(dynamic-branding) cleanup custom icon SVGs 2025-08-20 15:49:12 +02:00
damencho
b0a96b32d2 fix(jiconop): Fixes loading it under different virtual hosts. 2025-08-19 15:59:34 -05:00
bgrozev
dac9b5e244 test: Check for send/receive independently. (#16356)
This allows the logs to show which one definitely failed.
2025-08-19 15:31:52 -05:00
damencho
d15cfd845a fix(config): Drops legacy config prejoinPageEnabled. 2025-08-19 08:41:04 -05:00
bgrozev
91e4ac1665 ref: Extract test configuration code to TestsConfig.ts (#16329)
* ref: Move iFrameUsesJaas to TestsConfig.

* ref: Move room name prefix/suffix to config.

* ref: Move JaaS configuration to TestsConfig.

* ref: Move iframe config to TestsConfig.

* ref: Move webproxy config to TestsConfig.

* ref: Move JWT config to TestsConfig.

* doc: Document some of the IContext fields.

* Add a debug config option.
2025-08-18 13:32:41 -05:00
damencho
fda42e5230 fix: More fixes sending metadata to jicofo.
f1a0012 was not enough to address the issue.
2025-08-18 11:24:07 -05:00
Calin-Teodor
142d4441c1 feat(chat): add tooltip for each chat screen tab 2025-08-18 16:33:15 +03:00
Mihaela Dumitru
5814c4dda7 fix(dynamic-branding) expand background color option to support a wider range of configurations (#16334) 2025-08-18 11:38:09 +03:00
Jaya Allamsetty
8c1dc03363 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2048.0.0+4d9a138b...v2051.0.0+ccc06e83
2025-08-14 10:49:20 -04:00
Jaya Allamsetty
ff6fc198f1 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2044.0.0+437abe32...v2048.0.0+4d9a138b
2025-08-13 20:22:14 -04:00
damencho
f1a0012fc1 fix: Fixes sending metadata to jicofo.
In cases like waiting-for-host lobby, jicofo can leave the room and rejoin later, without the room being destroyed. We need to make sure the metadata will reach jicofo on second attempt.
2025-08-11 16:32:25 +03:00
Дамян Минков
85522aea25 feat(tests): Updates join logic. (#16320)
* fix: Fix example file.

* fix: Fix using webhook proxy with iframe and jaas tests.

* fix: Fixes detecting max users notification.

* ref: Clear _joinParticipant to not depend on participant names.

* ref: Use joinParticipant in jaas tests.

* ref: Drops JAAS_DOMAIN as BASE_URL is used.

* fix: Drops ctx from function parameters.

* ref: Drops not needed context members.

* ref: Drops extra function.

* ref: Participant.joinConference to use roomName from options.

* doc: Updates docs.

* fix: Adds roomName to joinOptions.

Make it possible to override the generated value.
2025-08-11 06:52:16 -05:00
243 changed files with 3126 additions and 1711 deletions

View File

@@ -152,12 +152,13 @@ jobs:
xcodebuild clean \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeetSDK
xcodebuild -downloadPlatform iOS -buildVersion 18.2
xcodebuild archive \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeetSDK \
-configuration Release \
-sdk iphoneos \
-destination='generic/platform=iOS' \
-destination 'generic/platform=iOS' \
-archivePath ios/sdk/out/ios-device \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES

View File

@@ -46,7 +46,6 @@ dependencies {
implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation 'com.squareup.duktape:duktape-android:1.3.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'androidx.startup:startup-runtime:1.1.0'
implementation 'com.google.j2objc:j2objc-annotations:3.0.0'
@@ -87,6 +86,7 @@ dependencies {
implementation project(':react-native-svg')
implementation project(':react-native-video')
implementation project(':react-native-webview')
implementation project(':react-native-worklets-core')
// Use `api` here so consumers can use WebRTCModuleOptions.
api project(':react-native-webrtc')

View File

@@ -1,57 +0,0 @@
/*
* 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 com.facebook.react.bridge.Promise;
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;
import com.squareup.duktape.Duktape;
@ReactModule(name = JavaScriptSandboxModule.NAME)
class JavaScriptSandboxModule extends ReactContextBaseJavaModule {
public static final String NAME = "JavaScriptSandbox";
public JavaScriptSandboxModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Evaluates the given code in a Duktape VM.
* @param code - The code that needs to evaluated.
* @param promise - Resolved with the output in case of success or rejected with an exception
* in case of failure.
*/
@ReactMethod
public void evaluate(String code, Promise promise) {
Duktape vm = Duktape.create();
try {
Object res = vm.evaluate(code);
promise.resolve(res.toString());
} catch (Throwable tr) {
promise.reject(tr);
} finally {
vm.close();
}
}
@Override
public String getName() {
return NAME;
}
}

View File

@@ -25,9 +25,15 @@ import android.content.IntentFilter;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.facebook.react.modules.core.PermissionListener;
@@ -87,6 +93,28 @@ public class JitsiMeetActivity extends AppCompatActivity
launch(context, options);
}
public static void addTopBottomInsets(@NonNull Window w, @NonNull View v) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) return;
View decorView = w.getDecorView();
decorView.post(() -> {
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(decorView);
if (insets != null) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
params.topMargin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top;
params.bottomMargin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;
v.setLayoutParams(params);
decorView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
view.setBackgroundColor(JitsiMeetView.BACKGROUND_COLOR);
return windowInsets;
});
}
});
}
// Overrides
//
@@ -107,6 +135,7 @@ public class JitsiMeetActivity extends AppCompatActivity
JitsiMeetActivityDelegate.onHostResume(this);
setContentView(R.layout.activity_jitsi_meet);
addTopBottomInsets(getWindow(),findViewById(android.R.id.content));
this.jitsiView = findViewById(R.id.jitsiView);
registerForBroadcastMessages();

View File

@@ -36,7 +36,7 @@ public class JitsiMeetView extends FrameLayout {
/**
* Background color. Should match the background color set in JS.
*/
private static final int BACKGROUND_COLOR = 0xFF040404;
public static final int BACKGROUND_COLOR = 0xFF040404;
/**
* React Native root view.

View File

@@ -17,7 +17,6 @@
package org.jitsi.meet.sdk;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import androidx.annotation.Nullable;
@@ -65,7 +64,6 @@ class ReactInstanceManagerHolder {
new AudioModeModule(reactContext),
new DropboxModule(reactContext),
new ExternalAPIModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),
new PictureInPictureModule(reactContext),
@@ -110,6 +108,7 @@ class ReactInstanceManagerHolder {
new com.horcrux.svg.SvgPackage(),
new org.wonday.orientation.OrientationPackage(),
new com.splashview.SplashViewPackage(),
new com.worklets.WorkletsCorePackage(),
new ReactPackageAdapter() {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {

View File

@@ -52,3 +52,5 @@ include ':react-native-webrtc'
project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android')
include ':react-native-webview'
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
include ':react-native-worklets-core'
project(':react-native-worklets-core').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-worklets-core/android')

View File

@@ -8,9 +8,13 @@ module.exports = {
// This happens because react native has conflict with @babel/plugin-transform-private-methods plugin
// https://github.com/ethers-io/ethers.js/discussions/4309#discussioncomment-6694524
plugins: [ 'optional-require',
[ '@babel/plugin-transform-private-methods', {
'loose': true
} ]
plugins: [
'optional-require',
[
'@babel/plugin-transform-private-methods', {
'loose': true
}
],
'react-native-worklets-core/plugin'
]
};

View File

@@ -134,6 +134,7 @@ import {
isLocalTrackMuted,
isUserInteractionRequiredForUnmute
} from './react/features/base/tracks/functions';
import { getLocalJitsiAudioTrackSettings } from './react/features/base/tracks/functions.web';
import { downloadJSON } from './react/features/base/util/downloadJSON';
import { getJitsiMeetGlobalNSConnectionTimes } from './react/features/base/util/helpers';
import { openLeaveReasonDialog } from './react/features/conference/actions.web';
@@ -158,13 +159,14 @@ import { disableReceiver, stopReceiver } from './react/features/remote-control/a
import { setScreenAudioShareState } from './react/features/screen-share/actions.web';
import { isScreenAudioShared } from './react/features/screen-share/functions';
import { toggleScreenshotCaptureSummary } from './react/features/screenshot-capture/actions';
import { setAudioSettings } from './react/features/settings/actions.web';
import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect';
import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
import { transcriberJoined, transcriberLeft } from './react/features/transcribing/actions';
import { muteLocal } from './react/features/video-menu/actions.any';
const logger = Logger.getLogger(__filename);
const logger = Logger.getLogger('app:conference-web');
let room;
/*
@@ -566,7 +568,15 @@ export default {
if (browser.isWebKitBased()) {
this.muteAudio(true, true);
} else {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
localTracks = localTracks.filter(track => {
if (track.getType() === MEDIA_TYPE.AUDIO) {
track.stopStream();
return false;
}
return true;
});
}
}
@@ -1763,7 +1773,11 @@ export default {
return this.useAudioStream(stream);
})
.then(() => {
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
const state = APP.store.getState();
const localAudio = getLocalJitsiAudioTrack(state);
const settings = getLocalJitsiAudioTrackSettings(state);
APP.store.dispatch(setAudioSettings(settings));
if (localAudio && isDefaultMicSelected) {
// workaround for the default device to be shown as selected in the

View File

@@ -363,6 +363,7 @@ var config = {
// Desktop sharing
// Optional desktop sharing frame rate options. Default value: min:5, max:5.
// Setting higher min/max values will affect the resolution, it makes it worse.
// desktopSharingFrameRate: {
// min: 5,
// max: 5,
@@ -791,7 +792,6 @@ var config = {
// Configs for prejoin page.
// prejoinConfig: {
// // When 'true', it shows an intermediate page before joining, where the user can configure their devices.
// // This replaces `prejoinPageEnabled`. Defaults to true.
// enabled: true,
// // Hides the participant name editing field in the prejoin screen.
// // If requireDisplayName is also set as true, a name should still be provided through

View File

@@ -1497,6 +1497,27 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-worklets-core (1.6.2):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- React-nativeconfig (0.77.2)
- React-NativeModulesApple (0.77.2):
- glog
@@ -1932,6 +1953,7 @@ DEPENDENCIES:
- react-native-video (from `../node_modules/react-native-video`)
- react-native-webrtc (from `../node_modules/react-native-webrtc`)
- react-native-webview (from `../node_modules/react-native-webview`)
- react-native-worklets-core (from `../node_modules/react-native-worklets-core`)
- React-nativeconfig (from `../node_modules/react-native/ReactCommon`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
@@ -2101,6 +2123,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-webrtc"
react-native-webview:
:path: "../node_modules/react-native-webview"
react-native-worklets-core:
:path: "../node_modules/react-native-worklets-core"
React-nativeconfig:
:path: "../node_modules/react-native/ReactCommon"
React-NativeModulesApple:
@@ -2257,6 +2281,7 @@ SPEC CHECKSUMS:
react-native-video: eb861d67a71dfef1bbf6086a811af5f338b13781
react-native-webrtc: 2261a482150195092246fe70b3aff976f2e11ec5
react-native-webview: 079eca50edf657503318b66687dadfb903731aa8
react-native-worklets-core: b59cf88762c8fb6132d8796babd4cec15217d6f0
React-nativeconfig: ecf4dc92c40b97e2b3f0c619938f78bfd6507b08
React-NativeModulesApple: f457bbfb30fb3bc41979b1a87b99d292d7340d39
React-perflogger: 1111b5feb064c4cc83df88fb403efda54b387951

View File

@@ -51,7 +51,6 @@
C81E9AB925AC5AD800B134D9 /* ExternalAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = C81E9AB825AC5AD800B134D9 /* ExternalAPI.h */; };
C8AFD27F2462C613000293D2 /* InfoPlistUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */; settings = {ATTRIBUTES = (Public, ); }; };
C8AFD2802462C613000293D2 /* InfoPlistUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */; };
DE438CDA2350934700DD541D /* JavaScriptSandbox.m in Sources */ = {isa = PBXBuildFile; fileRef = DE438CD82350934700DD541D /* JavaScriptSandbox.m */; };
DE65AACA2317FFCD00290BEC /* LogUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DE65AAC92317FFCD00290BEC /* LogUtils.h */; };
DE65AACC2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */; };
DE762DB422AFDE76000DEBD6 /* JitsiMeetUserInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DE762DB322AFDE76000DEBD6 /* JitsiMeetUserInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -98,7 +97,6 @@
DE9A015C289A9A9A00E41CBB /* JitsiMeetLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = DE81A2D32316AC4D00AE1940 /* JitsiMeetLogger.m */; };
DE9A015E289A9A9A00E41CBB /* JitsiMeetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */; };
DE9A015F289A9A9A00E41CBB /* JitsiMeet.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFE535321FB1BF800011A3A /* JitsiMeet.m */; };
DE9A0160289A9A9A00E41CBB /* JavaScriptSandbox.m in Sources */ = {isa = PBXBuildFile; fileRef = DE438CD82350934700DD541D /* JavaScriptSandbox.m */; };
DE9A0162289A9A9A00E41CBB /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BB9AD781F5EC6D7001C08DB /* Intents.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
DE9A0163289A9A9A00E41CBB /* CallKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BB9AD761F5EC6CE001C08DB /* CallKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
DE9A0166289A9A9A00E41CBB /* CallKitIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */; };
@@ -161,7 +159,6 @@
C81E9AB825AC5AD800B134D9 /* ExternalAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExternalAPI.h; sourceTree = "<group>"; };
C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InfoPlistUtil.h; sourceTree = "<group>"; };
C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InfoPlistUtil.m; sourceTree = "<group>"; };
DE438CD82350934700DD541D /* JavaScriptSandbox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JavaScriptSandbox.m; sourceTree = "<group>"; };
DE65AAC92317FFCD00290BEC /* LogUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LogUtils.h; sourceTree = "<group>"; };
DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetBaseLogHandler+Private.h"; sourceTree = "<group>"; };
DE762DB322AFDE76000DEBD6 /* JitsiMeetUserInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetUserInfo.h; sourceTree = "<group>"; };
@@ -250,7 +247,6 @@
C69EFA02209A0EFD0027712B /* callkit */,
A4A934E7212F3AB8001E9388 /* dropbox */,
0BD906E91EC0C00300C8C18E /* Info.plist */,
DE438CD82350934700DD541D /* JavaScriptSandbox.m */,
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
DEFE535821FB311F00011A3A /* JitsiMeet+Private.h */,
DEA9F283258A5D9900D4CD74 /* JitsiMeetSDK.h */,
@@ -681,7 +677,6 @@
DE81A2D52316AC4D00AE1940 /* JitsiMeetLogger.m in Sources */,
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
DEFE535421FB1BF800011A3A /* JitsiMeet.m in Sources */,
DE438CDA2350934700DD541D /* JavaScriptSandbox.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -714,7 +709,6 @@
4E0EF63328CA2FB3005D1B03 /* JMCallKitEmitter.m in Sources */,
DE9A015E289A9A9A00E41CBB /* JitsiMeetView.m in Sources */,
DE9A015F289A9A9A00E41CBB /* JitsiMeet.m in Sources */,
DE9A0160289A9A9A00E41CBB /* JavaScriptSandbox.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -1,55 +0,0 @@
/*
* 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.
*/
@import JavaScriptCore;
#import <React/RCTBridgeModule.h>
@interface JavaScriptSandbox : NSObject<RCTBridgeModule>
@end
@implementation JavaScriptSandbox
RCT_EXPORT_MODULE();
+ (BOOL)requiresMainQueueSetup {
return NO;
}
#pragma mark - Exported methods
RCT_EXPORT_METHOD(evaluate:(NSString *)code
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
__block BOOL hasError = NO;
JSContext *ctx = [[JSContext alloc] init];
ctx.exceptionHandler = ^(JSContext *context, JSValue *exception) {
hasError = YES;
reject(@"evaluate", [exception toString], nil);
};
JSValue *ret = [ctx evaluateScript:code];
if (!hasError) {
NSString *result = [ret toString];
if (result == nil) {
reject(@"evaluate", @"Error in string coercion", nil);
} else {
resolve(result);
}
}
}
@end

View File

@@ -112,7 +112,9 @@
"disabled": "L'invio di messaggi in chat è disabilitato.",
"enter": "Entra nella conversazione",
"error": "Errore: il tuo messaggio non è stato inviato. Motivo: {{error}}",
"everyone": "Tutti",
"fieldPlaceHolder": "Scrivi qui il tuo messaggio",
"guestsChatIndicator": "(ospite)",
"lobbyChatMessageTo": "Messaggio a {{recipient}} in sala d'attesa",
"message": "Messaggio",
"messageAccessibleTitle": "{{user}} dice:",
@@ -154,7 +156,7 @@
"installExtensionText": "Installa un'estensione per integrare Google Calendar e Office 365"
},
"closedCaptionsTab": {
"emptyState": "Il contenuto dei sottotitoli sarà disponibile una volta che l'organizzatore lo attiva",
"emptyState": "Il contenuto dei sottotitoli sarà disponibile una volta che l'organizzatore lo attiverà",
"startClosedCaptionsButton": "Attiva sottotitoli"
},
"connectingOverlay": {
@@ -300,6 +302,12 @@
"alreadySharedVideoTitle": "È permessa una sola condivisione video alla volta",
"applicationWindow": "Finestra dell'applicazione",
"authenticationRequired": "Richiesta autenticazione",
"cameraCaptureDialog": {
"description": "Scatta ed invia una foto usando la fotocamera del telefono",
"ok": "Apri la fotocamera",
"reject": "Non adesso",
"title": "Scatta una foto"
},
"cameraConstraintFailedError": "La tua videocamera non soddisfa alcuni dei requisiti.",
"cameraNotFoundError": "Videocamera non trovata.",
"cameraNotSendingData": "Impossibile accedere alla videocamera. Controlla che non sia in uso in un'altra applicazione, seleziona un altro dispositivo dalle impostazioni o prova a ricaricare l'applicazione.",
@@ -323,7 +331,7 @@
"contactSupport": "Contatta il supporto",
"copied": "Copiato",
"copy": "Copia",
"demoteParticipantDialog": "Sei sicuro di voler far diventare questo partecipante uno spettatore?",
"demoteParticipantDialog": "Vuoi far diventare questo partecipante uno spettatore?",
"demoteParticipantTitle": "Fai diventare spettatore",
"dismiss": "Scarta",
"displayNameRequired": "Ciao, qual è il tuo nome?",
@@ -338,8 +346,8 @@
"error": "Errore",
"errorRoomCreationRestriction": "Hai provato ad accedere alla riunione troppo presto, torna tra un po'.",
"gracefulShutdown": "Il nostro servizio è al momento inattivo per manutenzione. Si prega di riprovare più tardi.",
"grantModeratorDialog": "Sei sicuro di voler rendere organizzatore questo partecipante?",
"grantModeratorTitle": "Fai diventare organizzatore",
"grantModeratorDialog": "Vuoi rendere relatore questo partecipante?",
"grantModeratorTitle": "Fai diventare relatore",
"hide": "Nascondi",
"hideShareAudioHelper": "Non mostrare più questa finestra",
"incorrectPassword": "Nome utente o password errati",
@@ -348,7 +356,7 @@
"internalErrorTitle": "Errore interno",
"kickMessage": "Puoi contattare {{participantDisplayName}} per maggiori dettagli.",
"kickParticipantButton": "Espelli",
"kickParticipantDialog": "Sei sicuro di voler espellere questo partecipante?",
"kickParticipantDialog": "Vuoi espellere questo partecipante?",
"kickParticipantTitle": "Espellere questo partecipante?",
"kickSystemTitle": "Oh! Sei stato espulso dalla riunione",
"kickTitle": "Oh! {{participantDisplayName}} ti ha espulso dalla riunione.",
@@ -362,8 +370,8 @@
"lockRoom": "Aggiungi una $t(lockRoomPassword) alla riunione",
"lockTitle": "Blocco fallito",
"login": "Accesso",
"loginQuestion": "Sei sicuro di voler fare l'accesso e abbandonare la riunione?",
"logoutQuestion": "Sei sicuro di volerti disconnettere e abbandonare la riunione?",
"loginQuestion": "Vuoi fare l'accesso e abbandonare la riunione?",
"logoutQuestion": "Vuoi disconnetterti e abbandonare la riunione?",
"logoutTitle": "Disconnessione",
"maxUsersLimitReached": "È stato raggiunto il numero massimo di partecipanti. La riunione è al completo. Contatta l'organizzatore o riprova più tardi!",
"maxUsersLimitReachedTitle": "Raggiunto limite massimo partecipanti",
@@ -375,27 +383,39 @@
"micTimeoutError": "Impossibile avviare la sorgente audio. Tempo di attesa scaduto.",
"micUnknownError": "Impossibile usare il microfono per un motivo sconosciuto.",
"moderationAudioLabel": "Consenti ai partecipanti di attivare il microfono",
"moderationDesktopLabel": "Consenti ai partecipanti di condividere lo schermo",
"moderationVideoLabel": "Consenti ai partecipanti di attivare la videocamera",
"muteEveryoneDialog": "I partecipanti possono attivare il microfono in qualsiasi momento.",
"muteEveryoneDialogModerationOn": "I partecipanti possono chiedere di parlare in qualsiasi momento.",
"muteEveryoneElseDialog": "Una volta spenti i microfoni non potrai riattivarli, ma ogni partecipante potrà farlo da sé in qualsiasi momento.",
"muteEveryoneElseTitle": "Spegnere il microfono a tutti, tranne che a {{whom}}?",
"muteEveryoneElsesDesktopDialog": "Una volta interrotta la condivisione dello schermo non potrai riattivarla, ma ogni partecipante potrà farlo da sé in qualsiasi momento.",
"muteEveryoneElsesDesktopTitle": "Interrompere la condivisione dello schermo a tutti, tranne che a {{whom}}?",
"muteEveryoneElsesVideoDialog": "Una volta spente le videocamere non potrai riaccenderle, ma ogni partecipante potrà farlo da sé in qualsiasi momento.",
"muteEveryoneElsesVideoTitle": "Spegnere la videocamera a tutti, tranne che a {{whom}}?",
"muteEveryoneSelf": "tu",
"muteEveryoneStartMuted": "Tutti iniziano a microfono spento da adesso in avanti",
"muteEveryoneTitle": "Spegnere il microfono a tutti?",
"muteEveryonesDesktopDialog": "I partecipanti potranno condividere lo schermo in qualsiasi momento.",
"muteEveryonesDesktopDialogModerationOn": "I partecipanti possono inviare una richiesta per condividere lo schermo in ogni momento.",
"muteEveryonesDesktopTitle": "Interrompere la condivisione dello schermo a tutti?",
"muteEveryonesVideoDialog": "Ogni partecipante potrà riavviare il video da sé in qualsiasi momento.",
"muteEveryonesVideoDialogModerationOn": "I partecipanti possono chiedere di attivare il video in qualsiasi momento.",
"muteEveryonesVideoDialogOk": "Spegni",
"muteEveryonesVideoTitle": "Spegnere la videocamera a tutti?",
"muteParticipantBody": "Non potrai riattivare il loro microfono, ma loro potranno farlo in qualsiasi momento.",
"muteParticipantButton": "Silenzia",
"muteParticipantsDesktopBody": "Non potrai riavviare la loro condivisione dello schermo, ma loro potranno farlo in qualsiasi momento.",
"muteParticipantsDesktopBodyModerationOn": "Non potrai riavviare la loro condivisione dello schermo e nemmeno loro potranno.",
"muteParticipantsDesktopButton": "Interrompi la condivisione dello schermo",
"muteParticipantsDesktopDialog": "Vuoi interrompere la condivisione dello schermo di questo partecipante? Non potrai riattivarla, ma lui potrà farlo in qualsiasi momento.",
"muteParticipantsDesktopDialogModerationOn": "Vuoi interrompere la condivisione dello schermo di questo partecipante? Non potrai riattivarla e nemmeno loro potranno.",
"muteParticipantsDesktopTitle": "Disattivare la condivisione dello schermo di questo partecipante?",
"muteParticipantsVideoBody": "Non potrai riattivare le videocamere, ma loro potranno farlo in qualsiasi momento.",
"muteParticipantsVideoBodyModerationOn": "Non potrai riattivare le videocamere e nemmeno loro potranno.",
"muteParticipantsVideoButton": "Spegni videocamere",
"muteParticipantsVideoDialog": "Sei sicuro di voler spegnere la videocamera di questo partecipante? Non potrai riattivarla, ma lui potrà farlo in qualsiasi momento.",
"muteParticipantsVideoDialogModerationOn": "Sei sicuro di voler spegnere la videocamera di questo partecipante? Non potrai riattivarla e nemmeno lui potrà farlo.",
"muteParticipantsVideoDialog": "Vuoi spegnere la videocamera di questo partecipante? Non potrai riattivarla, ma lui potrà farlo in qualsiasi momento.",
"muteParticipantsVideoDialogModerationOn": "Vuoi spegnere la videocamera di questo partecipante? Non potrai riattivarla e nemmeno lui potrà farlo.",
"muteParticipantsVideoTitle": "Spegnere la videocamera di questo partecipante?",
"noDropboxToken": "Token Dropbox non valido",
"password": "Password",
@@ -424,7 +444,7 @@
"remoteControlTitle": "Connessione desktop remoto",
"remoteUserControls": "Controlli dell'utente remoto {{username}}",
"removePassword": "Rimuovi la $t(lockRoomPassword)",
"removeSharedVideoMsg": "Sei sicuro di voler rimuovere il tuo video condiviso?",
"removeSharedVideoMsg": "Vuoi rimuovere il tuo video condiviso?",
"removeSharedVideoTitle": "Rimuovi video condiviso",
"renameBreakoutRoomLabel": "Nome della stanza",
"renameBreakoutRoomTitle": "Rinomina stanza",
@@ -477,8 +497,8 @@
"startRemoteControlErrorMessage": "Si è verificato un errore nel tentativo di avviare la sessione di controllo remoto!",
"stopLiveStreaming": "Ferma la diretta",
"stopRecording": "Ferma registrazione",
"stopRecordingWarning": "Sei sicuro di voler interrompere la registrazione?",
"stopStreamingWarning": "Sei sicuro di voler interrompere la diretta?",
"stopRecordingWarning": "Vuoi interrompere la registrazione?",
"stopStreamingWarning": "Vuoi interrompere la diretta?",
"streamKey": "Chiave della diretta",
"thankYou": "Grazie per aver usato {{appName}}!",
"token": "token",
@@ -547,11 +567,15 @@
"downloadFailedDescription": "Si prega di riprovare.",
"downloadFailedTitle": "Download non riuscito",
"downloadFile": "Download",
"downloadStarted": "Download del file iniziato",
"dragAndDrop": "Trascina e rilascia i file qui o da qualsiasi altra parte nella schermata",
"fileAlreadyUploaded": "Questo file è già stato caricato nella riunione.",
"fileTooLargeDescription": "Assicurati che il file non superi {{ maxFileSize }}.",
"fileTooLargeTitle": "Il file selezionato è troppo grande",
"fileUploadProgress": "Caricamento del file in corso",
"fileUploadedSuccessfully": "Il file è stato caricato con successo",
"removeFile": "Rimuovi",
"removeFileSuccess": "File rimosso con successo",
"uploadFailedDescription": "Si prega di riprovare.",
"uploadFailedTitle": "Caricamento non riuscito",
"uploadFile": "Condividi file"
@@ -683,7 +707,7 @@
"signInCTA": "Accedi o inserisci la tua chiave della diretta su YouTube.",
"signOut": "Disconnetti",
"signedInAs": "Hai effettuato l'accesso come:",
"start": "Inizia una diretta",
"start": "Avvia una diretta",
"streamIdHelp": "Cos'è questo?",
"title": "Diretta",
"unavailableTitle": "La diretta non è disponibile",
@@ -744,9 +768,9 @@
"engaged": "Registrazione avviata.",
"finished": "La registrazione della sessione {{token}} è terminata. Invia il file della registrazione all'organizzatore.",
"finishedModerator": "La registrazione della sessione {{token}} è terminata. La registrazione della traccia è stata salvata. Chiedi ai partecipanti di inviare le loro registrazioni.",
"notModerator": "Non sei un organizzatore. Non puoi avviare o interrompere la registrazione."
"notModerator": "Non sei un relatore. Non puoi avviare o interrompere la registrazione."
},
"moderator": "Organizzatore",
"moderator": "Relatore",
"no": "No",
"participant": "Partecipante",
"participantStats": "Statistiche partecipante",
@@ -767,8 +791,9 @@
"me": "io",
"notify": {
"OldElectronAPPTitle": "Falla di sicurezza!",
"allowAll": "Consenti tutto",
"allowAudio": "Consenti l'audio",
"allowBoth": "Entrambi",
"allowDesktop": "Consenti la condivisione dello schermo",
"allowVideo": "Consenti il video",
"allowedUnmute": "Puoi accendere il microfono, avviare la videocamera o condividere il tuo schermo.",
"audioUnmuteBlockedDescription": "L'accensione dei microfoni è stata temporaneamente bloccata per i limiti del sistema.",
@@ -782,6 +807,7 @@
"dataChannelClosedDescription": "Il canale bridge è inattivo, quindi la qualità video potrebbe essere limitata all'impostazione più bassa.",
"dataChannelClosedDescriptionWithAudio": "Il canale bridge è inattivo, quindi potrebbero esserci interruzioni di audio e video",
"dataChannelClosedWithAudio": "La qualità audio e video potrebbe essere scarsa",
"desktopMutedRemotelyTitle": "La tua condivisione dello schermo è stata interrotta da {{participantDisplayName}}",
"disabledIframe": "L'incorporamento serve solo come dimostrazione, quindi questa chiamata si disconnetterà tra {{timeout}} minuti.",
"disabledIframeSecondaryNative": "L'incorporamento {{domain}} serve solo come dimostrazione, quindi questa chiamata si disconnetterà tra {{timeout}} minuti.",
"disabledIframeSecondaryWeb": "L'incorporamento {{domain}} serve solo come dimostrazione, quindi questa chiamata si disconnetterà tra {{timeout}} minuti. Si prega di usare <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> per la produzione di riunioni incorporate!",
@@ -792,7 +818,7 @@
"focusFail": "{{component}} non disponibile - nuovo tentativo tra {{ms}} sec",
"gifsMenu": "GIPHY",
"groupTitle": "Notifiche",
"hostAskedUnmute": "L'organizzatore ti chiede di intervenire.",
"hostAskedUnmute": "Il relatore ti chiede di intervenire.",
"invalidTenant": "Nome non valido",
"invalidTenantHyphenDescription": "Il nome che hai scelto non è valido (inizia o finisce con '-').",
"invalidTenantLengthDescription": "Il nome che hai scelto è troppo lungo.",
@@ -814,17 +840,17 @@
"localRecordingStopped": "{{name}} ha smesso di registrare.",
"me": "Io",
"moderationInEffectCSDescription": "Alza la mano, se vuoi condividere lo schermo.",
"moderationInEffectCSTitle": "La condivisione schermo è stata bloccata dall'organizzatore",
"moderationInEffectCSTitle": "La condivisione schermo è stata bloccata dal relatore",
"moderationInEffectDescription": "Alza la mano, se vuoi prendere la parola.",
"moderationInEffectTitle": "Il tuo microfono è stato spento dall'organizzatore",
"moderationInEffectTitle": "Il tuo microfono è stato spento dal relatore",
"moderationInEffectVideoDescription": "Alza la mano, se vuoi avviare la tua videocamera.",
"moderationInEffectVideoTitle": "La tua videocamera è stata spenta dall'organizzatore",
"moderationRequestFromModerator": "L'organizzatore vorrebbe che tu accendessi il microfono",
"moderationInEffectVideoTitle": "La tua videocamera è stata spenta dal relatore",
"moderationRequestFromModerator": "Il relatore vorrebbe che tu accendessi il microfono",
"moderationRequestFromParticipant": "Vuole parlare",
"moderationStartedTitle": "Moderazione in corso",
"moderationStoppedTitle": "Moderazione interrotta",
"moderationToggleDescription": "da {{participantDisplayName}}",
"moderator": "Ora sei un organizzatore!",
"moderator": "Ora sei un relatore!",
"muted": "Hai iniziato la conversazione con il microfono disattivato.",
"mutedRemotelyDescription": "Puoi sempre attivare il microfono quando vuoi parlare. Spegni il microfono quando hai finito, per evitare rumori di fondo nella riunione.",
"mutedRemotelyTitle": "{{participantDisplayName}} ti ha spento il microfono",
@@ -839,6 +865,7 @@
"oldElectronClientDescription1": "Sembra che tu stia usando una versione obsoleta del client Jitsi Meet, che ha vulnerabilità note. Assicurati di aggiornarlo alla nostra ",
"oldElectronClientDescription2": "ultima versione",
"oldElectronClientDescription3": " ora!",
"openChat": "Apri chat",
"participantWantsToJoin": "Vuole unirsi alla riunione",
"participantsWantToJoin": "Vogliono unirsi alla riunione",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) è stata rimossa da un altro partecipante",
@@ -862,6 +889,7 @@
"suggestRecordingDescription": "Vuoi avviare la registrazione?",
"suggestRecordingTitle": "Registra questa riunione",
"unmute": "Accendi il microfono",
"unmuteScreen": "Iniziata la condivisione dello schermo",
"unmuteVideo": "Accendi la videocamera",
"videoMutedRemotelyDescription": "Puoi riaccenderla in qualsiasi momento.",
"videoMutedRemotelyTitle": "{{participantDisplayName}} ti ha spento la videocamera",
@@ -881,11 +909,14 @@
"admit": "Ammetti",
"admitAll": "Ammetti tutti",
"allow": "Permetti ai partecipanti di:",
"allowDesktop": "Consenti la condivisione dello schermo",
"allowVideo": "Permetti videocamere",
"askDesktop": "Chiedi di condividere lo schermo",
"askUnmute": "Chiedi di accendere il microfono",
"audioModeration": "Riattivare il microfono",
"blockEveryoneMicCamera": "Blocca microfono e videocamera a tutti",
"breakoutRooms": "Stanze",
"desktopModeration": "Avvia la condivisione dello schermo",
"goLive": "Vai alla diretta",
"invite": "Invita partecipanti",
"lowerAllHands": "Abbassa tutte le mani",
@@ -897,6 +928,8 @@
"muteAll": "Silenzia tutti",
"muteEveryoneElse": "Silenzia tutti gli altri",
"reject": "Respingi",
"stopDesktop": "Interrompi la condivisione dello schermo",
"stopEveryonesDesktop": "Interrompi la condivisione dello schermo agli altri",
"stopEveryonesVideo": "Ferma il video di tutti",
"stopVideo": "Ferma il video",
"unblockEveryoneMicCamera": "Sblocca microfono e videocamera a tutti",
@@ -906,6 +939,7 @@
"headings": {
"lobby": "Sala d'attesa ({{count}})",
"participantsList": "Partecipanti alla riunione ({{count}})",
"viewerRequests": "Richieste spettatori ({{count}})",
"visitorInQueue": " ({{count}} in attesa)",
"visitorRequests": " ({{count}} richiesta/e)",
"visitors": "Spettatori {{count}}",
@@ -1073,7 +1107,7 @@
"fileSharingdescription": "Condividi la registrazione con i partecipanti alla riunione",
"highlight": "Evidenzia",
"highlightMoment": "Evidenzia momento",
"highlightMomentDisabled": "Puoi evidenziare dei momenti quando inizia la registrazione",
"highlightMomentDisabled": "Puoi evidenziare i momenti in cui inizia la registrazione",
"highlightMomentSuccess": "Momento evidenziato",
"highlightMomentSucessDescription": "Il tuo momento evidenziato sarà aggiunto al riepilogo della riunione.",
"inProgress": "Registrazione o diretta in corso",
@@ -1084,7 +1118,7 @@
"localRecordingNoVideo": "Il video non sta venendo registrato",
"localRecordingStartWarning": "Assicurati di interrompere la registrazione prima di uscire dalla riunione, altrimenti la registrazione non verrà salvata.",
"localRecordingStartWarningTitle": "Interrompi la registrazione per salvarla",
"localRecordingVideoStop": "Interrompere il video fermerà anche la registrazione. Sei sicuro di voler continuare?",
"localRecordingVideoStop": "Interrompere il video fermerà anche la registrazione. Vuoi continuare?",
"localRecordingVideoWarning": "Per registrare il video, deve essere già avviato prima dell'inizio della registrazione",
"localRecordingWarning": "Assicurati di aver selezionato la scheda corrente, per registrare gli audio e video corretti.",
"loggedIn": "Accesso effettuato come {{userName}}",
@@ -1153,8 +1187,8 @@
"loggedIn": "Connesso come {{name}}",
"maxStageParticipants": "Numero massimo di partecipanti che possono essere messi in evidenza nella schermata principale",
"microphones": "Microfoni",
"moderator": "Organizzatore",
"moderatorOptions": "Opzioni organizzatore",
"moderator": "Relatore",
"moderatorOptions": "Opzioni relatore",
"more": "Generali",
"name": "Nome",
"noDevice": "Nessuno",
@@ -1190,7 +1224,7 @@
"conferenceSection": "Riunione",
"disableCallIntegration": "Disattiva l'integrazione nativa delle chiamate",
"disableCrashReporting": "Disattiva la diagnostica dei crash",
"disableCrashReportingWarning": "Sei sicuro di voler disattivare la diagnostica dei crash? Quest'impostazione verrà applicata al prossimo avvio dell'app.",
"disableCrashReportingWarning": "Vuoi disattivare la diagnostica dei crash? Quest'impostazione verrà applicata al prossimo avvio dell'app.",
"disableP2P": "Disattiva la modalità Peer-to-Peer",
"displayName": "Nome visualizzato",
"displayNamePlaceholderText": "Es: Mario Rossi",
@@ -1207,8 +1241,8 @@
"serverURL": "URL del server",
"showAdvanced": "Mostra impostazioni avanzate",
"startCarModeInLowBandwidthMode": "Avvia modalità auto in modalità larghezza di banda limitata",
"startWithAudioMuted": "Inizia con audio disattivato",
"startWithVideoMuted": "Inizia con video disattivato",
"startWithAudioMuted": "Avvia con audio disattivato",
"startWithVideoMuted": "Avvia con video disattivato",
"terms": "Termini",
"version": "Versione"
},
@@ -1283,7 +1317,7 @@
"feedback": "Lascia un feedback",
"fullScreen": "Attiva modalità a schermo intero",
"giphy": "Mostra menu GIPHY",
"grantModerator": "Concedi permessi di organizzatore",
"grantModerator": "Concedi permessi da relatore",
"hangup": "Lascia la riunione",
"heading": "Barra degli strumenti",
"help": "Aiuto",
@@ -1346,6 +1380,20 @@
"videounmute": "Accendi videocamera"
},
"addPeople": "Aggiungi partecipanti alla chiamata",
"advancedAudioSettings": {
"aec": {
"label": "Cancellazione dell'eco"
},
"agc": {
"label": "Controllo del guadagno automatico"
},
"ns": {
"label": "Cancellazione del rumore"
},
"stereo": {
"label": "Stereo"
}
},
"audioOnlyOff": "Disabilita modalità larghezza di banda limitata",
"audioOnlyOn": "Abilita modalità larghezza di banda limitata",
"audioRoute": "Scegli il dispositivo audio",
@@ -1412,11 +1460,12 @@
"profile": "Modifica profilo",
"raiseHand": "Alza la mano",
"raiseYourHand": "Alza la mano",
"reactionBoo": "Invia buu",
"reactionClap": "Invia applauso",
"reactionHeart": "Invia cuore",
"reactionLaugh": "Invia risata",
"reactionLike": "Invia mi piace",
"reactionBoo": "Invia Buu",
"reactionClap": "Invia Applauso",
"reactionHeart": "Invia Cuore",
"reactionLaugh": "Invia Risata",
"reactionLike": "Invia Mi piace",
"reactionLove": "Invia Love",
"reactionSilence": "Invia senza parole",
"reactionSurprised": "Invia a bocca aperta",
"reactions": "Reazioni",
@@ -1502,15 +1551,17 @@
"connectionInfo": "Informazioni connessione",
"demote": "Fai diventare spettatore",
"domute": "Disattiva microfono",
"domuteDesktop": "Interrompi la condivisione dello schermo",
"domuteDesktopOfOthers": "Interrompi la condivisione dello schermo agli altri",
"domuteOthers": "Disattiva microfono a tutti gli altri",
"domuteVideo": "Disattiva videocamera",
"domuteVideoOfOthers": "Disattiva videocamera a tutti gli altri",
"flip": "Specchia",
"grantModerator": "Concedi permessi da organizzatore",
"grantModerator": "Concedi permessi da relatore",
"hideSelfView": "Nascondi la tua immagine",
"kick": "Espelli",
"mirrorVideo": "Specchia il tuo video",
"moderator": "Organizzatore",
"moderator": "Relatore",
"mute": "Il partecipante ha il microfono spento",
"muted": "Microfono spento",
"pinToStage": "Metti in primo piano",
@@ -1557,7 +1608,7 @@
"description": "Adesso sei uno spettatore in questa riunione.",
"raiseHand": "Alza la mano",
"title": "Ingresso nella riunione in corso",
"wishToSpeak": "Se vuoi parlare, si prega di alzare la mano sotto e aspettare l'autorizzazione dell'organizzatore."
"wishToSpeak": "Se vuoi parlare, si prega di alzare la mano sotto e aspettare l'autorizzazione del relatore."
},
"labelTooltip": "Numero di spettatori: {{count}}",
"notification": {
@@ -1618,7 +1669,7 @@
"roomnameHint": "Inserisci il nome o l'URL della riunione a cui vuoi accedere. Puoi anche inventarti un nome, assicurati solo che le persone con cui vuoi collegarti lo conoscano, così che possano inserire lo stesso nome.",
"sendFeedback": "Invia feedback",
"settings": "Impostazioni",
"startMeeting": "Inizia riunione",
"startMeeting": "Avvia riunione",
"terms": "Termini di utilizzo",
"title": "Il sistema di videoconferenza sicuro, funzionale e completamente gratuito.",
"upcomingMeetings": "Prossime riunioni"

View File

@@ -112,7 +112,9 @@
"disabled": "Tērzēšanas ziņojumu sūtīšana ir atspējota.",
"enter": "Ienākt istabā",
"error": "Kļūda: Jūsu ziņa netika nosūtīta. Cēlonis: {{error}}",
"everyone": "Visi",
"fieldPlaceHolder": "Rakstiet ziņu šeit",
"guestsChatIndicator": "(viesis)",
"lobbyChatMessageTo": "Vestibila tērzēšanas ziņa adresātam {{recipient}}",
"message": "Ziņa",
"messageAccessibleTitle": "{{user}} saka:",
@@ -565,11 +567,15 @@
"downloadFailedDescription": "Lūdzu, mēģiniet vēlreiz.",
"downloadFailedTitle": "Lejuplādes kļūda",
"downloadFile": "Lejuplādēt",
"downloadStarted": "Sākta faila lejuplāde",
"dragAndDrop": "Velciet un palaidiet failus šeit, vai jebkurā ekrāna vietā",
"fileAlreadyUploaded": "Fails jau ir augšupielādēts šajā sanāksmē.",
"fileAlreadyUploaded": "Fails jau ir augšuplādēts šajā sanāksmē.",
"fileTooLargeDescription": "Lūdzu, pārliecinieties, vai faila lielums nepārsniedz {{ maxFileSize }}.",
"fileTooLargeTitle": "Izvēlētais fails ir pārāk liels",
"fileUploadProgress": "Faila augšuplādes gaita",
"fileUploadedSuccessfully": "Fails veiksmīgi augšuplādēts",
"removeFile": "Noņemt",
"removeFileSuccess": "Fails veiksmīgi noņemts",
"uploadFailedDescription": "Lūdzu, mēģiniet vēlreiz.",
"uploadFailedTitle": "Augšuplādes kļūda",
"uploadFile": "Kopīgot failu"

View File

@@ -109,8 +109,10 @@
}
},
"chat": {
"disabled": "O envio de mensagens de chat está desativado.",
"enter": "Entrar na sala",
"error": "Erro: a sua mensagem não foi enviada. Motivo: {{error}}",
"everyone": "Todos",
"fieldPlaceHolder": "Aa",
"lobbyChatMessageTo": "Mensagem de chat na sala de espera para {{recipient}}",
"message": "Mensagem",
@@ -122,7 +124,10 @@
"nickname": {
"popover": "Escolha um apelido",
"title": "Introduza um apelido para usar o chat",
"titleWithPolls": "Introduza um apelido para usar o chat e as sondagens"
"titleWithCC": "Insira um apelido para usar o chat e as legendas ocultas",
"titleWithPolls": "Digite um apelido para usar o chat e as sondagens",
"titleWithPollsAndCC": "Insira um apelido para utilizar o chat, as sondagens e as legendas ocultas",
"titleWithPollsAndCCAndFileSharing": "Insira um apelido para utilizar o chat, as sondagens, as legendas e os ficheiros"
},
"noMessagesMessage": "Ainda não há mensagens na reunião. Comece aqui uma conversa!",
"privateNotice": "Mensagem privada para {{recipient}}",
@@ -131,10 +136,15 @@
"systemDisplayName": "Sistema",
"tabs": {
"chat": "Chat",
"closedCaptions": "LO",
"fileSharing": "Ficheiros",
"polls": "Sondagens"
},
"title": "Chat",
"titleWithPolls": "Chat e Sondagens",
"titleWithCC": "LO",
"titleWithFeatures": "Chat e",
"titleWithFileSharing": "Ficheiros",
"titleWithPolls": "Sondagens",
"you": "você"
},
"chromeExtensionBanner": {
@@ -144,6 +154,10 @@
"dontShowAgain": "Não me mostre isto outra vez",
"installExtensionText": "Instalar a extensão para a integração Google Calendar e Office 365"
},
"closedCaptionsTab": {
"emptyState": "O conteúdo das legendas ocultas estará disponível assim que um moderador iniciar a sessão.",
"startClosedCaptionsButton": "Iniciar legendas ocultas"
},
"connectingOverlay": {
"joiningRoom": "A ligá-lo à reunião…"
},
@@ -263,6 +277,8 @@
"Remove": "Remover",
"Share": "Partilhar",
"Submit": "Submeter",
"Understand": "Entendo, mantenha-me em silêncio por enquanto.",
"UnderstandAndUnmute": "Entendo, por favor, desative o silêncio.",
"WaitForHostMsg": "A conferência ainda não começou porque ainda não chegaram moderadores. Se quiser ser um moderador, inicie a sessão. Caso contrário, aguarde.",
"WaitForHostNoAuthMsg": "A conferência ainda não começou porque ainda não chegaram os moderadores. Por favor, aguarde.",
"WaitingForHostButton": "Esperar pelo moderador",
@@ -285,6 +301,12 @@
"alreadySharedVideoTitle": "Só é permitido um vídeo partilhado de cada vez",
"applicationWindow": "Janela de aplicação",
"authenticationRequired": "Autenticação necessária",
"cameraCaptureDialog": {
"description": "Tire e envie uma foto usando a câmara do seu telemóvel",
"ok": "Ligar a câmara",
"reject": "Agora não",
"title": "Tire uma foto"
},
"cameraConstraintFailedError": "A sua câmara não satisfaz algumas das restrições exigidas.",
"cameraNotFoundError": "A câmara não foi encontrada.",
"cameraNotSendingData": "Não podemos aceder à sua câmara. Verifique se outra aplicação está a utilizar este dispositivo, seleccione outro dispositivo do menu de definições ou tente recarregar a aplicação.",
@@ -299,6 +321,7 @@
"conferenceReloadMsg": "Estamos a tentar resolver isto. Reconexão em {{seconds}} seg…",
"conferenceReloadTitle": "Infelizmente, algo correu mal.",
"confirm": "Confirme",
"confirmBack": "Voltar",
"confirmNo": "Não",
"confirmYes": "Sim",
"connectError": "Oops! Algo correu mal e não conseguimos estabelecer uma ligação com a conferência.",
@@ -307,8 +330,8 @@
"contactSupport": "Contacte o suporte",
"copied": "Copiado",
"copy": "Cópia",
"demoteParticipantDialog": "Tem a certeza de que pretende passar este participante para visitante?",
"demoteParticipantTitle": "Passar a visitante",
"demoteParticipantDialog": "Tem a certeza de que deseja mover este participante para espectador?",
"demoteParticipantTitle": "Mover para espectador",
"dismiss": "Dispensar",
"displayNameRequired": "Olá! Qual é o seu nome?",
"done": "Feito",
@@ -334,7 +357,9 @@
"kickParticipantButton": "Expulsar",
"kickParticipantDialog": "Tem a certeza que quer expulsar este participante?",
"kickParticipantTitle": "Expulsar este participante?",
"kickSystemTitle": "Ai! Foste expulso da reunião.",
"kickTitle": "Ai! {{participantDisplayName}} expulsou-o da reunião",
"learnMore": "saiba mais",
"linkMeeting": "Link da reunião",
"linkMeetingTitle": "Link da reunião à Força de Vendas",
"liveStreaming": "Transmissão em direto",
@@ -356,23 +381,35 @@
"micPermissionDeniedError": "Não concedeu autorização para utilizar o seu microfone. Ainda pode participar na conferência, mas outros não o ouvirão. Use o botão da câmara na barra de endereço para corrigir isto.",
"micTimeoutError": "Não foi possível iniciar a fonte de áudio. Tempo limite expirado!",
"micUnknownError": "Não pode usar microfone por uma razão desconhecida.",
"moderationAudioLabel": "Permitir aos participantes ligar o som",
"moderationVideoLabel": "Permitir aos participantes ligar a câmara",
"moderationAudioLabel": "Permitir aos não moderadores ligar o som",
"moderationDesktopLabel": "Permitir que não moderadores partilhem o seu ecrã",
"moderationVideoLabel": "Permitir que não moderadores iniciem os seus vídeos",
"muteEveryoneDialog": "Os participantes podem ligar o som a qualquer momento.",
"muteEveryoneDialogModerationOn": "Os participantes podem enviar um pedido para falar a qualquer momento.",
"muteEveryoneElseDialog": "Uma vez silenciados, não poderá reativá-los, mas eles podem ligar o microfone a qualquer momento.",
"muteEveryoneElseTitle": "Silenciar todos excepto {{whom}}?",
"muteEveryoneElsesDesktopDialog": "Depois que o compartilhamento for interrompido, não será possível reiniciá-lo, mas eles poderão fazê-lo a qualquer momento.",
"muteEveryoneElsesDesktopTitle": "SInterromper a partilha de ecrã de todos, exceto {{whom}}?",
"muteEveryoneElsesVideoDialog": "Quando a câmara for desligada, não poderá voltar a ligá-la, mas eles podem voltar a ligá-la em qualquer momento.",
"muteEveryoneElsesVideoTitle": "Parar o vídeo de todos excepto {{whom}}?",
"muteEveryoneSelf": "você mesmo",
"muteEveryoneStartMuted": "A partir de agora, toda a gente começa a ficar calada",
"muteEveryoneTitle": "Silenciar toda a gente?",
"muteEveryonesDesktopDialog": "Os participantes podem partilhar o seu ecrã a qualquer momento.",
"muteEveryonesDesktopDialogModerationOn": "Os participantes podem enviar um pedido para partilhar o seu ecrã a qualquer momento.",
"muteEveryonesDesktopTitle": "Interromper a partilha de ecrã de todos?",
"muteEveryonesVideoDialog": "Os participantes podem ligar a sua câmara a qualquer momento.",
"muteEveryonesVideoDialogModerationOn": "Os participantes podem enviar um pedido para ligar a sua câmara a qualquer momento.",
"muteEveryonesVideoDialogOk": "Desativar",
"muteEveryonesVideoTitle": "Desligar a câmara de todos?",
"muteParticipantBody": "Não poderá reativá-los, mas eles podem reativar-se a qualquer momento.",
"muteParticipantButton": "Silenciar",
"muteParticipantsDesktopBody": "Não poderá iniciar a partilha de ecrã deles, mas eles podem fazê-lo a qualquer momento.",
"muteParticipantsDesktopBodyModerationOn": "Não será possível iniciar a partilha de ecrã nem para si nem para eles.",
"muteParticipantsDesktopButton": "Parar a partilha de ecrã",
"muteParticipantsDesktopDialog": "Tem a certeza de que deseja desativar a partilha de ecrã deste participante? Não será possível reiniciá-la, mas ele poderá fazê-lo a qualquer momento.",
"muteParticipantsDesktopDialogModerationOn": "Tem a certeza de que deseja desativar a partilha de ecrã deste participante? Não será possível reativar o ecrã, nem para si nem para ele.",
"muteParticipantsDesktopTitle": "Desativar a partilha de ecrã deste participante?",
"muteParticipantsVideoBody": "Não poderá voltar a ligar a câmara, mas eles podem voltar a ligá-la a qualquer momento.",
"muteParticipantsVideoBodyModerationOn": "Não será capaz de voltar a ligar a câmara e eles também não.",
"muteParticipantsVideoButton": "Parar vídeo",
@@ -392,6 +429,10 @@
"recentlyUsedObjects": "Os seus objetos recentemente utilizados",
"recording": "A gravar",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Não possível enquanto a transmissão em direto estiver activa",
"recordingInProgressDescription": "Esta reunião está a ser gravada e analisada pela IA{{learnMore}}. O seu áudio e vídeo foram silenciados. Se optar por ativar o som, concorda em ser gravado.",
"recordingInProgressDescriptionFirstHalf": "Esta reunião está a ser gravada e analisada por IA.",
"recordingInProgressDescriptionSecondHalf": ". Your audio and video have been muted. If you choose to unmute, you consent to being recorded.",
"recordingInProgressTitle": "Gravação em andamento",
"rejoinNow": "Reingressar agora",
"remoteControlAllowedMessage": "{{user}} aceitou o seu pedido de controlo remoto!",
"remoteControlDeniedMessage": "{{user}} rejeitou o seu pedido de controlo remoto!",
@@ -439,7 +480,10 @@
"shareScreenWarningD2": "é necessário parar a partilha de áudio, iniciar a partilha de ecrã e verificar a opção \"partilhar áudio\".",
"shareScreenWarningH1": "Se quiser partilhar apenas o seu ecrã:",
"shareScreenWarningTitle": "Tem de parar a partilha de áudio antes de partilhar o seu ecrã",
"shareVideoLinkError": "Por favor, forneça um link correcto do vídeo.",
"shareVideoConfirmPlay": "Está prestes a abrir um site externo. Deseja continuar?",
"shareVideoConfirmPlayTitle": "{{name}} partilhou um vídeo consigo.",
"shareVideoLinkError": "Oops, este vídeo não pode ser reproduzido.",
"shareVideoLinkStopped": "O vídeo de {{name}} foi interrompido.",
"shareVideoTitle": "Partilhar vídeo",
"shareYourScreen": "Partilhe o seu ecrã",
"shareYourScreenDisabled": "Partilha de ecrã desactivada.",
@@ -518,6 +562,21 @@
"veryBad": "Muito má",
"veryGood": "Muito boa"
},
"fileSharing": {
"downloadFailedDescription": "Por favor, tente novamente.",
"downloadFailedTitle": "Falha no descarregar",
"downloadFile": "Descarregar",
"dragAndDrop": "Arraste e solte os ficheiros aqui ou em qualquer lugar do ecrã",
"fileAlreadyUploaded": "O ficheiro já foi carregado para esta reunião.",
"fileTooLargeDescription": "Certifique-se de que o ficheiro não exceda {{ maxFileSize }}.",
"fileTooLargeTitle": "O ficheiro selecionado é muito grande",
"fileUploadProgress": "Progresso do envio do ficheiro",
"fileUploadedSuccessfully": "Ficheiro carregado com sucesso",
"removeFile": "Remover",
"uploadFailedDescription": "Por favor, tente novamente.",
"uploadFailedTitle": "Falha ao carregar",
"uploadFile": "Partilhar ficheiro"
},
"filmstrip": {
"accessibilityLabel": {
"heading": "Miniaturas de vídeo"
@@ -638,6 +697,7 @@
"on": "Iniciada a transmissão em direto",
"onBy": "{{name}} iniciou a transmissão em direto",
"pending": "Início da transmissão em direto…",
"policyError": "Tentou iniciar uma transmissão ao vivo muito rapidamente. Por favor, tente novamente mais tarde!",
"serviceName": "Serviço de Transmissão em Direto",
"sessionAlreadyActive": "Esta sessão já está a ser gravada ou transmitida em direto.",
"signIn": "Iniciar sessão com o Google",
@@ -728,7 +788,10 @@
"me": "eu",
"notify": {
"OldElectronAPPTitle": "Vulnerabilidade de segurança!",
"allowAction": "Permitir",
"allowAll": "Permitir tudo",
"allowAudio": "Permitir áudio",
"allowDesktop": "Permitir partilha de ecrã",
"allowVideo": "Permitir vídeo",
"allowedUnmute": "Pode ligar o seu microfone, ligar a sua câmara ou partilhar o seu ecrã.",
"audioUnmuteBlockedDescription": "A operação de ligar o microfone foi temporariamente bloqueada devido aos limites do sistema.",
"audioUnmuteBlockedTitle": "Ligar microfone bloqueado!",
@@ -736,12 +799,15 @@
"connectedOneMember": "{{name}} entrou na reunião",
"connectedThreePlusMembers": "{{name}} e muitos outros entraram na reunião",
"connectedTwoMembers": "{{first}} e {{second}} entraram na reunião",
"connectionFailed": "Falha na ligação. Por favor, tente novamente mais tarde!",
"dataChannelClosed": "A qualidade do vídeo pode ser afetada",
"dataChannelClosedDescription": "O canal de ponte está em baixo e, por isso, a qualidade de vídeo pode estar limitada à sua definição mais baixa.",
"dataChannelClosedDescriptionWithAudio": "O canal de ponte está em baixo, pelo que podem ocorrer interrupções no áudio e no vídeo.",
"dataChannelClosedWithAudio": "A qualidade do áudio e do vídeo pode ser afetada",
"desktopMutedRemotelyTitle": "A partilha do seu ecrã foi interrompida por {{participantDisplayName}}",
"disabledIframe": "A incorporação destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos.",
"disabledIframeSecondary": "A incorporação de {{domain}} destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos. Por favor, use <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> para incorporação em produção!",
"disabledIframeSecondaryNative": "A incorporação de {{domain}} destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos.",
"disabledIframeSecondaryWeb": "A incorporação de {{domain}} destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos. Utilize <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> para incorporação em produção!",
"disconnected": "desconectado",
"displayNotifications": "Mostrar notificações para",
"dontRemindMe": "Não me lembre",
@@ -749,7 +815,10 @@
"focusFail": "{{component}} não disponĩvel - tente em {{ms}} seg.",
"gifsMenu": "GIPHY",
"groupTitle": "Notificações",
"hostAskedUnmute": "O moderador gostaria que você falasse",
"hostAskedUnmute": "O moderador gostaria que participasse.",
"invalidTenant": "Tenant inválido",
"invalidTenantHyphenDescription": "O tenant que está a utilizar é inválido (começa ou termina com '-').",
"invalidTenantLengthDescription": "O tenant que está a utilizar é demasiado longo.",
"invitedOneMember": "{{displayName}} foi convidado",
"invitedThreePlusMembers": "{{name}} e {{count}} outros foram convidados",
"invitedTwoMembers": "{{first}} e {{second}} foram convidados",
@@ -787,9 +856,9 @@
"newDeviceAudioTitle": "Novo dispositivo de áudio detetado",
"newDeviceCameraTitle": "Nova câmara detetada",
"nextToSpeak": "É o próximo na fila para falar",
"noiseSuppressionDesktopAudioDescription": "A supressão de ruído não pode ser ativada enquanto se partilha o áudio do ambiente de trabalho, por favor desative-o e tente novamente.",
"noiseSuppressionFailedTitle": "Falha ao iniciar a supressão de ruído",
"noiseSuppressionStereoDescription": "A supressão do ruído de áudio estéreo não é atualmente suportada.",
"noiseSuppressionDesktopAudioDescription": "A supressão de ruído extra não pode ser ativada enquanto estiver a partilhar o áudio do ambiente de trabalho. Desative-a e tente novamente.",
"noiseSuppressionFailedTitle": "Falha ao iniciar a supressão de ruído extra",
"noiseSuppressionStereoDescription": "Atualmente, a supressão extra de ruído não é suportada com áudio estéreo.",
"oldElectronClientDescription1": "Parece estar a utilizar uma versão antiga do cliente Jitsi Meet que tem vulnerabilidades de segurança conhecidas. Por favor, certifique-se de que actualiza a nossa ",
"oldElectronClientDescription2": "compilação mais recente",
"oldElectronClientDescription3": " agora!",
@@ -798,7 +867,7 @@
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removido por outro participante",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) definido por outro participante",
"raiseHandAction": "Levantar a mão",
"raisedHand": "Gostaria de falar.",
"raisedHand": "Gostaria de participar.",
"raisedHands": "{{participantName}} e mais {{raisedHands}} pessoas",
"reactionSounds": "Desactivar sons",
"reactionSoundsForAll": "Desativar sons para todos",
@@ -816,15 +885,17 @@
"suggestRecordingDescription": "Gostaria de iniciar uma gravação?",
"suggestRecordingTitle": "Gravar esta reunião",
"unmute": "Ligar microfone",
"unmuteScreen": "Iniciar partilha de ecrã",
"unmuteVideo": "Ligar câmara",
"videoMutedRemotelyDescription": "Pode sempre ligá-la novamente.",
"videoMutedRemotelyTitle": "A sua câmara foi desligada pelo {{participantDisplayName}}.",
"videoUnmuteBlockedDescription": "A operação de ligar a câmara e partilhar o ambiente de trabalho foi temporariamente bloqueada devido aos limites do sistema.",
"videoUnmuteBlockedTitle": "Está bloqueado ligar a câmara e partilhar o ambiente de trabalho!",
"viewLobby": "Ver sala de espera",
"viewParticipants": "Ver participantes",
"viewVisitors": "Ver visitantes",
"viewVisitors": "Visualizar espectadores",
"waitingParticipants": "{{waitingParticipants}} pessoas",
"waitingVisitors": "Visitantes em fila de espera: {{waitingVisitors}}",
"waitingVisitors": "Espectadores em fila de espera: {{waitingVisitors}}",
"waitingVisitorsTitle": "A reunião ainda não está em direto!",
"whiteboardLimitDescription": "Guarde o seu progresso, pois o limite de utilizadores será atingido em breve e o quadro branco será encerrado.",
"whiteboardLimitTitle": "Utilização do quadro branco"
@@ -833,14 +904,17 @@
"actions": {
"admit": "Aceitar",
"admitAll": "Aceitar todos",
"allow": "Permitir aos participantes:",
"allow": "Permitir que os não moderadores:",
"allowDesktop": "Permitir partilha de ecrã",
"allowVideo": "Permitir vídeo",
"askDesktop": "Pedir para partilhar o ecrã",
"askUnmute": "Pedir para ligar o som",
"audioModeration": "Ligar o microfone deles",
"blockEveryoneMicCamera": "Bloquear o microfone e a câmara de todos",
"breakoutRooms": "Salas simultâneas",
"desktopModeration": "Iniciar partilha de ecrã",
"goLive": "Aceder ao vivo",
"invite": "Convidar alguém",
"invite": "Convide alguém",
"lowerAllHands": "Baixar todas as mãos",
"lowerHand": "Baixar a mão",
"moreModerationActions": "Mais opções de moderação",
@@ -850,6 +924,8 @@
"muteAll": "Silenciar todos",
"muteEveryoneElse": "Silenciar todos os outros",
"reject": "Rejeitar",
"stopDesktop": "Parar a partilha de ecrã",
"stopEveryonesDesktop": "Interromper a partilha de ecrã de todos",
"stopEveryonesVideo": "Desligar a câmara de todos",
"stopVideo": "Desligar a câmara",
"unblockEveryoneMicCamera": "Desbloquear o microfone e a câmara de todos",
@@ -859,12 +935,15 @@
"headings": {
"lobby": "Sala de espera ({{count}})",
"participantsList": "Participantes da reunião ({{count}})",
"viewerRequests": "Pedidos dos espectadores {{count}}",
"visitorInQueue": " (à espera {{count}})",
"visitorRequests": " (pedidos {{count}})",
"visitors": "Visitantes ({{count}})",
"visitors": "Espectadores ({{count}})",
"visitorsList": "Espectadores ({{count}})",
"waitingLobby": "Aguardam na sala de espera ({{count}})"
},
"search": "Pesquisar participantes",
"searchDescription": "Comece a digitar para filtrar os participantes",
"title": "Participantes"
},
"passwordDigitsOnly": "Até {{number}} dígitos",
@@ -901,7 +980,7 @@
},
"results": {
"changeVote": "Mudar o voto",
"empty": "Ainda não há sondagens na reunião. Comece aqui uma sondagem!",
"empty": "Ainda não há sondagens na reunião.",
"hideDetailedResults": "Ocultar detalhes",
"showDetailedResults": "Mostrar detalhes",
"vote": "Voto"
@@ -919,9 +998,11 @@
"configuringDevices": "A configurar os dispositivos…",
"connectedWithAudioQ": "Está ligado com áudio?",
"connection": {
"failed": "Falha no teste de ligação!",
"good": "A sua ligação à Internet parece boa!",
"nonOptimal": "A sua ligação à Internet não é óptima",
"poor": "Tem uma ligação à Internet"
"poor": "Tem uma ligação à Internet fraca",
"running": "A realizar teste de ligação..."
},
"connectionDetails": {
"audioClipping": "Prevemos que o seu áudio tenha cortes.",
@@ -930,6 +1011,7 @@
"goodQuality": "Fantástico! A qualidade dos seus meios de comunicação vai ser óptima.",
"noMediaConnectivity": "Não foi possível encontrar uma forma de estabelecer a conectividade dos meios de comunicação para este teste. Isto é tipicamente causado por uma firewall ou NAT.",
"noVideo": "Prevemos que o seu vídeo seja terrível.",
"testFailed": "O teste de ligação encontrou problemas inesperados, mas isso pode não afetar a sua experiência.",
"undetectable": "Se mesmo assim não conseguir fazer chamadas no browser, recomendamos que se certifique de que os seus altifalantes, microfone e câmara estão devidamente configurados, que concedeu ao seu browser direitos de utilização do seu microfone e câmara, e que a versão do seu browser está actualizada. Se mesmo assim tiver problemas em telefonar, deverá contactar o criador da aplicação web.",
"veryPoorConnection": "Prevemos que a qualidade da sua chamada seja realmente terrível.",
"videoFreezing": "Prevemos que o seu vídeo congele, fique preto, e seja pixelizado.",
@@ -958,7 +1040,7 @@
"joinWithoutAudio": "Entrar sem áudio",
"keyboardShortcuts": "Ativar os atalhos de teclado",
"linkCopied": "Link copiado para a área de transferência",
"lookGood": "Tudo está a funcionar corretamente",
"lookGood": "Os seus dispositivos estão a funcionar corretamente",
"or": "ou",
"premeeting": "Pré-reunião",
"proceedAnyway": "Continuar na mesma",
@@ -1044,6 +1126,7 @@
"onBy": "{{name}} iniciou a gravação",
"onlyRecordSelf": "Gravar apenas as minhas transmissões áudio e vídeo",
"pending": "Preparando para gravar a reunião…",
"policyError": "Tentou iniciar uma gravação muito rapidamente. Por favor, tente novamente mais tarde!",
"recordAudioAndVideo": "Gravar áudio e vídeo",
"recordTranscription": "Gravar transcrições",
"saveLocalRecording": "Guardar ficheiro de gravação localmente (Beta)",
@@ -1087,11 +1170,13 @@
"signedIn": "Atualmente a aceder a eventos de calendário por {{email}}. Clique no botão Desconectar abaixo para parar de aceder a eventos de calendário.",
"title": "Calendário"
},
"chatWithPermissions": "O chat requer permissão",
"desktopShareFramerate": "Taxa de fotogramas para partilha do ambiente de trabalho",
"desktopShareHighFpsWarning": "Uma taxa de fotogramas mais elevada para a partilha do ambiente de trabalho pode afectar a sua largura de banda. É necessário reiniciar a partilha de ecrã para que as novas definições entrem em vigor.",
"desktopShareWarning": "É necessário reiniciar a partilha do ecrã para que as novas definições entrem em vigor.",
"devices": "Dispositivos",
"followMe": "Todos me seguem",
"followMeRecorder": "O gravador segue-me",
"framesPerSecond": "fotogramas-por-segundo",
"incomingMessage": "Receber uma mensagem",
"language": "Idioma",
@@ -1115,6 +1200,7 @@
"selectMic": "Microfone",
"selfView": "Autovisualização",
"shortcuts": "Atalhos",
"showSubtitlesOnStage": "Mostrar legendas",
"speakers": "Altifalantes",
"startAudioMuted": "Todos começam com microfone desligado",
"startReactionsMuted": "Todos começam com os sons de reação desativados",
@@ -1168,11 +1254,13 @@
"fearful": "Temeroso",
"happy": "Feliz",
"hours": "{{count}}h",
"labelTooltip": "Número de participantes: {{count}}",
"minutes": "{{count}}m",
"name": "Nome",
"neutral": "Neutro",
"sad": "Triste",
"search": "Pesquisar",
"searchDescription": "Comece a digitar para filtrar os participantes",
"searchHint": "Pesquisar participantes",
"seconds": "{{count}}s",
"speakerStats": "Estatísticas dos Participantes",
@@ -1209,6 +1297,7 @@
"closeChat": "Fechar chat",
"closeMoreActions": "Fechar menu de mais ações",
"closeParticipantsPane": "Fechar painel de participantes",
"closedCaptions": "Legendas ocultas",
"collapse": "Colapsar",
"document": "Mudar para documento partilhado",
"documentClose": "Fechar documento partilhado",
@@ -1238,6 +1327,7 @@
"lobbyButton": "Ativar/desativar sala de espera",
"localRecording": "Mudar os controlos locais de gravação",
"lockRoom": "Mudar palavra-chave de reunião",
"love": "Coração",
"lowerHand": "Baixar a mão",
"moreActions": "Mais ações",
"moreActionsMenu": "Menu de mais ações",
@@ -1248,13 +1338,14 @@
"muteEveryoneElsesVideo": "Parar o vídeo de todos os outros",
"muteEveryonesVideo": "Parar o vídeo de todos",
"muteGUMPending": "A ligar o seu microfone",
"noiseSuppression": "Supressão de ruído",
"noiseSuppression": "Supressão extra de ruído (BETA)",
"openChat": "Abrir chat",
"participants": "Abrir painel de participantes",
"participants": "Abrir painel de participantes. {{participantsCount}} participantes",
"pip": "Mudar para o modo Picture-in-Picture",
"privateMessage": "Enviar mensagem privada",
"profile": "Editar o seu perfil",
"raiseHand": "Levantar a mão",
"react": "Reações às mensagens",
"reactions": "Reações",
"reactionsMenu": "Menu de reações",
"recording": "Mudar gravação",
@@ -1297,14 +1388,15 @@
"closeChat": "Fechar chat",
"closeParticipantsPane": "Fechar painel de participantes",
"closeReactionsMenu": "Fechar menu de reações",
"disableNoiseSuppression": "Desativar a supressão de ruído",
"closedCaptions": "Legendas ocultas",
"disableNoiseSuppression": "Desativar supressão de ruído extra (BETA)",
"disableReactionSounds": "Pode desactivar os sons de reacção para esta reunião",
"documentClose": "Fechar documento partilhado",
"documentOpen": "Abrir documento partilhado",
"download": "Descarregar as nossas aplicações",
"e2ee": "Criptografia ponta a ponta",
"embedMeeting": "Incorporar reunião",
"enableNoiseSuppression": "Ativar a supressão de ruído",
"enableNoiseSuppression": "Ativar supressão extra de ruído (BETA)",
"endConference": "Terminar reunião para todos",
"enterFullScreen": "Ver em ecrã completo",
"enterTileView": "Ver em quadrícula",
@@ -1326,6 +1418,7 @@
"lobbyButtonEnable": "Ativar sala de espera",
"login": "Iniciar sessão",
"logout": "Terminar sessão",
"love": "Coração",
"lowerYourHand": "Baixar a mão",
"moreActions": "Mais ações",
"moreOptions": "Mais opções",
@@ -1338,7 +1431,7 @@
"noAudioSignalDialInDesc": "Também pode marcar usando:",
"noAudioSignalDialInLinkDesc": "Números de marcação",
"noAudioSignalTitle": "Não há nenhuma entrada vinda do seu microfone!",
"noiseSuppression": "Supressão de ruído",
"noiseSuppression": "Supressão extra de ruído (BETA)",
"noisyAudioInputDesc": "Parece que o seu microfone está a fazer barulho, por favor considere silenciar ou mudar de dispositivo.",
"noisyAudioInputTitle": "Seu microfone parece estar barulhento!",
"openChat": "Abrir chat",
@@ -1351,6 +1444,7 @@
"raiseYourHand": "Levantar a mão",
"reactionBoo": "Enviar reação de vaia",
"reactionClap": "Enviar reação de aplausos",
"reactionHeart": "Enviar reação com coração",
"reactionLaugh": "Enviar reação de risos",
"reactionLike": "Enviar reação de aprovado",
"reactionSilence": "Enviar reação de silêncio",
@@ -1384,15 +1478,19 @@
"transcribing": {
"ccButtonTooltip": "Iniciar/parar legendas",
"expandedLabel": "Transcrição ativada",
"failedToStart": "Transcrição falhou ao iniciar",
"labelToolTip": "A reunião esta sendo transcrita",
"failed": "Falha na transcrição",
"labelTooltip": "Esta reunião está a ser transcrita.",
"labelTooltipExtra": "Além disso, uma transcrição estará disponível posteriormente.",
"openClosedCaptions": "Abrir legendas ocultas",
"original": "Original",
"sourceLanguageDesc": "Atualmente a língua da reunião está definida para <b>{{sourceLanguage}}</b>. <br/> Pode alterá-la a partir ",
"sourceLanguageHere": "daqui",
"start": "Exibir legendas",
"stop": "Não exibir legendas",
"subtitles": "Legendas",
"subtitlesOff": "Desligado",
"tr": "TR"
"tr": "TR",
"translateTo": "Traduzir para"
},
"unpinParticipant": "{{participantName}} - Desafixar",
"userMedia": {
@@ -1424,7 +1522,7 @@
"ldTooltip": "Ver vídeo em baixa definição",
"lowDefinition": "Baixa definição (LD)",
"performanceSettings": "Definições de desempenho",
"recording": "Gravação em curso",
"recording": "Esta reunião está a ser gravada.",
"sd": "SD",
"sdTooltip": "Ver vídeo em definição padrão",
"standardDefinition": "Definição padrão",
@@ -1432,8 +1530,10 @@
},
"videothumbnail": {
"connectionInfo": "Informações sobre a ligação",
"demote": "Passar a visitante",
"demote": "Passar a espectador",
"domute": "Sem som",
"domuteDesktop": "Parar a partilha de ecrã",
"domuteDesktopOfOthers": "Interromper a partilha de ecrã para todos os outros",
"domuteOthers": "Silenciar todos os outros",
"domuteVideo": "Desativar a câmara",
"domuteVideoOfOthers": "Desactivar a câmara de todos os outros",
@@ -1484,21 +1584,21 @@
"webAssemblyWarningDescription": "WebAssembly desactivado ou não suportado por este navegador"
},
"visitors": {
"chatIndicator": "(visitante)",
"chatIndicator": "(espectador)",
"joinMeeting": {
"description": "Atualmente, é um observador nesta conferência.",
"description": "Atualmente, é um espectador nesta conferência.",
"raiseHand": "Levantar a mão",
"title": "Participar na reunião",
"wishToSpeak": "Se deseja intervir, levante a mão e aguarde a aprovação do moderador."
},
"labelTooltip": "Número de visitantes: {{count}}",
"labelTooltip": "Número de espectadores: {{count}}",
"notification": {
"demoteDescription": "Enviado aqui pelo {{actor}}, levante a mão para participar",
"noMainParticipantsDescription": "Um participante precisa de iniciar a reunião. Tente novamente daqui a pouco.",
"noMainParticipantsTitle": "Esta reunião ainda não começou.",
"noVisitorLobby": "Não é possível aderir enquanto houver uma sala de espera activada para a reunião.",
"notAllowedPromotion": "É necessário que um participante autorize primeiro o seu pedido.",
"title": "É um visitante na reunião"
"title": "É um espectador na reunião"
},
"waitingMessage": "Participará na reunião assim que esta estiver em direto!"
},

File diff suppressed because it is too large Load Diff

View File

@@ -331,11 +331,11 @@
"internalError": "Ett fel uppstod. Fel: {{error}}",
"internalErrorTitle": "Internt fel",
"kickMessage": "Du kan kontakta {{participantDisplayName}} för mer information.",
"kickParticipantButton": "Sparka ut",
"kickParticipantDialog": "Vill du sparka ut den här deltagaren?",
"kickParticipantButton": "Ta bort från mötet",
"kickParticipantDialog": "Vill du ta bort denna deltagaren från mötet?",
"kickParticipantTitle": "Tysta deltagaren?",
"kickSystemTitle": "Aj! du blev utsparkad ur mötet",
"kickTitle": "Aj! {{participantDisplayName}} sparkade ut dig ur mötet",
"kickSystemTitle": "Du har blivit borttagen från mötet",
"kickTitle": "{{participantDisplayName}} tog bort dig från mötet",
"linkMeeting": "Länka möte",
"linkMeetingTitle": "Länka möte till Salesforce",
"liveStreaming": "Streama",
@@ -763,7 +763,7 @@
"invitedThreePlusMembers": "{{name}} och {{count}} andra har bjudits in",
"invitedTwoMembers": "{{first}} och {{second}} har bjudits in",
"joinMeeting": "Delta",
"kickParticipant": "{{kicked}} sparkades ut av {{kicker}}",
"kickParticipant": "{{kicked}} togs bort av {{kicker}}",
"leftOneMember": "{{name}} lämnade mötet",
"leftThreePlusMembers": "{{name}} och många andra lämnade mötet",
"leftTwoMembers": "{{first}} och {{second}} lämnade mötet",
@@ -1245,7 +1245,7 @@
"help": "Hjälp",
"hideWhiteboard": "Dölj whiteboard",
"invite": "Bjud in personer",
"kick": "Sparka ut deltagare",
"kick": "Ta bort deltagare",
"laugh": "Skratta",
"leaveConference": "Lämna mötet",
"like": "Tummen upp",
@@ -1459,7 +1459,7 @@
"flip": "Vänd",
"grantModerator": "Godkänn moderator",
"hideSelfView": "Dölj självvyn",
"kick": "Sparka ut",
"kick": "Ta bort",
"mirrorVideo": "Spegelvänd video",
"moderator": "Moderator",
"mute": "Deltagaren har avstängd mikrofon",

View File

@@ -114,6 +114,7 @@
"error": "Error: your message was not sent. Reason: {{error}}",
"everyone": "Everyone",
"fieldPlaceHolder": "Aa",
"guestsChatIndicator": "(guest)",
"lobbyChatMessageTo": "Lobby chat message to {{recipient}}",
"message": "Message",
"messageAccessibleTitle": "{{user}} says:",
@@ -566,6 +567,7 @@
"downloadFailedDescription": "Please try again.",
"downloadFailedTitle": "Download failed",
"downloadFile": "Download",
"downloadStarted": "File download started",
"dragAndDrop": "Drag and drop files here or anywhere on screen",
"fileAlreadyUploaded": "File has already been uploaded to this meeting.",
"fileTooLargeDescription": "Please make sure the file does not exceed {{ maxFileSize }}.",
@@ -573,6 +575,7 @@
"fileUploadProgress": "File upload progress",
"fileUploadedSuccessfully": "File uploaded successfully",
"removeFile": "Remove",
"removeFileSuccess": "File removed successfully",
"uploadFailedDescription": "Please try again.",
"uploadFailedTitle": "Upload failed",
"uploadFile": "Share file"
@@ -862,6 +865,7 @@
"oldElectronClientDescription1": "You appear to be using an old version of the Jitsi Meet client which has known security vulnerabilities. Please make sure you update to our ",
"oldElectronClientDescription2": "latest build",
"oldElectronClientDescription3": " now!",
"openChat": "Open chat",
"participantWantsToJoin": "Wants to join the meeting",
"participantsWantToJoin": "Want to join the meeting",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removed by another participant",
@@ -1376,6 +1380,20 @@
"videounmute": "Start camera"
},
"addPeople": "Add people to your call",
"advancedAudioSettings": {
"aec": {
"label": "Acoustic echo cancellation"
},
"agc": {
"label": "Automatic gain control"
},
"ns": {
"label": "Noise suppression"
},
"stereo": {
"label": "Stereo"
}
},
"audioOnlyOff": "Disable low bandwidth mode",
"audioOnlyOn": "Enable low bandwidth mode",
"audioRoute": "Select the sound device",
@@ -1447,6 +1465,7 @@
"reactionHeart": "Send heart reaction",
"reactionLaugh": "Send laugh reaction",
"reactionLike": "Send thumbs up reaction",
"reactionLove": "Send love reaction",
"reactionSilence": "Send silence reaction",
"reactionSurprised": "Send surprised reaction",
"reactions": "Reactions",
@@ -1598,6 +1617,8 @@
"noMainParticipantsTitle": "This meeting hasn't started yet.",
"noVisitorLobby": "You cannot join while there is a lobby enabled for the meeting.",
"notAllowedPromotion": "A participant needs to allow your request first.",
"requestToJoin": "Hand Raised",
"requestToJoinDescription": "Your request was sent to the moderators. Hang tight!",
"title": "You are a viewer in the meeting"
},
"waitingMessage": "You'll join the meeting as soon as it is live!"

View File

@@ -138,7 +138,7 @@ import {
ENDPOINT_TEXT_MESSAGE_NAME
} from './constants';
const logger = Logger.getLogger(__filename);
const logger = Logger.getLogger('api:core');
/**
* List of the available commands.

View File

@@ -1,6 +1,6 @@
import Logger from '@jitsi/logger';
const logger = Logger.getLogger(__filename);
const logger = Logger.getLogger('api:external');
/**
* Returns Promise that resolves with result an list of available devices.

View File

@@ -28,7 +28,7 @@ import EtherpadManager from './etherpad/Etherpad';
import UIUtil from './util/UIUtil';
import VideoLayout from './videolayout/VideoLayout';
const logger = Logger.getLogger(__filename);
const logger = Logger.getLogger('ui:core');
let etherpadManager;

View File

@@ -45,7 +45,7 @@ import AudioLevels from '../audio_levels/AudioLevels';
import { VIDEO_CONTAINER_TYPE, VideoContainer } from './VideoContainer';
const logger = Logger.getLogger(__filename);
const logger = Logger.getLogger('ui:videolayout');
const DESKTOP_CONTAINER_TYPE = 'desktop';

View File

@@ -24,7 +24,7 @@ export const VIDEO_CONTAINER_TYPE = 'camera';
// Corresponds to animation duration from the animatedFadeIn and animatedFadeOut CSS classes.
const FADE_DURATION_MS = 300;
const logger = Logger.getLogger(__filename);
const logger = Logger.getLogger('ui:VideoContainer');
/**
* List of container events that we are going to process for the large video.

View File

@@ -16,7 +16,7 @@ import {
import LargeVideoManager from './LargeVideoManager';
import { VIDEO_CONTAINER_TYPE } from './VideoContainer';
const logger = Logger.getLogger(__filename);
const logger = Logger.getLogger('ui:VideoLayout');
let largeVideo;
const VideoLayout = {

View File

@@ -1,4 +1,4 @@
const logger = require('@jitsi/logger').getLogger(__filename);
const logger = require('@jitsi/logger').getLogger('app:utils');
/**
* Manages a queue of functions where the current function in progress will

182
package-lock.json generated
View File

@@ -20,7 +20,7 @@
"@giphy/react-native-sdk": "4.1.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/logger": "2.1.1",
"@jitsi/rnnoise-wasm": "0.2.1",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@microsoft/microsoft-graph-client": "3.0.1",
@@ -51,6 +51,7 @@
"clipboard-copy": "4.0.1",
"clsx": "1.1.1",
"dayjs": "1.11.13",
"dompurify": "3.2.6",
"dropbox": "10.7.0",
"focus-visible": "5.1.0",
"glob": "11.0.3",
@@ -65,7 +66,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/v2044.0.0+437abe32/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2091.0.0+518cec5f/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"null-loader": "4.0.1",
"optional-require": "1.0.3",
@@ -103,6 +104,7 @@
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "124.0.4",
"react-native-webview": "13.13.5",
"react-native-worklets-core": "https://github.com/jitsi/react-native-worklets-core.git#8c5dfab2a5907305da8971696a781b60f0f9cb18",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
"react-textarea-autosize": "8.3.0",
@@ -124,6 +126,8 @@
},
"devDependencies": {
"@babel/core": "7.25.9",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/plugin-transform-private-methods": "7.25.9",
"@babel/preset-env": "7.25.9",
"@babel/preset-react": "7.25.9",
@@ -1000,6 +1004,43 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-proposal-nullish-coalescing-operator": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz",
"integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==",
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.18.6",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-proposal-optional-chaining": {
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz",
"integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==",
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.20.2",
"@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
"@babel/plugin-syntax-optional-chaining": "^7.8.3"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-proposal-private-property-in-object": {
"version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
@@ -1839,13 +1880,13 @@
}
},
"node_modules/@babel/plugin-transform-optional-chaining": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz",
"integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
"integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
"@babel/helper-skip-transparent-expression-wrappers": "^7.25.9"
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -4520,9 +4561,9 @@
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"node_modules/@jitsi/logger": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
"integrity": "sha512-qwbpRwuwkBFgh0F5jivq/5fAm46yVoXURc5LCklEs8lAShYVangFEXKW7RLpZuZ5nQnrHrlvU8MswQNREmvahg=="
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.1.1.tgz",
"integrity": "sha512-adMtODSXvYJtqfRuDwxN1hNWPJ4gvp868G7QXII3ajSgPaE2mMcbLYRHy6mZZlQJGnxr0dDaivOrvV6CikHZNQ=="
},
"node_modules/@jitsi/precall-test": {
"version": "1.0.6",
@@ -7957,6 +7998,13 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@types/unorm": {
"version": "1.3.28",
"resolved": "https://registry.npmjs.org/@types/unorm/-/unorm-1.3.28.tgz",
@@ -12437,6 +12485,15 @@
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/dompurify": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
"integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/domutils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
@@ -18203,12 +18260,12 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2044.0.0+437abe32/lib-jitsi-meet.tgz",
"integrity": "sha512-LdYhBImyB3q7HV3KlJdww1Qw+Ke2vJOir32+8dYxJl9QdrQ5HedRXoF/+FF9+LdDIy5BG1o8M+To4/poYKLYTw==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2091.0.0+518cec5f/lib-jitsi-meet.tgz",
"integrity": "sha512-mmdvU3B7dYWV6RcfFW/BWJkaNR3LLCG3wHcBYT8WZKVbh00vu4SZJKwtjiVrw2aIP1FcXoD3T0RJ5+IaGROU3A==",
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.4.6",
"@jitsi/logger": "2.0.2",
"@jitsi/logger": "2.1.1",
"@jitsi/precall-test": "1.0.6",
"@jitsi/rtcstats": "9.7.0",
"@testrtc/watchrtc-sdk": "1.38.2",
@@ -18235,6 +18292,11 @@
"ua-parser-js": "1.0.35"
}
},
"node_modules/lib-jitsi-meet/node_modules/@jitsi/logger": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
"integrity": "sha512-qwbpRwuwkBFgh0F5jivq/5fAm46yVoXURc5LCklEs8lAShYVangFEXKW7RLpZuZ5nQnrHrlvU8MswQNREmvahg=="
},
"node_modules/lib-jitsi-meet/node_modules/@jitsi/rtcstats": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.7.0.tgz",
@@ -22474,6 +22536,19 @@
"react-native": "*"
}
},
"node_modules/react-native-worklets-core": {
"version": "1.6.2",
"resolved": "git+ssh://git@github.com/jitsi/react-native-worklets-core.git#8c5dfab2a5907305da8971696a781b60f0f9cb18",
"integrity": "sha512-SW47DvuNLjhoj8PJK8haq0B/69//ChG+/3WyK9NkhwUSM1kqZLle1O7SqGeLFp2f37qyBXH5X+cXPDaabJ8CHA==",
"license": "MIT",
"dependencies": {
"string-hash-64": "^1.0.3"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-youtube-iframe": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-native-youtube-iframe/-/react-native-youtube-iframe-2.3.0.tgz",
@@ -24457,6 +24532,12 @@
}
]
},
"node_modules/string-hash-64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string-hash-64/-/string-hash-64-1.0.3.tgz",
"integrity": "sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==",
"license": "MIT"
},
"node_modules/string-replace-to-array": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string-replace-to-array/-/string-replace-to-array-1.0.3.tgz",
@@ -27652,6 +27733,27 @@
"@babel/helper-plugin-utils": "^7.25.9"
}
},
"@babel/plugin-proposal-nullish-coalescing-operator": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz",
"integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.18.6",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
}
},
"@babel/plugin-proposal-optional-chaining": {
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz",
"integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.20.2",
"@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
"@babel/plugin-syntax-optional-chaining": "^7.8.3"
}
},
"@babel/plugin-proposal-private-property-in-object": {
"version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
@@ -28136,12 +28238,12 @@
}
},
"@babel/plugin-transform-optional-chaining": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz",
"integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
"integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
"requires": {
"@babel/helper-plugin-utils": "^7.25.9",
"@babel/helper-skip-transparent-expression-wrappers": "^7.25.9"
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
}
},
"@babel/plugin-transform-parameters": {
@@ -29958,9 +30060,9 @@
}
},
"@jitsi/logger": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
"integrity": "sha512-qwbpRwuwkBFgh0F5jivq/5fAm46yVoXURc5LCklEs8lAShYVangFEXKW7RLpZuZ5nQnrHrlvU8MswQNREmvahg=="
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.1.1.tgz",
"integrity": "sha512-adMtODSXvYJtqfRuDwxN1hNWPJ4gvp868G7QXII3ajSgPaE2mMcbLYRHy6mZZlQJGnxr0dDaivOrvV6CikHZNQ=="
},
"@jitsi/precall-test": {
"version": "1.0.6",
@@ -32389,6 +32491,12 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
},
"@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"optional": true
},
"@types/unorm": {
"version": "1.3.28",
"resolved": "https://registry.npmjs.org/@types/unorm/-/unorm-1.3.28.tgz",
@@ -35531,6 +35639,14 @@
"domelementtype": "^2.2.0"
}
},
"dompurify": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
"integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
"requires": {
"@types/trusted-types": "^2.0.7"
}
},
"domutils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
@@ -39600,11 +39716,11 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2044.0.0+437abe32/lib-jitsi-meet.tgz",
"integrity": "sha512-LdYhBImyB3q7HV3KlJdww1Qw+Ke2vJOir32+8dYxJl9QdrQ5HedRXoF/+FF9+LdDIy5BG1o8M+To4/poYKLYTw==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2091.0.0+518cec5f/lib-jitsi-meet.tgz",
"integrity": "sha512-mmdvU3B7dYWV6RcfFW/BWJkaNR3LLCG3wHcBYT8WZKVbh00vu4SZJKwtjiVrw2aIP1FcXoD3T0RJ5+IaGROU3A==",
"requires": {
"@jitsi/js-utils": "2.4.6",
"@jitsi/logger": "2.0.2",
"@jitsi/logger": "2.1.1",
"@jitsi/precall-test": "1.0.6",
"@jitsi/rtcstats": "9.7.0",
"@testrtc/watchrtc-sdk": "1.38.2",
@@ -39630,6 +39746,11 @@
"ua-parser-js": "1.0.35"
}
},
"@jitsi/logger": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
"integrity": "sha512-qwbpRwuwkBFgh0F5jivq/5fAm46yVoXURc5LCklEs8lAShYVangFEXKW7RLpZuZ5nQnrHrlvU8MswQNREmvahg=="
},
"@jitsi/rtcstats": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.7.0.tgz",
@@ -42634,6 +42755,14 @@
"invariant": "2.2.4"
}
},
"react-native-worklets-core": {
"version": "git+ssh://git@github.com/jitsi/react-native-worklets-core.git#8c5dfab2a5907305da8971696a781b60f0f9cb18",
"integrity": "sha512-SW47DvuNLjhoj8PJK8haq0B/69//ChG+/3WyK9NkhwUSM1kqZLle1O7SqGeLFp2f37qyBXH5X+cXPDaabJ8CHA==",
"from": "react-native-worklets-core@https://github.com/jitsi/react-native-worklets-core.git#8c5dfab2a5907305da8971696a781b60f0f9cb18",
"requires": {
"string-hash-64": "^1.0.3"
}
},
"react-native-youtube-iframe": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-native-youtube-iframe/-/react-native-youtube-iframe-2.3.0.tgz",
@@ -43991,6 +44120,11 @@
}
}
},
"string-hash-64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string-hash-64/-/string-hash-64-1.0.3.tgz",
"integrity": "sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw=="
},
"string-replace-to-array": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string-replace-to-array/-/string-replace-to-array-1.0.3.tgz",

View File

@@ -26,7 +26,7 @@
"@giphy/react-native-sdk": "4.1.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/logger": "2.1.1",
"@jitsi/rnnoise-wasm": "0.2.1",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@microsoft/microsoft-graph-client": "3.0.1",
@@ -57,6 +57,7 @@
"clipboard-copy": "4.0.1",
"clsx": "1.1.1",
"dayjs": "1.11.13",
"dompurify": "3.2.6",
"dropbox": "10.7.0",
"focus-visible": "5.1.0",
"glob": "11.0.3",
@@ -71,7 +72,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/v2044.0.0+437abe32/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2091.0.0+518cec5f/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"null-loader": "4.0.1",
"optional-require": "1.0.3",
@@ -109,6 +110,7 @@
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "124.0.4",
"react-native-webview": "13.13.5",
"react-native-worklets-core": "https://github.com/jitsi/react-native-worklets-core.git#8c5dfab2a5907305da8971696a781b60f0f9cb18",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
"react-textarea-autosize": "8.3.0",
@@ -130,6 +132,8 @@
},
"devDependencies": {
"@babel/core": "7.25.9",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/plugin-transform-private-methods": "7.25.9",
"@babel/preset-env": "7.25.9",
"@babel/preset-react": "7.25.9",

View File

@@ -124,12 +124,11 @@ repositories {
dependencies {
//noinspection GradleDynamicVersion
// noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation 'com.squareup.duktape:duktape-android:1.3.0'
implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
implementation 'com.jakewharton.timber:timber:4.7.1'
// From node_modules
// From node_modules
}
if (isNewArchitectureEnabled()) {

View File

@@ -22,7 +22,6 @@ public class JitsiMeetReactNativePackage implements ReactPackage {
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new JMOngoingConferenceModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),
new PictureInPictureModule(reactContext),

View File

@@ -85,7 +85,12 @@
"react-native-video": "0.0.0",
"react-native-watch-connectivity": "0.0.0",
"react-native-webrtc": "0.0.0",
"react-native-webview": "0.0.0"
"react-native-webview": "0.0.0",
"react-native-worklets-core": "0.0.0"
},
"devDependencies": {
"@babel/plugin-proposal-nullish-coalescing-operator": "0.0.0",
"@babel/plugin-proposal-optional-chaining": "0.0.0"
},
"scripts": {
"postinstall": "node sdk_instructions.js",

View File

@@ -100,10 +100,6 @@ fs.copyFileSync(
`${iosSrcPath}/InfoPlistUtil.h`,
`${iosDestPath}/InfoPlistUtil.h`
);
fs.copyFileSync(
`${iosSrcPath}/JavaScriptSandbox.m`,
`${iosDestPath}/JavaScriptSandbox.m`
);
fs.copyFileSync(
`${iosSrcPath}/JitsiAudioSession.m`,
`${iosDestPath}/JitsiAudioSession.m`
@@ -184,10 +180,6 @@ fs.copyFileSync(
`${androidSourcePath}/ConnectionService.java`,
`${androidTargetPath}/ConnectionService.java`
);
fs.copyFileSync(
`${androidSourcePath}/JavaScriptSandboxModule.java`,
`${androidTargetPath}/JavaScriptSandboxModule.java`
);
fs.copyFileSync(
`${androidSourcePath}/LocaleDetector.java`,
`${androidTargetPath}/LocaleDetector.java`

View File

@@ -60,12 +60,12 @@ This is now set on your end.`
);
}
}
packageJSON.devDependencies = packageJSON.devDependencies || {};
packageJSON.overrides = packageJSON.overrides || {};
for (const key in RNSDKpackageJSON.overrides) {
if (!packageJSON.overrides.hasOwnProperty(key)) {
packageJSON.overrides[key] = RNSDKpackageJSON.overrides[key];
for (const key in RNSDKpackageJSON.devDependencies) {
if (!packageJSON.devDependencies.hasOwnProperty(key)) {
packageJSON.devDependencies[key] = RNSDKpackageJSON.devDependencies[key];
updated = true;
}
}
@@ -91,6 +91,14 @@ This is now set on your end.`
return item;
}, {});
packageJSON.devDependencies = Object.keys(packageJSON.devDependencies)
.sort()
.reduce((item, itemKey) => {
item[itemKey] = packageJSON.devDependencies[itemKey];
return item;
}, {});
fs.writeFileSync(pathToPackageJSON, JSON.stringify(packageJSON, null, 2));

View File

@@ -26,6 +26,13 @@ function mergeDependencyVersions() {
SDKPackageJSON.peerDependencies[key] = packageJSON.dependencies[key];
}
}
// Updates SDK dev dependencies(used by react-native-worklets-core lib. babel plugin)
for (const key in packageJSON.devDependencies) {
if (SDKPackageJSON.devDependencies.hasOwnProperty(key)) {
SDKPackageJSON.devDependencies[key] = packageJSON.devDependencies[key];
}
}
// Set RN peer dependency.
const rnVersion = semver.parse(packageJSON.dependencies['react-native']);

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/analytics');
export default getLogger('app:analytics');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/app');
export default getLogger('app:core');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/authentication');
export default getLogger('app:authentication');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/app');
export default getLogger('app:base-app');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/audio-only');
export default getLogger('app:audio-only');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/conference');
export default getLogger('app:base-conference');

View File

@@ -26,7 +26,7 @@ import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEff
import { iAmVisitor } from '../../visitors/functions';
import { overwriteConfig } from '../config/actions';
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, CONNECTION_WILL_CONNECT } from '../connection/actionTypes';
import { connectionDisconnected, disconnect } from '../connection/actions';
import { connect, connectionDisconnected, disconnect, setPreferVisitor } from '../connection/actions';
import { validateJwt } from '../jwt/functions';
import { JitsiConferenceErrors, JitsiConferenceEvents, JitsiConnectionErrors } from '../lib-jitsi-meet';
import { MEDIA_TYPE } from '../media/constants';
@@ -78,6 +78,11 @@ import { IConferenceMetadata } from './reducer';
*/
let beforeUnloadHandler: ((e?: any) => void) | undefined;
/**
* A simple flag to avoid retrying more than once to join as a visitor when hitting max occupants reached.
*/
let retryAsVisitorOnMaxError = true;
/**
* Implements the middleware of the feature base/conference.
*
@@ -202,11 +207,20 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
break;
}
case JitsiConferenceErrors.CONFERENCE_MAX_USERS: {
dispatch(showErrorNotification({
hideErrorSupportLink: true,
descriptionKey: 'dialog.maxUsersLimitReached',
titleKey: 'dialog.maxUsersLimitReachedTitle'
}));
let retryAsVisitor = false;
if (error.params?.length && error.params[0]?.visitorsSupported) {
// visitors are supported, so let's try joining that way
retryAsVisitor = true;
}
if (!retryAsVisitor) {
dispatch(showErrorNotification({
hideErrorSupportLink: true,
descriptionKey: 'dialog.maxUsersLimitReached',
titleKey: 'dialog.maxUsersLimitReachedTitle'
}));
}
// In case of max users(it can be from a visitor node), let's restore
// oldConfig if any as we will be back to the main prosody.
@@ -220,6 +234,18 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
.then(() => dispatch(disconnect()));
}
if (retryAsVisitor && !newConfig && retryAsVisitorOnMaxError) {
retryAsVisitorOnMaxError = false;
logger.info('On max user reached will retry joining as a visitor');
dispatch(disconnect(true)).then(() => {
dispatch(setPreferVisitor(true));
return dispatch(connect());
});
}
break;
}
case JitsiConferenceErrors.NOT_ALLOWED_ERROR: {
@@ -300,6 +326,8 @@ function _conferenceJoined({ dispatch, getState }: IStore, next: Function, actio
requireDisplayName
} = getState()['features/base/config'];
retryAsVisitorOnMaxError = true;
dispatch(removeLobbyChatParticipant(true));
pendingSubjectChange && dispatch(setSubject(pendingSubjectChange));

View File

@@ -1,5 +1,6 @@
import { ToolbarButton } from '../../toolbox/types';
import { ILoggingConfig } from '../logging/types';
import { IAudioSettings } from '../settings/reducer';
import { DesktopSharingSourceType } from '../tracks/types';
type ButtonsWithNotifyClick = 'camera' |
@@ -191,6 +192,7 @@ export interface IConfig {
appId?: string;
audioLevelsInterval?: number;
audioQuality?: {
enableAdvancedAudioSettings?: boolean;
opusMaxAverageBitrate?: number | null;
stereo?: boolean;
};
@@ -237,6 +239,7 @@ export interface IConfig {
inactiveDisabled?: boolean;
};
constraints?: {
audio?: IAudioSettings;
video?: {
height?: {
ideal?: number;
@@ -521,7 +524,6 @@ export interface IConfig {
preCallTestEnabled?: boolean;
preCallTestICEUrl?: string;
};
prejoinPageEnabled?: boolean;
raisedHands?: {
disableLowerHandByModerator?: boolean;
disableLowerHandNotification?: boolean;

View File

@@ -202,7 +202,6 @@ export default [
'prejoinConfig.enabled',
'prejoinConfig.hideDisplayName',
'prejoinConfig.hideExtraJoinButtons',
'prejoinPageEnabled',
'raisedHands',
'recordingService',
'requireDisplayName',

View File

@@ -6,6 +6,7 @@ import { safeJsonParse } from '@jitsi/js-utils/json';
import { isEmpty, mergeWith, pick } from 'lodash-es';
import { IReduxState } from '../../app/types';
import { browser } from '../lib-jitsi-meet';
import { getLocalParticipant } from '../participants/functions';
import { isEmbedded } from '../util/embedUtils';
import { parseURLParams } from '../util/parseURLParams';
@@ -256,6 +257,17 @@ export function isDisplayNameVisible(state: IReduxState): boolean {
return !state['features/base/config'].hideDisplayName;
}
/**
* Selector for determining if the advanced audio settings are enabled.
*
* @param {Object} state - The state of the app.
* @returns {boolean}
*/
export function isAdvancedAudioSettingsEnabled(state: IReduxState): boolean {
return !browser.isWebKitBased() && Boolean(state['features/base/config']?.audioQuality?.enableAdvancedAudioSettings);
}
/**
* Restores a Jitsi Meet config.js from {@code localStorage} if it was
* previously downloaded from a specific {@code baseURL} and stored with
@@ -375,8 +387,7 @@ export function setConfigFromURLParams(
// When not in an iframe, start without media if the pre-join page is not enabled.
if (!isEmbedded()
&& ('config.prejoinConfig.enabled' in params || 'config.prejoinPageEnabled' in params)
&& (config.prejoinConfig?.enabled === false || config.prejoinPageEnabled === false)) {
&& 'config.prejoinConfig.enabled' in params && config.prejoinConfig?.enabled === false) {
logger.warn('Using prejoinConfig.enabled config URL overwrite implies starting without media.');
config.disableInitialGUM = true;
}

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/config');
export default getLogger('app:config');

View File

@@ -405,11 +405,6 @@ function _translateLegacyConfig(oldValue: IConfig) {
newValue.welcomePage.disabled = !oldValue.enableWelcomePage;
}
newValue.prejoinConfig = oldValue.prejoinConfig || {};
if (oldValue.hasOwnProperty('prejoinPageEnabled')) {
newValue.prejoinConfig.enabled = oldValue.prejoinPageEnabled;
}
newValue.disabledSounds = newValue.disabledSounds || [];
if (oldValue.disableJoinLeaveSounds) {

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/connection');
export default getLogger('app:connection');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/devices');
export default getLogger('app:devices');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/dialog');
export default getLogger('app:dialog');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/i18n');
export default getLogger('app:i18n');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/jitsi-local-storage');
export default getLogger('app:jitsi-local-storage');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/jwt');
export default getLogger('app:jwt');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/lastn');
export default getLogger('app:lastn');

View File

@@ -1,6 +1,7 @@
// @ts-ignore
import { safeJsonParse } from '@jitsi/js-utils/json';
import { NativeModules } from 'react-native';
// @ts-ignore
import { Worklets } from 'react-native-worklets-core';
import { loadScript } from '../util/loadScript.native';
@@ -8,7 +9,12 @@ import logger from './logger';
export * from './functions.any';
const { JavaScriptSandbox } = NativeModules;
/**
* Worklet context usefull for running small tasks off the JS thread.
*/
export const workletContext = Worklets.createContext('ConfigParser');
/**
* Loads config.js from a specific remote server.
@@ -18,14 +24,46 @@ const { JavaScriptSandbox } = NativeModules;
*/
export async function loadConfig(url: string): Promise<Object> {
try {
const configTxt = await loadScript(url, 10 * 1000 /* Timeout in ms */, true /* skipeval */);
const configJson = await JavaScriptSandbox.evaluate(`${configTxt}\nJSON.stringify(config);`);
const config = safeJsonParse(configJson);
const configTxt = await loadScript(url, 10 * 1000, true);
if (typeof config !== 'object') {
throw new Error('config is not an object');
const parseConfigAsync = workletContext.createRunAsync(function parseConfig(configText: string): string {
'worklet';
try {
// Used IIFE wrapper to capture config object from config.js
const configObj = eval(
'(function(){\n'
+ configText
+ '\n; return (typeof config !== "undefined" ? config : globalThis.config); })()'
);
if (configObj == void 0) {
return 'Worklet_Error: config is undefined after eval()';
}
if (typeof configObj !== 'object') {
return 'Worklet_Error: config is not an object';
}
return JSON.stringify(configObj);
} catch (err) {
return 'Worklet_Error:' + ((err as Error)?.message ?? String(err));
}
});
const workletConfig = await parseConfigAsync(configTxt);
if (typeof workletConfig !== 'string') {
throw new Error('Worklet error: workletConfig is not a string');
}
if (workletConfig.startsWith('Worklet_Error:')) {
const msg = workletConfig.slice('Worklet_Error:'.length);
throw new Error(`Worklet error: ${msg}`);
}
const config = safeJsonParse(workletConfig);
logger.info(`Config loaded from ${url}`);
return config;

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/lib-jitsi-meet');
export default getLogger('app:lib-jitsi-meet');

View File

@@ -17,8 +17,8 @@ const DEFAULT_LOGGING_CONFIG: ILoggingConfig = {
loggers: {
// The following are too verbose in their logging with the
// {@link #defaultLogLevel}:
'modules/RTC/TraceablePeerConnection': 'info',
'modules/xmpp/strophe.util': 'log'
'rtc:TraceablePeerConnection': 'info',
'xmpp:strophe.util': 'log'
}
};
@@ -41,10 +41,10 @@ const DEFAULT_STATE = {
// Reduce default verbosity on mobile, it kills performance.
if (navigator.product === 'ReactNative') {
const RN_LOGGERS: { [key: string]: LogLevel; } = {
'modules/sdp/SDPUtil': 'info',
'modules/xmpp/ChatRoom': 'warn',
'modules/xmpp/JingleSessionPC': 'info',
'modules/xmpp/strophe.jingle': 'info'
'sdp:SDPUtils': 'info',
'xmpp:ChatRoom': 'warn',
'xmpp:JingleSessionPC': 'info',
'xmpp:strophe.jingle': 'info'
};
DEFAULT_STATE.config.loggers = {

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/media');
export default getLogger('app:media');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/net-info');
export default getLogger('app:net-info');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/participants');
export default getLogger('app:participants');

View File

@@ -24,12 +24,14 @@ import { hideNotification, showNotification } from '../../notifications/actions'
import {
LOCAL_RECORDING_NOTIFICATION_ID,
NOTIFICATION_TIMEOUT_TYPE,
RAISE_HAND_NOTIFICATION_ID
RAISE_HAND_NOTIFICATION_ID,
VISITOR_ASKED_TO_JOIN_NOTIFICATION_ID
} from '../../notifications/constants';
import { open as openParticipantsPane } from '../../participants-pane/actions';
import { CALLING, INVITED } from '../../presence-status/constants';
import { RAISE_HAND_SOUND_ID } from '../../reactions/constants';
import { RECORDING_OFF_SOUND_ID, RECORDING_ON_SOUND_ID } from '../../recording/constants';
import { iAmVisitor } from '../../visitors/functions';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app/actionTypes';
import { CONFERENCE_JOINED, CONFERENCE_WILL_JOIN } from '../conference/actionTypes';
import { forEachConference, getCurrentConference } from '../conference/functions';
@@ -167,7 +169,10 @@ MiddlewareRegistry.register(store => next => action => {
case LOCAL_PARTICIPANT_RAISE_HAND: {
const { raisedHandTimestamp } = action;
const localId = getLocalParticipant(store.getState())?.id;
const localParticipant = getLocalParticipant(store.getState());
const localId = localParticipant?.id;
const _iAmVisitor = iAmVisitor(store.getState());
const isHandRaised = hasRaisedHand(localParticipant);
store.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without
@@ -186,6 +191,18 @@ MiddlewareRegistry.register(store => next => action => {
raisedHandTimestamp
}));
if (_iAmVisitor) {
const notifyAction = isHandRaised
? hideNotification(VISITOR_ASKED_TO_JOIN_NOTIFICATION_ID)
: showNotification({
titleKey: 'visitors.notification.requestToJoin',
descriptionKey: 'visitors.notification.requestToJoinDescription',
uid: VISITOR_ASKED_TO_JOIN_NOTIFICATION_ID
}, NOTIFICATION_TIMEOUT_TYPE.STICKY);
store.dispatch(notifyAction);
}
if (typeof APP !== 'undefined') {
APP.API.notifyRaiseHandUpdated(localId, raisedHandTimestamp);
}

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/premeeting');
export default getLogger('app:premeeting');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/react');
export default getLogger('app:react');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/redux');
export default getLogger('app:redux');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/settings');
export default getLogger('app:settings');

View File

@@ -51,8 +51,15 @@ const DEFAULT_STATE: ISettingsState = {
userSelectedMicDeviceLabel: undefined
};
export interface IAudioSettings {
autoGainControl?: boolean;
channelCount?: 1 | 2;
echoCancellation?: boolean;
noiseSuppression?: boolean;
}
export interface ISettingsState {
audioOutputDeviceId?: string;
audioSettings?: IAudioSettings;
audioSettingsVisible?: boolean;
avatarURL?: string;
cameraDeviceId?: string | boolean;
@@ -66,6 +73,7 @@ export interface ISettingsState {
localFlipX?: boolean;
maxStageParticipants?: number;
micDeviceId?: string | boolean;
previewAudioTrack?: any | null;
serverURL?: string;
showSubtitlesOnStage?: boolean;
soundsIncomingMessage?: boolean;

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/sounds');
export default getLogger('app:sounds');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/testing');
export default getLogger('app:testing');

View File

@@ -164,6 +164,7 @@ export function createLocalTracksA(options: ITrackOptions = {}) {
= createLocalTracksF(
{
cameraDeviceId: options.cameraDeviceId,
constraints: options?.constraints,
devices: [ device ],
facingMode:
options.facingMode || getCameraFacingMode(state),

View File

@@ -12,6 +12,7 @@ import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen
import { isAudioOnlySharing, isScreenVideoShared } from '../../screen-share/functions';
import { toggleScreenshotCaptureSummary } from '../../screenshot-capture/actions';
import { isScreenshotCaptureEnabled } from '../../screenshot-capture/functions';
import { setAudioSettings } from '../../settings/actions.web';
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
import { getCurrentConference } from '../conference/functions';
import { notifyCameraError, notifyMicError } from '../devices/actions.web';
@@ -27,6 +28,7 @@ import {
} from '../media/constants';
import { IGUMPendingState } from '../media/types';
import { updateSettings } from '../settings/actions';
import { IAudioSettings } from '../settings/reducer';
import { addLocalTrack, replaceLocalTrack } from './actions.any';
import AllowToggleCameraDialog from './components/web/AllowToggleCameraDialog';
@@ -37,6 +39,7 @@ import {
getLocalVideoTrack,
isToggleCameraEnabled
} from './functions';
import { applyAudioConstraints, getLocalJitsiAudioTrackSettings } from './functions.web';
import logger from './logger';
import { ICreateInitialTracksOptions, IInitialTracksErrors, IShareOptions, IToggleScreenSharingOptions } from './types';
@@ -329,7 +332,6 @@ export function setGUMPendingStateOnFailedTracks(tracks: Array<any>, dispatch: I
export function createAndAddInitialAVTracks(devices: Array<MediaType>) {
return async (dispatch: IStore['dispatch']) => {
dispatch(gumPending(devices, IGUMPendingState.PENDING_UNMUTE));
const { tracks, errors } = await dispatch(createInitialAVTracks({ devices }));
setGUMPendingStateOnFailedTracks(tracks, dispatch);
@@ -541,3 +543,21 @@ export function toggleCamera() {
await dispatch(replaceLocalTrack(null, newVideoTrack));
};
}
/**
* Toggles the audio settings.
*
* @param {IAudioSettings} settings - The settings to apply.
* @returns {Function}
*/
export function toggleUpdateAudioSettings(settings: IAudioSettings) {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
await applyAudioConstraints(state, settings);
const updatedSettings = getLocalJitsiAudioTrackSettings(state) as IAudioSettings;
dispatch(setAudioSettings(updatedSettings));
};
}

View File

@@ -201,6 +201,45 @@ export function getLocalJitsiAudioTrack(state: IReduxState) {
return track?.jitsiTrack;
}
/**
* Returns audio settings from the local Jitsi audio track.
*
* @param {IReduxState} state - The Redux state.
* @returns {IAudioSettings} The extracted audio settings.
*/
export function getLocalJitsiAudioTrackSettings(state: IReduxState) {
const jitsiTrack = getLocalJitsiAudioTrack(state);
if (!jitsiTrack) {
const config = state['features/base/config'];
const disableAP = Boolean(config?.disableAP);
const disableAGC = Boolean(config?.disableAGC);
const disableAEC = Boolean(config?.disableAEC);
const disableNS = Boolean(config?.disableNS);
const stereo = Boolean(config?.audioQuality?.stereo);
return {
autoGainControl: !disableAP && !disableAGC,
channelCount: stereo ? 2 : 1,
echoCancellation: !disableAP && !disableAEC,
noiseSuppression: !disableAP && !disableNS
};
}
const hasAudioMixerEffect = Boolean(typeof jitsiTrack._streamEffect?.setMuted === 'function' && jitsiTrack._streamEffect?._originalTrack);
const track = hasAudioMixerEffect ? jitsiTrack._streamEffect._originalTrack : jitsiTrack.getTrack();
const { autoGainControl, channelCount, echoCancellation, noiseSuppression } = track.getSettings();
return {
autoGainControl,
channelCount,
echoCancellation,
noiseSuppression
};
}
/**
* Returns track of specified media type for specified participant.
*

View File

@@ -1,5 +1,6 @@
import { IStore } from '../../app/types';
import { IStateful } from '../app/types';
import { isAdvancedAudioSettingsEnabled } from '../config/functions.any';
import { isMobileBrowser } from '../environment/utils';
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
import { gumPending, setAudioMuted } from '../media/actions';
@@ -11,9 +12,10 @@ import {
getUserSelectedCameraDeviceId,
getUserSelectedMicDeviceId
} from '../settings/functions.web';
import { IAudioSettings } from '../settings/reducer';
import { getJitsiMeetGlobalNSConnectionTimes } from '../util/helpers';
import { getCameraFacingMode } from './functions.any';
import { getCameraFacingMode, getLocalJitsiAudioTrack, getLocalJitsiAudioTrackSettings } from './functions.any';
import loadEffects from './loadEffects';
import logger from './logger';
import { ITrackOptions } from './types';
@@ -62,7 +64,13 @@ export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore,
desktopSharingFrameRate,
resolution
} = state['features/base/config'];
const constraints = options.constraints ?? state['features/base/config'].constraints;
const constraints = options.constraints ?? state['features/base/config'].constraints ?? {};
if (isAdvancedAudioSettingsEnabled(state) && typeof APP !== 'undefined') {
constraints.audio = state['features/settings'].audioSettings ?? getLocalJitsiAudioTrackSettings(state);
}
return (
loadEffects(store).then((effectsArray: Object[]) => {
@@ -214,3 +222,32 @@ export function isToggleCameraEnabled(stateful: IStateful) {
return isMobileBrowser() && Number(videoInput?.length) > 1;
}
/**
* Applies audio constraints to the local Jitsi audio track.
*
* @param {Function|Object} stateful - The redux store or {@code getState} function.
* @param {IAudioSettings} settings - The audio settings to apply.
* @returns {Promise<void>}
*/
export async function applyAudioConstraints(stateful: IStateful, settings: IAudioSettings) {
const state = toState(stateful);
const track = getLocalJitsiAudioTrack(state);
if (!track) {
logger.debug('No local audio track found');
return;
}
if (!isAdvancedAudioSettingsEnabled(state)) {
logger.debug('Advanced audio settings disabled');
return;
}
try {
await track.applyConstraints(settings);
} catch (error) {
logger.error('Failed to apply audio constraints ', error);
}
}

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/tracks');
export default getLogger('app:tracks');

View File

@@ -4,7 +4,6 @@ import { IStore } from '../../app/types';
import { _RESET_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
import { getCurrentConference } from '../conference/functions';
import {
SET_AUDIO_MUTED,
SET_CAMERA_FACING_MODE,
SET_SCREENSHARE_MUTED,
SET_VIDEO_MUTED,
@@ -46,15 +45,6 @@ import './subscriber';
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case SET_AUDIO_MUTED:
if (!action.muted
&& isUserInteractionRequiredForUnmute(store.getState())) {
return;
}
_setMuted(store, action, MEDIA_TYPE.AUDIO);
break;
case SET_CAMERA_FACING_MODE: {
// XXX The camera facing mode of a MediaStreamTrack can be specified
// only at initialization time and then it can only be toggled. So in

View File

@@ -1,3 +1,5 @@
import { IStore } from '../../app/types';
import { SET_AUDIO_MUTED } from '../media/actionTypes';
import {
MEDIA_TYPE,
VIDEO_TYPE
@@ -8,8 +10,11 @@ import {
TRACK_UPDATED
} from './actionTypes';
import {
toggleScreensharing
createLocalTracksA,
toggleScreensharing,
trackMuteUnmuteFailed
} from './actions.native';
import { getLocalTrack, setTrackMuted } from './functions.any';
import './middleware.any';
@@ -23,11 +28,15 @@ import './middleware.any';
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case SET_AUDIO_MUTED: {
_setMuted(store, action);
break;
}
case TRACK_UPDATED: {
const { jitsiTrack, local } = action.track;
if (local && jitsiTrack.isMuted()
&& jitsiTrack.type === MEDIA_TYPE.VIDEO && jitsiTrack.videoType === VIDEO_TYPE.DESKTOP) {
&& jitsiTrack.type === MEDIA_TYPE.VIDEO && jitsiTrack.videoType === VIDEO_TYPE.DESKTOP) {
store.dispatch(toggleScreensharing(false));
}
break;
@@ -36,3 +45,32 @@ MiddlewareRegistry.register(store => next => action => {
return next(action);
});
/**
* Mutes or unmutes a local track with a specific media type.
*
* @param {Store} store - The redux store in which the specified action is dispatched.
* @param {Action} action - The redux action dispatched in the specified store.
* @private
* @returns {void}
*/
function _setMuted(store: IStore, { ensureTrack, muted }: {
ensureTrack: boolean; muted: boolean; }) {
const { dispatch, getState } = store;
const state = getState();
const localTrack = getLocalTrack(state['features/base/tracks'], MEDIA_TYPE.AUDIO, /* includePending */ true);
if (localTrack) {
// The `jitsiTrack` property will have a value only for a localTrack for which `getUserMedia` has already
// completed. If there's no `jitsiTrack`, then the `muted` state will be applied once the `jitsiTrack` is
// created.
const { jitsiTrack } = localTrack;
if (jitsiTrack) {
setTrackMuted(jitsiTrack, muted, state, dispatch)
.catch(() => dispatch(trackMuteUnmuteFailed(localTrack, muted)));
}
} else if (!muted && ensureTrack) {
dispatch(createLocalTracksA({ devices: [ MEDIA_TYPE.AUDIO ] }));
}
}

View File

@@ -3,12 +3,15 @@ import { AnyAction } from 'redux';
import { IStore } from '../../app/types';
import { hideNotification } from '../../notifications/actions';
import { isPrejoinPageVisible } from '../../prejoin/functions';
import { setAudioSettings } from '../../settings/actions.web';
import { getAvailableDevices } from '../devices/actions.web';
import { setScreenshareMuted } from '../media/actions';
import { SET_AUDIO_MUTED } from '../media/actionTypes';
import { gumPending, setScreenshareMuted } from '../media/actions';
import {
MEDIA_TYPE,
VIDEO_TYPE
} from '../media/constants';
import { IGUMPendingState } from '../media/types';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import {
@@ -20,14 +23,19 @@ import {
TRACK_UPDATED
} from './actionTypes';
import {
createLocalTracksA,
showNoDataFromSourceVideoError,
toggleScreensharing,
trackMuteUnmuteFailed,
trackNoDataFromSourceNotificationInfoChanged
} from './actions.web';
import {
getTrackByJitsiTrack, logTracksForParticipant
getLocalJitsiAudioTrackSettings,
getLocalTrack,
getTrackByJitsiTrack, isUserInteractionRequiredForUnmute, logTracksForParticipant,
setTrackMuted
} from './functions.web';
import { ITrack } from './types';
import { ITrack, ITrackOptions } from './types';
import './middleware.any';
@@ -138,7 +146,15 @@ MiddlewareRegistry.register(store => next => action => {
return result;
}
case SET_AUDIO_MUTED: {
if (!action.muted
&& isUserInteractionRequiredForUnmute(store.getState())) {
return;
}
_setMuted(store, action);
break;
}
}
return next(action);
@@ -207,3 +223,47 @@ function _removeNoDataFromSourceNotification({ getState, dispatch }: IStore, tra
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, undefined));
}
}
/**
* Mutes or unmutes a local track with a specific media type.
*
* @param {Store} store - The redux store in which the specified action is
* dispatched.
* @param {Action} action - The redux action dispatched in the specified store.
* @private
* @returns {void}
*/
function _setMuted(store: IStore, { ensureTrack, muted }: {
ensureTrack: boolean; muted: boolean; }) {
const { dispatch, getState } = store;
const state = getState();
const localTrack = getLocalTrack(state['features/base/tracks'], MEDIA_TYPE.AUDIO, /* includePending */ true);
if (localTrack) {
// The `jitsiTrack` property will have a value only for a localTrack for which `getUserMedia` has already
// completed. If there's no `jitsiTrack`, then the `muted` state will be applied once the `jitsiTrack` is
// created.
const { jitsiTrack } = localTrack;
if (jitsiTrack) {
setTrackMuted(jitsiTrack, muted, state, dispatch)
.catch(() => {
dispatch(trackMuteUnmuteFailed(localTrack, muted));
});
}
} else if (!muted && ensureTrack) {
// TODO(saghul): reconcile these 2 types.
dispatch(gumPending([ MEDIA_TYPE.AUDIO ], IGUMPendingState.PENDING_UNMUTE));
const createTrackOptions: ITrackOptions = {
devices: [ MEDIA_TYPE.AUDIO ],
};
dispatch(createLocalTracksA(createTrackOptions)).then(() => {
dispatch(gumPending([ MEDIA_TYPE.AUDIO ], IGUMPendingState.NONE));
const updatedSettings = getLocalJitsiAudioTrackSettings(getState());
dispatch(setAudioSettings(updatedSettings));
});
}
}

View File

@@ -1,8 +1,10 @@
import { MediaType } from '../media/constants';
import { IAudioSettings } from '../settings/reducer';
export interface ITrackOptions {
cameraDeviceId?: string | null;
constraints?: {
audio?: IAudioSettings;
video?: {
height?: {
ideal?: number;

View File

@@ -17,6 +17,7 @@ interface ITabProps {
icon?: Function;
id: string;
label?: string;
title?: string;
}>;
}
@@ -126,26 +127,34 @@ const Tabs = ({
aria-label = { accessibilityLabel }
className = { cx(classes.container, className) }
role = 'tablist'>
{tabs.map((tab, index) => (
<button
aria-controls = { tab.controlsId }
aria-label = { tab.accessibilityLabel }
aria-selected = { selected === tab.id }
className = { cx(classes.tab, selected === tab.id && 'selected', isMobile && 'is-mobile') }
disabled = { tab.disabled }
id = { tab.id }
key = { tab.id }
onClick = { onClick(tab.id) }
onKeyDown = { onKeyDown(index) }
role = 'tab'
tabIndex = { selected === tab.id ? undefined : -1 }>
{tab.icon && <Icon
className = { classes.icon }
src = { tab.icon } />}
{tab.label}
{tab.countBadge && <span className = { classes.badge }>{tab.countBadge}</span>}
</button>
))}
{
tabs.map((tab, index) => (
<button
aria-controls = { tab.controlsId }
aria-label = { tab.accessibilityLabel }
aria-selected = { selected === tab.id }
className = { cx(classes.tab, selected === tab.id && 'selected', isMobile && 'is-mobile') }
disabled = { tab.disabled }
id = { tab.id }
key = { tab.id }
onClick = { onClick(tab.id) }
onKeyDown = { onKeyDown(index) }
role = 'tab'
tabIndex = { selected === tab.id ? undefined : -1 }
title = { tab.title }>
{
tab.icon && <Icon
className = { classes.icon }
src = { tab.icon } />
}
{ tab.label }
{
tab.countBadge && <span className = { classes.badge }>
{ tab.countBadge }
</span>
}
</button>
))}
</div>
);
};

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('features/base/util');
export default getLogger('app:util');

View File

@@ -1,5 +1,3 @@
import { getLogger } from '../base/logging/functions';
import { FEATURE_KEY } from './constants';
export default getLogger(FEATURE_KEY);
export default getLogger('app:breakout-rooms');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/calendar-sync');
export default getLogger('app:calendar-sync');

View File

@@ -170,7 +170,7 @@ export const microsoftCalendarApi = {
popupAuthWindow = window.open(
authUrl,
'Auth M$',
`Auth M$-${Date.now()}`,
`width=${w}, height=${h}, top=${
(screen.height / 2) - (h / 2)}, left=${
(screen.width / 2) - (w / 2)}`);

View File

@@ -1,6 +1,5 @@
import { IParticipant } from '../base/participants/types';
import { navigate }
from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { navigate } from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../mobile/navigation/routes';
import { OPEN_CHAT } from './actionTypes';
@@ -18,7 +17,7 @@ export * from './actions.any';
* type: OPEN_CHAT
* }}
*/
export function openChat(participant: IParticipant | undefined | Object, disablePolls?: boolean) {
export function openChat(participant?: IParticipant | undefined | Object, disablePolls?: boolean) {
if (disablePolls) {
navigate(screen.conference.chat);
}

View File

@@ -3,7 +3,7 @@ import { WithTranslation } from 'react-i18next';
import { IReduxState, IStore } from '../../app/types';
import { getParticipantDisplayName, isLocalParticipantModerator } from '../../base/participants/functions';
import { getVisitorDisplayName } from '../../visitors/functions';
import { getDisplayName } from '../../visitors/functions';
import { setLobbyChatActiveState, setPrivateMessageRecipient } from '../actions.any';
import { isVisitorChatParticipant } from '../functions';
@@ -84,7 +84,7 @@ export function _mapStateToProps(state: IReduxState, _ownProps: any) {
if (privateMessageRecipient) {
_privateMessageRecipient = _isVisitor
? getVisitorDisplayName(state, privateMessageRecipient.name)
? getDisplayName(state, privateMessageRecipient.name)
: getParticipantDisplayName(state, privateMessageRecipient.id);
}

View File

@@ -10,6 +10,7 @@ import { isGifEnabled, isGifMessage } from '../../../gifs/functions.native';
import { CHAR_LIMIT, MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
import {
getCanReplyToMessage,
getDisplayNameSuffix,
getFormattedTimestamp,
getMessageText,
getPrivateNoticeMessage,
@@ -122,17 +123,17 @@ class ChatMessage extends Component<IChatMessageProps> {
* @returns {React.ReactElement<*> | null}
*/
_renderDisplayName() {
const { message, showDisplayName, t } = this.props;
const { message, showDisplayName } = this.props;
if (!showDisplayName) {
return null;
}
const { displayName, isFromVisitor } = message;
const { displayName } = message;
return (
<Text style = { styles.senderDisplayName }>
{ `${displayName}${isFromVisitor ? ` ${t('visitors.chatIndicator')}` : ''}` }
{ `${displayName}${getDisplayNameSuffix(message)}` }
</Text>
);
}

View File

@@ -78,32 +78,6 @@ interface IProps extends AbstractProps {
*/
_nbUnreadPolls: number;
/**
* Function to send a text message.
*
* @protected
*/
_onSendMessage: Function;
/**
* Function to toggle the chat window.
*/
_onToggleChat: Function;
/**
* Function to display the chat tab.
*
* @protected
*/
_onToggleChatTab: Function;
/**
* Function to display the polls tab.
*
* @protected
*/
_onTogglePollsTab: Function;
/**
* Whether or not to block chat access with a nickname input form.
*/
@@ -244,10 +218,6 @@ const Chat = ({
_messages,
_nbUnreadMessages,
_nbUnreadPolls,
_onSendMessage,
_onToggleChat,
_onToggleChatTab,
_onTogglePollsTab,
_showNamePrompt,
_width,
dispatch,
@@ -512,7 +482,8 @@ const Chat = ({
_focusedTab !== ChatTabs.CHAT && _nbUnreadMessages > 0 ? _nbUnreadMessages : undefined,
id: ChatTabs.CHAT,
controlsId: `${ChatTabs.CHAT}-panel`,
icon: IconMessage
icon: IconMessage,
title: t('chat.tabs.chat')
}
];
@@ -522,7 +493,8 @@ const Chat = ({
countBadge: _focusedTab !== ChatTabs.POLLS && _nbUnreadPolls > 0 ? _nbUnreadPolls : undefined,
id: ChatTabs.POLLS,
controlsId: `${ChatTabs.POLLS}-panel`,
icon: IconInfo
icon: IconInfo,
title: t('chat.tabs.polls')
});
}
@@ -532,7 +504,8 @@ const Chat = ({
countBadge: undefined,
id: ChatTabs.CLOSED_CAPTIONS,
controlsId: `${ChatTabs.CLOSED_CAPTIONS}-panel`,
icon: IconSubtitles
icon: IconSubtitles,
title: t('chat.tabs.closedCaptions')
});
}
@@ -542,7 +515,8 @@ const Chat = ({
countBadge: undefined,
id: ChatTabs.FILE_SHARING,
controlsId: `${ChatTabs.FILE_SHARING}-panel`,
icon: IconShareDoc
icon: IconShareDoc,
title: t('chat.tabs.fileSharing')
});
}

View File

@@ -9,7 +9,7 @@ import { getParticipantById, getParticipantDisplayName, isPrivateChatEnabled } f
import Popover from '../../../base/popover/components/Popover.web';
import Message from '../../../base/react/components/web/Message';
import { MESSAGE_TYPE_LOCAL } from '../../constants';
import { getFormattedTimestamp, getMessageText, getPrivateNoticeMessage } from '../../functions';
import { getDisplayNameSuffix, getFormattedTimestamp, getMessageText, getPrivateNoticeMessage } from '../../functions';
import { IChatMessageProps } from '../../types';
import MessageMenu from './MessageMenu';
@@ -226,13 +226,13 @@ const ChatMessage = ({
* @returns {React$Element<*>}
*/
function _renderDisplayName() {
const { displayName, isFromVisitor = false } = message;
const { displayName } = message;
return (
<div
aria-hidden = { true }
className = { cx('display-name', classes.displayName) }>
{`${displayName}${isFromVisitor ? ` ${t('visitors.chatIndicator')}` : ''}`}
{`${displayName}${getDisplayNameSuffix(message)}`}
</div>
);
}
@@ -387,7 +387,8 @@ const ChatMessage = ({
</div>
{shouldDisplayMenuOnRight && (
<div className = { classes.sideBySideContainer }>
{!message.privateMessage && !message.lobbyChat && <div>
{!message.privateMessage && !message.lobbyChat
&& !message.isReaction && <div>
<div className = { classes.optionsButtonContainer }>
{isHovered && <ReactButton
messageId = { message.messageId }

View File

@@ -265,3 +265,21 @@ export function isVisitorChatParticipant(
): participant is IVisitorChatParticipant {
return Boolean(participant && 'isVisitor' in participant && participant.isVisitor === true);
}
/**
* Returns a suffix to be appended to the display name based on the message origin.
*
* @param {IMessage} message - The message.
* @returns {string} The suffix, if any.
*/
export function getDisplayNameSuffix(message: IMessage): string {
let suffix = '';
if (message.isFromVisitor) {
suffix = ` ${i18next.t('visitors.chatIndicator')}`;
} else if (message.isFromGuest) {
suffix = ` ${i18next.t('chat.guestsChatIndicator')}`;
}
return suffix;
}

View File

@@ -35,7 +35,7 @@ import { pushReactions } from '../reactions/actions.any';
import { ENDPOINT_REACTION_NAME } from '../reactions/constants';
import { getReactionMessageFromBuffer, isReactionsEnabled } from '../reactions/functions.any';
import { showToolbox } from '../toolbox/actions';
import { getVisitorDisplayName } from '../visitors/functions';
import { getDisplayName } from '../visitors/functions';
import {
ADD_MESSAGE,
@@ -51,8 +51,9 @@ import {
clearMessages,
closeChat,
notifyPrivateRecipientsChanged,
openChat,
setPrivateMessageRecipient
} from './actions.any';
} from './actions';
import { ChatPrivacyDialog } from './components';
import {
ChatTabs,
@@ -63,7 +64,12 @@ import {
MESSAGE_TYPE_REMOTE,
MESSAGE_TYPE_SYSTEM
} from './constants';
import { getUnreadCount, isSendGroupChatDisabled, isVisitorChatParticipant } from './functions';
import {
getDisplayNameSuffix,
getUnreadCount,
isSendGroupChatDisabled,
isVisitorChatParticipant
} from './functions';
import { INCOMING_MSG_SOUND_FILE } from './sounds';
import './subscriber';
@@ -366,7 +372,7 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
JitsiConferenceEvents.MESSAGE_RECEIVED,
/* eslint-disable max-params */
(participantId: string, message: string, timestamp: number,
displayName: string, isFromVisitor: boolean, messageId: string) => {
displayName: string, isFromVisitor: boolean, messageId: string, source: string) => {
/* eslint-enable max-params */
_onConferenceMessageReceived(store, {
// in case of messages coming from visitors we can have unknown id
@@ -376,7 +382,9 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
displayName,
isFromVisitor,
messageId,
privateMessage: false });
source,
privateMessage: false
});
if (isSendGroupChatDisabled(store.getState()) && participantId) {
const participant = getParticipantById(store, participantId);
@@ -426,9 +434,9 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
* @returns {void}
*/
function _onConferenceMessageReceived(store: IStore,
{ displayName, isFromVisitor, message, messageId, participantId, privateMessage, timestamp }: {
{ displayName, isFromVisitor, message, messageId, participantId, privateMessage, timestamp, source }: {
displayName?: string; isFromVisitor?: boolean; message: string; messageId?: string;
participantId: string; privateMessage: boolean; timestamp: number; }
participantId: string; privateMessage: boolean; source?: string; timestamp: number; }
) {
const isGif = isGifEnabled(store.getState()) && isGifMessage(message);
@@ -447,7 +455,8 @@ function _onConferenceMessageReceived(store: IStore,
privateMessage,
lobbyChat: false,
timestamp,
messageId
messageId,
source
}, true, isGif);
}
@@ -556,9 +565,9 @@ function getLobbyChatDisplayName(state: IReduxState, participantId: string) {
* @returns {void}
*/
function _handleReceivedMessage({ dispatch, getState }: IStore,
{ displayName, isFromVisitor, lobbyChat, message, messageId, participantId, privateMessage, timestamp }: {
{ displayName, isFromVisitor, lobbyChat, message, messageId, participantId, privateMessage, source, timestamp }: {
displayName?: string; isFromVisitor?: boolean; lobbyChat: boolean; message: string;
messageId?: string; participantId: string; privateMessage: boolean; timestamp: number; },
messageId?: string; participantId: string; privateMessage: boolean; source?: string; timestamp: number; },
shouldPlaySound = true,
isReaction = false
) {
@@ -571,35 +580,33 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
dispatch(playSound(INCOMING_MSG_SOUND_ID));
}
// Provide a default for the case when a message is being
// backfilled for a participant that has left the conference.
const participant = getParticipantById(state, participantId) || { local: undefined };
const participant = getParticipantById(state, participantId);
const localParticipant = getLocalParticipant(getState);
let _displayName, displayNameToShow;
let _displayName;
if (lobbyChat) {
displayNameToShow = _displayName = getLobbyChatDisplayName(state, participantId);
_displayName = getLobbyChatDisplayName(state, participantId);
} else if (isFromVisitor) {
_displayName = getVisitorDisplayName(state, displayName);
displayNameToShow = `${_displayName} ${i18next.t('visitors.chatIndicator')}`;
_displayName = getDisplayName(state, displayName);
} else if (!participant) {
_displayName = getDisplayName(state, displayName);
} else {
displayNameToShow = _displayName = getParticipantDisplayName(state, participantId);
_displayName = getParticipantDisplayName(state, participantId);
}
const hasRead = participant.local || isChatOpen;
const hasRead = participant?.local || isChatOpen;
const timestampToDate = timestamp ? new Date(timestamp) : new Date();
const millisecondsTimestamp = timestampToDate.getTime();
// skip message notifications on join (the messages having timestamp - coming from the history)
const shouldShowNotification = userSelectedNotifications?.['notify.chatMessages']
&& !hasRead && !isReaction && (!timestamp || lobbyChat);
dispatch(addMessage({
const newMessage = {
displayName: _displayName,
hasRead,
participantId,
messageType: participant.local ? MESSAGE_TYPE_LOCAL : MESSAGE_TYPE_REMOTE,
messageType: participant?.local ? MESSAGE_TYPE_LOCAL : MESSAGE_TYPE_REMOTE,
message,
privateMessage,
lobbyChat,
@@ -607,13 +614,29 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
timestamp: millisecondsTimestamp,
messageId,
isReaction,
isFromVisitor
}));
isFromVisitor,
isFromGuest: source === 'guest'
};
dispatch(addMessage(newMessage));
let notificationDisplayName = _displayName;
// source can be 'token' or 'guest'. When it is 'guest', we append a guest indicator
// to the display name to notify users that this message can be anything, while the token one is coming
// from a trusted source (the jwt token, the name can be locked from the backend if the
// token has the feature 'name-readonly')
if (isFromVisitor || (!participant && source === 'guest')) {
// @ts-ignore
notificationDisplayName = `${_displayName} ${getDisplayNameSuffix(newMessage)}`;
}
if (shouldShowNotification) {
dispatch(showMessageNotification({
title: displayNameToShow,
description: message
title: notificationDisplayName,
description: message,
customActionNameKey: [ 'notify.openChat' ],
customActionHandler: [ () => dispatch(openChat()) ]
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
}
@@ -623,7 +646,7 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
APP.API.notifyReceivedChatMessage({
body: message,
from: participantId,
nick: displayNameToShow,
nick: notificationDisplayName,
privateMessage,
ts: timestamp
});
@@ -665,7 +688,7 @@ function _persistSentPrivateMessage({ dispatch, getState }: IStore, recipient: I
const recipientName
= recipient.isVisitor
? getVisitorDisplayName(state, recipient.name)
? getDisplayName(state, recipient.name)
: (isLobbyPrivateMessage
? lobbyMessageRecipient?.name
: getParticipantDisplayName(getState, recipient?.id));

View File

@@ -68,6 +68,7 @@ ReducerRegistry.register<IChatState>('features/chat', (state = DEFAULT_STATE, ac
const newMessage: IMessage = {
displayName: action.displayName,
error: action.error,
isFromGuest: Boolean(action.isFromGuest),
isFromVisitor: Boolean(action.isFromVisitor),
participantId: action.participantId,
isReaction: action.isReaction,

View File

@@ -5,6 +5,7 @@ import { IStore } from '../app/types';
export interface IMessage {
displayName: string;
error?: Object;
isFromGuest?: boolean;
isFromVisitor?: boolean;
isReaction: boolean;
lobbyChat: boolean;

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/chrome-banner');
export default getLogger('app:chrome-banner');

View File

@@ -579,7 +579,7 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { startCarMode } = state['features/base/settings'];
const { enabled: audioOnlyEnabled } = state['features/base/audio-only'];
const brandingStyles = backgroundColor ? {
backgroundColor
background: backgroundColor
} : undefined;
return {

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/conference');
export default getLogger('app:conference');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/desktop-picker');
export default getLogger('app:desktop-picker');

View File

@@ -1,3 +1,5 @@
import { isEqual } from 'lodash-es';
import { createDeviceChangedEvent } from '../analytics/AnalyticsEvents';
import { sendAnalytics } from '../analytics/functions';
import { IStore } from '../app/types';
@@ -7,8 +9,12 @@ import {
} from '../base/devices/actions';
import { getDeviceLabelById, setAudioOutputDeviceId } from '../base/devices/functions';
import { updateSettings } from '../base/settings/actions';
import { toggleUpdateAudioSettings } from '../base/tracks/actions.web';
import { getLocalJitsiAudioTrack } from '../base/tracks/functions.any';
import { toggleNoiseSuppression } from '../noise-suppression/actions';
import { setScreenshareFramerate } from '../screen-share/actions';
import { setScreenshareFramerate } from '../screen-share/actions.web';
import { setAudioSettings } from '../settings/actions.web';
import { disposePreviewAudioTrack } from '../settings/functions.web';
import { getAudioDeviceSelectionDialogProps, getVideoDeviceSelectionDialogProps } from './functions';
import logger from './logger';
@@ -22,16 +28,21 @@ import logger from './logger';
* @returns {Function}
*/
export function submitAudioDeviceSelectionTab(newState: any, isDisplayedOnWelcomePage: boolean) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const currentState = getAudioDeviceSelectionDialogProps(getState(), isDisplayedOnWelcomePage);
const isSelectedAudioInputIdChanged = newState.selectedAudioInputId
&& newState.selectedAudioInputId !== currentState.selectedAudioInputId;
if (newState.selectedAudioInputId && newState.selectedAudioInputId !== currentState.selectedAudioInputId) {
if (isSelectedAudioInputIdChanged) {
dispatch(updateSettings({
userSelectedMicDeviceId: newState.selectedAudioInputId,
userSelectedMicDeviceLabel:
getDeviceLabelById(getState(), newState.selectedAudioInputId, 'audioInput')
}));
await disposePreviewAudioTrack(getState());
dispatch(setAudioSettings(newState.audioSettings));
dispatch(setAudioInputDevice(newState.selectedAudioInputId));
}
@@ -58,6 +69,21 @@ export function submitAudioDeviceSelectionTab(newState: any, isDisplayedOnWelcom
if (newState.noiseSuppressionEnabled !== currentState.noiseSuppressionEnabled) {
dispatch(toggleNoiseSuppression());
}
if (!isEqual(newState.audioSettings, currentState.audioSettings) && !isSelectedAudioInputIdChanged && !newState.noiseSuppressionEnabled) {
const state = getState();
const jitsiTrack = getLocalJitsiAudioTrack(state);
if (!jitsiTrack) {
logger.debug('No local audio track found');
dispatch(setAudioSettings(newState.audioSettings));
return;
}
await disposePreviewAudioTrack(getState());
dispatch(toggleUpdateAudioSettings(newState.audioSettings));
}
};
}

View File

@@ -1,30 +1,41 @@
import { Theme } from '@mui/material';
import clsx from 'clsx';
import React from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { withStyles } from 'tss-react/mui';
import { IReduxState, IStore } from '../../app/types';
import { isAdvancedAudioSettingsEnabled } from '../../base/config/functions.any';
import { getAvailableDevices } from '../../base/devices/actions.web';
import AbstractDialogTab, {
type IProps as AbstractDialogTabProps
} from '../../base/dialog/components/web/AbstractDialogTab';
import { translate } from '../../base/i18n/functions';
import { createLocalTrack } from '../../base/lib-jitsi-meet/functions.web';
import { IAudioSettings } from '../../base/settings/reducer';
import Checkbox from '../../base/ui/components/web/Checkbox';
import { setPreviewAudioTrack } from '../../settings/actions.web';
import { disposeTrack } from '../../settings/functions.web';
import { iAmVisitor as iAmVisitorCheck } from '../../visitors/functions';
import logger from '../logger';
import AudioInputPreview from './AudioInputPreview';
import AudioOutputPreview from './AudioOutputPreview';
import AudioInputPreview from './AudioInputPreview.web';
import AudioOutputPreview from './AudioOutputPreview.web';
import DeviceHidContainer from './DeviceHidContainer.web';
import DeviceSelector from './DeviceSelector.web';
/**
* The type of the React {@code Component} props of {@link AudioDevicesSelection}.
*/
interface IProps extends AbstractDialogTabProps, WithTranslation {
/**
* The audio local track settings.
*/
audioSettings?: IAudioSettings;
/**
* All known audio and video devices split by type. This prop comes from
* the app state.
@@ -98,11 +109,21 @@ interface IProps extends AbstractDialogTabProps, WithTranslation {
*/
iAmVisitor: boolean;
/**
* Whether the advanced audio settings are enabled from config.
*/
isAdvancedAudioSettingsConfigEnabled: boolean;
/**
* Whether noise suppression is on or not.
*/
noiseSuppressionEnabled: boolean;
/**
* The audio track that is used for previewing the audio input.
*/
previewAudioTrack: any | null;
/**
* The id of the audio input device to preview.
*/
@@ -114,19 +135,20 @@ interface IProps extends AbstractDialogTabProps, WithTranslation {
selectedAudioOutputId: string;
}
/**
* The type of the React {@code Component} state of {@link AudioDevicesSelection}.
*/
interface IState {
/**
* The JitsiTrack to use for previewing audio input.
*/
previewAudioTrack?: any | null;
}
const styles = (theme: Theme) => {
return {
checkbox: {
width: 'max-content'
},
checkboxGrid: {
display: 'grid',
gridTemplateColumns: 'auto auto',
gap: theme.spacing(3),
margin: `${theme.spacing(3)} 0`
},
container: {
display: 'flex',
flexDirection: 'column' as const,
@@ -134,32 +156,33 @@ const styles = (theme: Theme) => {
width: '100%'
},
hidden: {
display: 'none'
},
inputContainer: {
marginBottom: theme.spacing(3)
},
outputContainer: {
margin: `${theme.spacing(5)} 0`,
display: 'flex',
alignItems: 'flex-end'
alignItems: 'flex-end',
margin: `${theme.spacing(5)} 0`
},
outputButton: {
marginLeft: theme.spacing(3)
},
noiseSuppressionContainer: {
marginBottom: theme.spacing(5)
}
};
};
/**
* React {@code Component} for previewing audio and video input/output devices.
*
* @augments Component
*/
class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
/**
* Whether current component is mounted or not.
@@ -181,9 +204,6 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
previewAudioTrack: null
};
this._unMounted = true;
}
@@ -224,9 +244,29 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
*/
override componentWillUnmount() {
this._unMounted = true;
this._disposeAudioInputPreview();
disposeTrack(this.props.previewAudioTrack);
}
/**
* Toggles the audio settings based on the input change event and updates the state.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onToggleAudioSettings = ({ target: { checked, name } }: React.ChangeEvent<HTMLInputElement>) => {
const { audioSettings } = this.props;
const newValue = name === 'channelCount' ? (checked ? 2 : 1) : checked;
super._onChange({
audioSettings: {
...audioSettings,
[name]: newValue
}
});
};
/**
* Implements React's {@link Component#render()}.
*
@@ -234,11 +274,13 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
*/
override render() {
const {
audioSettings,
hasAudioPermission,
hideAudioInputPreview,
hideAudioOutputPreview,
hideDeviceHIDContainer,
hideNoiseSuppression,
isAdvancedAudioSettingsConfigEnabled,
iAmVisitor,
noiseSuppressionEnabled,
selectedAudioOutputId,
@@ -248,6 +290,13 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
const classes = withStyles.getClasses(this.props);
const isAudioSettingsEnabled = Boolean(audioSettings?.autoGainControl || audioSettings?.channelCount === 2 || audioSettings?.echoCancellation || audioSettings?.noiseSuppression);
const shouldDisplayNoiseSuppressionCheckbox = !hideNoiseSuppression && !iAmVisitor;
const shouldDisplayAdvancedAudioSettingsCheckboxes = !hideNoiseSuppression && !iAmVisitor && isAdvancedAudioSettingsConfigEnabled && Boolean(audioSettings);
const shouldDisabledNoiseSupressionCheckbox = shouldDisplayAdvancedAudioSettingsCheckboxes && (isAudioSettingsEnabled && !noiseSuppressionEnabled);
return (
<div className = { classes.container }>
{!iAmVisitor && <div
@@ -255,9 +304,60 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
className = { classes.inputContainer }>
{this._renderSelector(audioInput)}
</div>}
{!hideAudioInputPreview && hasAudioPermission && !iAmVisitor
&& <AudioInputPreview
track = { this.state.previewAudioTrack } />}
&& <AudioInputPreview
track = { this.props.previewAudioTrack } />}
<fieldset className = { classes.checkboxGrid }>
<Checkbox
checked = { Boolean(audioSettings?.echoCancellation) }
className = { clsx(classes.checkbox, {
[classes.hidden]: !shouldDisplayAdvancedAudioSettingsCheckboxes,
}) }
disabled = { noiseSuppressionEnabled }
label = { t('toolbar.advancedAudioSettings.aec.label') }
name = { 'echoCancellation' }
onChange = { this._onToggleAudioSettings } />
<Checkbox
checked = { Boolean(audioSettings?.channelCount === 2) }
className = { clsx(classes.checkbox, {
[classes.hidden]: !shouldDisplayAdvancedAudioSettingsCheckboxes,
}) }
disabled = { noiseSuppressionEnabled }
label = { t('toolbar.advancedAudioSettings.stereo.label') }
name = { 'channelCount' }
onChange = { this._onToggleAudioSettings } />
<Checkbox
checked = { Boolean(audioSettings?.autoGainControl) }
className = { clsx(classes.checkbox, {
[classes.hidden]: !shouldDisplayAdvancedAudioSettingsCheckboxes,
}) }
disabled = { noiseSuppressionEnabled }
label = { t('toolbar.advancedAudioSettings.agc.label') }
name = { 'autoGainControl' }
onChange = { this._onToggleAudioSettings } />
<Checkbox
checked = { Boolean(audioSettings?.noiseSuppression) }
className = { clsx(classes.checkbox, {
[classes.hidden]: !shouldDisplayAdvancedAudioSettingsCheckboxes,
}) }
disabled = { noiseSuppressionEnabled }
label = { t('toolbar.advancedAudioSettings.ns.label') }
name = { 'noiseSuppression' }
onChange = { this._onToggleAudioSettings } />
<Checkbox
checked = { noiseSuppressionEnabled }
className = { clsx(classes.checkbox, {
[classes.hidden]: !shouldDisplayNoiseSuppressionCheckbox,
}) }
disabled = { shouldDisabledNoiseSupressionCheckbox }
label = { t('toolbar.enableNoiseSuppression') }
// eslint-disable-next-line react/jsx-no-bind
onChange = { () => super._onChange({
noiseSuppressionEnabled: !noiseSuppressionEnabled
}) } />
</fieldset>
<div
aria-live = 'polite'
className = { classes.outputContainer }>
@@ -267,17 +367,7 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
className = { classes.outputButton }
deviceId = { selectedAudioOutputId } />}
</div>
{!hideNoiseSuppression && !iAmVisitor && (
<div className = { classes.noiseSuppressionContainer }>
<Checkbox
checked = { noiseSuppressionEnabled }
label = { t('toolbar.enableNoiseSuppression') }
// eslint-disable-next-line react/jsx-no-bind
onChange = { () => super._onChange({
noiseSuppressionEnabled: !noiseSuppressionEnabled
}) } />
</div>
)}
{!hideDeviceHIDContainer && !iAmVisitor
&& <DeviceHidContainer />}
</div>
@@ -292,13 +382,13 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
* @returns {void}
*/
_createAudioInputTrack(deviceId: string) {
const { hideAudioInputPreview } = this.props;
const { hideAudioInputPreview, previewAudioTrack } = this.props;
if (hideAudioInputPreview) {
return;
}
return this._disposeAudioInputPreview()
return disposeTrack(previewAudioTrack)
.then(() => createLocalTrack('audio', deviceId, 5000))
.then(jitsiLocalTrack => {
if (this._unMounted) {
@@ -306,29 +396,14 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
return;
}
this.props.dispatch(setPreviewAudioTrack(jitsiLocalTrack));
this.setState({
previewAudioTrack: jitsiLocalTrack
});
})
.catch(() => {
this.setState({
previewAudioTrack: null
});
this.props.dispatch(setPreviewAudioTrack(null));
});
}
/**
* Utility function for disposing the current audio input preview.
*
* @private
* @returns {Promise}
*/
_disposeAudioInputPreview(): Promise<any> {
return this.state.previewAudioTrack
? this.state.previewAudioTrack.dispose() : Promise.resolve();
}
/**
* Creates a DeviceSelector instance based on the passed in configuration.
*
@@ -362,8 +437,8 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
id: 'audioInput',
label: 'settings.selectMic',
onSelect: (selectedAudioInputId: string) => super._onChange({ selectedAudioInputId }),
selectedDeviceId: this.state.previewAudioTrack
? this.state.previewAudioTrack.getDeviceId() : this.props.selectedAudioInputId
selectedDeviceId: this.props.previewAudioTrack
? this.props.previewAudioTrack.getDeviceId() : this.props.selectedAudioInputId
};
let audioOutput;
@@ -389,7 +464,9 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, IState> {
const mapStateToProps = (state: IReduxState) => {
return {
availableDevices: state['features/base/devices'].availableDevices ?? {},
iAmVisitor: iAmVisitorCheck(state)
isAdvancedAudioSettingsConfigEnabled: isAdvancedAudioSettingsEnabled(state),
iAmVisitor: iAmVisitorCheck(state),
previewAudioTrack: state['features/settings'].previewAudioTrack
};
};

View File

@@ -22,6 +22,7 @@ import {
getUserSelectedMicDeviceId,
getUserSelectedOutputDeviceId
} from '../base/settings/functions.web';
import { getLocalJitsiAudioTrackSettings } from '../base/tracks/functions.any';
import { isNoiseSuppressionEnabled } from '../noise-suppression/functions';
import { isPrejoinPageVisible } from '../prejoin/functions';
import { SS_DEFAULT_FRAME_RATE, SS_SUPPORTED_FRAMERATES } from '../settings/constants';
@@ -50,6 +51,7 @@ export function getAudioDeviceSelectionDialogProps(stateful: IStateful, isDispla
const deviceHidSupported = isDeviceHidSupported() && getWebHIDFeatureConfig(state);
const noiseSuppressionEnabled = isNoiseSuppressionEnabled(state);
const hideNoiseSuppression = isPrejoinPageVisible(state) || isDisplayedOnWelcomePage;
const audioSettings = state['features/settings'].audioSettings ?? getLocalJitsiAudioTrackSettings(state);
// When the previews are disabled we don't need multiple audio input support in order to change the mic. This is the
// case for Safari on iOS.
@@ -71,6 +73,7 @@ export function getAudioDeviceSelectionDialogProps(stateful: IStateful, isDispla
// we fill the device selection dialog with the devices that are currently
// used or if none are currently used with what we have in settings(user selected)
return {
audioSettings,
disableAudioInputChange,
disableDeviceChange: !JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(),
hasAudioPermission: permissions.audio,

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/device-selection');
export default getLogger('app:device-selection');

View File

@@ -1,3 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/dropbox');
export default getLogger('app:dropbox');

View File

@@ -2,6 +2,7 @@ import { IReduxState } from '../app/types';
import { IStateful } from '../base/app/types';
import { toState } from '../base/redux/functions';
import { cleanSvg } from './functions';
import logger from './logger';
/**
@@ -82,7 +83,7 @@ export const fetchCustomIcons = async (customIcons: Record<string, string>) => {
if (response.ok) {
const svgXml = await response.text();
localCustomIcons[key] = svgXml;
localCustomIcons[key] = cleanSvg(svgXml);
} else {
logger.error(`Failed to fetch ${url}. Status: ${response.status}`);
}

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