Compare commits

...

78 Commits

Author SHA1 Message Date
Hristo Terezov
64307306f9 fix(participants-pane): Allow multiline text in footer context menu
Fixes text truncation issue in participants pane footer context menu
items (the three-dot menu). Menu items now wrap naturally to multiple
lines instead of being truncated mid-word, improving readability for
languages with longer text strings like French.

The fix uses standard CSS properties (whiteSpace, wordBreak,
overflowWrap) without browser-specific prefixes. It is specific to
the footer context menu in the participants pane and does not affect
other context menus.
2025-09-29 14:32:52 -05:00
Дамян Минков
36ce5a1661 feat(token_verification): Adds more token failure reasons on verify room. (#16473)
* feat(token_verification): Adds more token failure reasons on verify room.

* squash: Update resources/prosody-plugins/token/util.lib.lua

Co-authored-by: bgrozev <boris@jitsi.org>

---------

Co-authored-by: bgrozev <boris@jitsi.org>
2025-09-29 08:22:31 -05:00
xinfei.wu
23c831e9b0 fix: check if asapKeyServer is empty string 2025-09-27 08:27:29 -05:00
Дамян Минков
e6fbeb9458 feat(conference): Process unauthenticated access disabled error. (#16465)
* feat(conference): Process unauthenticated access disabled error.

Shows notification with a button to login.

* squash: Fix texts.

* feat(visitors): Propagate and use allowUnauthenticatedAccess.

* squash: Avoids always sending a value, even when not set.

* squash: Rename error.

* squash: Fix comments.

* squash: Move check before log.
2025-09-26 14:05:19 -05:00
damencho
e15a59c994 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2095.0.0+43bbd502...v2097.0.0+58646fc3
2025-09-26 14:05:05 -05:00
Hristo Terezov
f5e1a97d64 feat(i18n): Complete Bulgarian translation with missing keys (#16464)
* feat(i18n): Complete Bulgarian translation with missing keys

- Added 587+ missing Bulgarian translations for all untranslated keys
- Achieved 100% translation coverage (1,469/1,469 keys)
- Maintained consistency with existing Bulgarian terminology and tone
- Removed 34 orphaned keys that weren't present in English version
- Applied proper 4-space indentation formatting
- Sorted all keys alphabetically to match project standards
- Created comprehensive professional Bulgarian localization

---------

Co-authored-by: Дамян Минков <damencho@jitsi.org>
Co-authored-by: bgrozev <boris@jitsi.org>
2025-09-26 11:06:28 -05:00
Calin-Teodor
cd25652182 .github(workflows): add clean Xcode step 2025-09-26 16:35:11 +03:00
damencho
2bf0b1922f feat(visitors): Adds support for visitors voting in polls. 2025-09-26 07:04:02 -05:00
Дамян Минков
469406d7cd feat(polls): Move polls to using a component (#16406)
* squash: Renames module.

* squash: Loads polls component.

* squash: Attach needed logic when components/hosts load.

* squash: Moves to use component.

* squash: Uses json-message format with types.

* squash: Checks for polls support.

* squash: Fixes comments and moves validate polls to backend.

* squash: Fix debian build.

* fix(polls): Fixes polls in breakout rooms.

* squash: Further simplify types.

Separate type that needs to go into ljm and those used only for the UI part.
Simplify answer/voter type to be unified across operations which simplifies and its logic.

* squash: Change voters structure to be {id, name}.

* squash: Update react/features/conference/functions.any.ts

Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org>

* squash: Drops roomJid from messages. Uses the connection information as breakout does.

---------

Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org>
2025-09-25 16:46:06 -05:00
damencho
60679aa2d3 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2094.0.0+13aeca6c...v2095.0.0+43bbd502
2025-09-25 15:32:25 -05:00
Hristo Terezov
319e8d1e4b feat(CLAUDE.md): Add 2025-09-25 08:23:51 -05:00
Calin-Teodor
40b8d6168b feat(base/flags): add warning for unsupported feature flags 2025-09-25 16:21:56 +03:00
Hristo Terezov
753d0399c9 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2091.0.0+518cec5f...v2094.0.0+13aeca6c
2025-09-24 18:33:23 -05:00
Hristo Terezov
2475aff21a feat(RN): Add querySelector polyfill. 2025-09-24 16:48:40 -05:00
damencho
121aabeb25 fix(muc_displayname): Handles few more cases for missing nick element. 2025-09-24 10:32:17 -05:00
damencho
086f01aa5b fix(i18n): Uses language-variant for translations.
This way we can take advantage of internal i18next mechanism for fallback from en-US to en and from es-ES to es and so on.
2025-09-24 08:31:22 -05:00
Mihaela Dumitru
6b6920693b feat(lobby) integrate login in lobby + configs (#16401)
* feat(lobby) integrate login in lobby + configs

* fixed toolboxContainer styles, used HangupButton

* make hangup button visible by default

* use hangup button

* feat(prejoin): fixed indent, import extension

* squash: Restore back wait for owner dialog.

* squash: Drops not used state and functions.

---------

Co-authored-by: Calin-Teodor <calin.chitu@8x8.com>
Co-authored-by: damencho <damencho@jitsi.org>
2025-09-22 16:56:29 +03:00
Calin-Teodor
566b3ba2d5 chore(android): apply edge to edge if supported or enforced 2025-09-22 13:17:32 +03:00
bgrozev
7373123166 fix: Fix the tenant used for webhook proxy. (#16445)
* fix: Fix the tenant used for webhook proxy.

* squash: Linting, skip test if WH proxy is required but not configured.

* ref: Change visitorsLive to use the JaaS utils.

* ref: Move visitorsLive to the specs/jaas.

* squash: Fix import paths.

* fix: Use the iframe configured tenant for iframe tests.
2025-09-19 14:32:36 -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
264 changed files with 5753 additions and 2133 deletions

View File

@@ -139,6 +139,12 @@ jobs:
xcode-select -p
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
xcodebuild -version
- name: clean Xcode
run: |
rm -rf ios/sdk/out
xcodebuild clean \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeetSDK
- name: setup-cocoapods
uses: ruby/setup-ruby@v1
with:
@@ -149,15 +155,13 @@ jobs:
working-directory: ./ios
run: bundle exec pod install --repo-update --deployment
- run: |
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

267
CLAUDE.md Normal file
View File

@@ -0,0 +1,267 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
### Building and Development
- `npm run lint-fix` - Automatically fix linting issues
- `npm run tsc:ci` - Run TypeScript checks for both web and native platforms
- `npm run tsc:web` - TypeScript check for web platform only
- `npm run tsc:native` - TypeScript check for native platform only
- `npm run lint:ci` - Run ESLint without type checking
- `make dev` - Start development server with webpack-dev-server
- `make compile` - Build production bundles
- `make clean` - Clean build directory
- `make all` - Full build (compile + deploy)
### Testing
- `npm test` - Run full test suite using WebDriverIO
- `npm run test-single -- <spec-file>` - Run single test file
- `npm run test-dev` - Run tests against development environment
- `npm run test-dev-single -- <spec-file>` - Run single test in dev mode
### Language Tools
- `npm run lang-sort` - Sort language files
- `npm run lint:lang` - Validate JSON language files
### Platform-Specific TypeScript
TypeScript configuration is split between web and native platforms with separate tsconfig files.
## Architecture Overview
### Multi-Platform Structure
Jitsi Meet supports both web and React Native platforms with platform-specific file extensions and directories:
- `.web.ts/.web.tsx` - Web-specific implementations
- `.native.ts/.native.tsx` - React Native-specific implementations
- `.any.ts/.any.tsx` - Shared cross-platform code
- `.android.ts/.android.tsx` - Android-specific code
- `.ios.ts/.ios.tsx` - iOS-specific code
- `web/` directories - Web-specific components and modules
- `native/` directories - React Native-specific components and modules
- `react/features/mobile/` - Native-only features
### Core Directories
- `react/features/` - Main application features organized by domain (83+ feature modules)
- `modules/` - Legacy JavaScript modules and APIs
- `css/` - SCSS stylesheets compiled to CSS
- `libs/` - Compiled output directory for JavaScript bundles
- `static/` - Static assets and HTML files
- `tests/` - WebDriverIO end-to-end tests
### Feature-Driven Architecture
The application is organized under `react/features/` with each feature containing:
- **`actionTypes.ts`** - Redux action type constants
- **`actions.ts`** - Redux action creators (platform-specific variants with `.any.ts`, `.web.ts`, `.native.ts`)
- **`reducer.ts`** - Redux reducer functions
- **`middleware.ts`** - Redux middleware for side effects
- **`functions.ts`** - Utility functions and selectors
- **`constants.ts`** - Feature-specific constants
- **`logger.ts`** - Feature-specific logger instance
- **`types.ts`** - TypeScript type definitions
### Key Application Files
- `app.js` - Main web application entry point
- `webpack.config.js` - Multi-bundle Webpack configuration
- `Makefile` - Build system for development and production
- `package.json` - Dependencies and scripts with version requirements
### Bundle Architecture
The application builds multiple bundles:
- `app.bundle.js` / `app.bundle.min.js` - Main application bundle (entry: `./app.js`)
- `external_api.js` / `external_api.min.js` - External API for embedders (entry: `./modules/API/external/index.js`)
- `alwaysontop.js` / `alwaysontop.min.js` - Always-on-top window functionality (entry: `./react/features/always-on-top/index.tsx`)
- `close3.js` / `close3.min.js` - Close3 functionality (entry: `./static/close3.js`)
- `face-landmarks-worker.js` / `face-landmarks-worker.min.js` - Face landmarks detection worker (entry: `./react/features/face-landmarks/faceLandmarksWorker.ts`)
- `noise-suppressor-worklet.js` / `noise-suppressor-worklet.min.js` - Audio noise suppression worklet (entry: `./react/features/stream-effects/noise-suppression/NoiseSuppressorWorklet.ts`)
- `screenshot-capture-worker.js` / `screenshot-capture-worker.min.js` - Screenshot capture worker (entry: `./react/features/screenshot-capture/worker.ts`)
### Redux Architecture
Features follow a Redux-based architecture with:
- Actions, reducers, and middleware in each feature directory
- Cross-platform state management
- Modular feature organization with clear boundaries
The codebase uses a registry-based Redux architecture:
- **ReducerRegistry** - Features register their reducers independently
- **MiddlewareRegistry** - Features register middleware without cross-dependencies
- **IReduxState** - Global state is strongly typed with 80+ feature states
### Dependencies
- Uses `lib-jitsi-meet` as the core WebRTC library
- React with TypeScript support
- React Native for mobile applications
- Webpack for bundling with development server
### TypeScript Configuration
- `tsconfig.web.json` - Web platform TypeScript config (excludes native files)
- `tsconfig.native.json` - React Native TypeScript config (excludes web files)
- Strict TypeScript settings with ES2024 target
- Platform-specific module suffixes (`.web`, `.native`)
### Key Base Features
- **`base/app/`** - Application lifecycle management
- **`base/conference/`** - Core conference logic
- **`base/tracks/`** - Media track management
- **`base/participants/`** - Participant management
- **`base/config/`** - Configuration management
- **`base/redux/`** - Redux infrastructure
### Component Patterns
- **Abstract Components** - Base classes for cross-platform components
- **Platform-Specific Components** - Separate implementations in `web/` and `native/` directories
- **Hook-based patterns** - Modern React patterns for component logic
### Testing Framework
- WebDriverIO for end-to-end testing
- Test files are located in `tests/specs/` and use page objects in `tests/pageobjects/`.
- Environment configuration via `.env` files
- Support for Chrome, Firefox, and grid testing
## Development Guidelines
### Adding New Features
1. Create feature directory under `react/features/[feature-name]/`
2. Follow the standard file structure (actionTypes, actions, reducer, etc.)
3. Register reducers and middleware using the registry pattern
4. Define TypeScript interfaces for state and props
5. Use platform-specific files for web/native differences
6. Add feature-specific logger for debugging
### Working with Existing Features
1. Check for existing `.any.ts`, `.web.ts`, `.native.ts` variants
2. Follow established action-reducer-middleware patterns
3. Use existing base utilities rather than creating new ones
4. Leverage abstract components for cross-platform logic
5. Maintain type safety across the entire state tree
### Testing
The project uses WebDriver (WebdriverIO) for end-to-end testing. Test files are located in `tests/specs/` and use page objects in `tests/pageobjects/`.
### Build System
- **Webpack** - Main build system for web bundles
- **Makefile** - Coordinates build process and asset deployment
- **Metro** - React Native bundler (configured in `metro.config.js`)
### Platform-Specific Notes
- Web builds exclude files matching `**/native/*`, `**/*.native.ts`, etc.
- Native builds exclude files matching `**/web/*`, `**/*.web.ts`, etc.
- Use `moduleSuffixes` in TypeScript config to handle platform-specific imports
- Check `tsconfig.web.json` and `tsconfig.native.json` for platform-specific exclusions
## Environment and Setup Requirements
### System Requirements
- **Node.js and npm** are required
- Development server runs at https://localhost:8080/
- Certificate errors in development are expected (self-signed certificates)
### Development Workflow
- Development server proxies to configurable target (default: https://alpha.jitsi.net)
- Hot module replacement enabled for development
- Bundle analysis available via `ANALYZE_BUNDLE=true` environment variable
- Circular dependency detection via `DETECT_CIRCULAR_DEPS=true`
## Code Quality Requirements
- All code must pass `npm run lint:ci` and `npm run tsc:ci` with 0 warnings before committing
- TypeScript strict mode enabled - avoid `any` type
- ESLint config extends `@jitsi/eslint-config`
- Prefer TypeScript for new features, convert existing JavaScript when possible
## Code Style and Standards
### Conventional Commits Format
Follow [Conventional Commits](https://www.conventionalcommits.org) with **mandatory scopes**:
```
feat(feature-name): description
fix(feature-name): description
docs(section): description
```
Available types: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test
### Feature Layout Structure
When adding new features:
```
react/features/sample/
├── actionTypes.ts
├── actions.ts
├── components/
│ ├── AnotherComponent.tsx
│ └── OneComponent.tsx
├── middleware.ts
└── reducer.ts
```
### TypeScript Requirements
- All new features must be written in TypeScript
- Convert JavaScript to TypeScript when modifying existing code
- Import middleware in `react/features/app/middlewares.{any,native,web}.js`
- Import reducers in appropriate registry files
- Avoid `index` files
### Bundle Size Management
- Bundle size limits are enforced to prevent bloat
- For increases, analyze first: `npx webpack -p --analyze-bundle`
- Open analyzer: `npx webpack-bundle-analyzer build/app-stats.json`
- Justify any dependency additions that increase bundle size
## Testing and Quality Assurance
### Tests
- End-to-end tests are defined in the tests/
- Tests run automatically for project member PRs via Jenkins
- Tests cover peer-to-peer, invites, iOS, Android, and web platforms
- Beta testing available at https://beta.meet.jit.si/
### Manual Testing Checklist
- Test with 2 participants (P2P mode)
- Test with 3+ participants (JVB mode)
- Verify audio/video in both modes
- Test mobile apps if changes affect mobile
- Check that TLS certificate chain is complete for mobile app compatibility
## Common Issues and Debugging
### P2P vs JVB Problems
- **Works with 2 participants, fails with 3+**: JVB/firewall issue, check UDP 10000
- **Works on web, fails on mobile apps**: TLS certificate chain issue, need fullchain.pem
- Use the tests from tests/ directory to verify functionality across platforms
### Development Server Issues
- Certificate warnings are normal for development (self-signed)
- Use different backend with WEBPACK_DEV_SERVER_PROXY_TARGET environment variable
- Check firewall settings if local development fails
### Configuration and Customization
- Extensive configuration options documented in handbook
- See `config.js` for client-side options
- Options marked 🚫 are not overwritable through `configOverwrite`
- Reference [Configuration Guide](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-configuration) for details
## Architecture Deep Dive
### Core Application Files
- **`./conference.js`** - Foundation for user-conference interactions (connection, joining, muting)
- **`./modules/external-api`** - External API for iframe integration and events
- **`./lang/`** - Translations in `main-[language].json` files
- **`./css/`** - SCSS files organized by features, matching React feature structure
### State Management Flow
1. Actions dispatched from components
2. Middleware processes side effects
3. Reducers update state
4. Components re-render based on state changes
5. Registry pattern keeps features decoupled
### Cross-Platform Strategy
- Abstract components handle shared logic
- Platform files (.web.ts, .native.ts) handle platform differences
- Build system excludes irrelevant platform files
- TypeScript configs ensure proper platform targeting
## External Resources
- [Jitsi Handbook](https://jitsi.github.io/handbook/) - Comprehensive documentation
- [Community Forum](https://community.jitsi.org/) - Ask questions and get support
- [Architecture Guide](https://jitsi.github.io/handbook/docs/architecture) - System overview
- [Contributing Guidelines](https://jitsi.github.io/handbook/docs/dev-guide/contributing) - Detailed contribution process

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

@@ -24,10 +24,17 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
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 +94,30 @@ public class JitsiMeetActivity extends AppCompatActivity
launch(context, options);
}
public static void addTopBottomInsets(@NonNull Window w, @NonNull View v) {
// Only apply if edge-to-edge is supported (API 30+) or enforced (API 35+)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) 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 +138,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,
@@ -722,6 +723,8 @@ var config = {
// autoKnock: false,
// // Enables the lobby chat. Replaces `enableLobbyChat`.
// enableChat: true,
// // Shows the hangup button in the lobby screen.
// showHangUp: true,
// },
// Configs for the security related UI elements.

View File

@@ -154,6 +154,16 @@ case "$1" in
PROSODY_CONFIG_PRESENT="false"
fi
# Start using the polls component
if ! grep -q "Component \"polls.$JVB_HOSTNAME\"" $PROSODY_HOST_CONFIG ;then
echo -e "\nComponent \"polls.$JVB_HOSTNAME\" \"polls_component\"" >> $PROSODY_HOST_CONFIG
PROSODY_CONFIG_PRESENT="false"
fi
if ! grep -q -- '--"polls";' $PROSODY_HOST_CONFIG ;then
sed -i "s/\"polls\";/--\"polls\";/g" $PROSODY_HOST_CONFIG
PROSODY_CONFIG_PRESENT="false"
fi
# Old versions of jitsi-meet-prosody come with the extra plugin path commented out (https://github.com/jitsi/jitsi-meet/commit/e11d4d3101e5228bf956a69a9e8da73d0aee7949)
# Make sure it is uncommented, as it contains required modules.
if grep -q -- '--plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }' $PROSODY_HOST_CONFIG ;then

View File

@@ -83,7 +83,6 @@ Component "conference.jitmeet.example.com" "muc"
"muc_hide_all";
"muc_meeting_id";
"muc_domain_mapper";
"polls";
--"token_verification";
"muc_rate_limit";
"muc_password_whitelist";
@@ -159,9 +158,10 @@ Component "lobby.jitmeet.example.com" "muc"
modules_enabled = {
"muc_hide_all";
"muc_rate_limit";
"polls";
}
Component "metadata.jitmeet.example.com" "room_metadata_component"
muc_component = "conference.jitmeet.example.com"
breakout_rooms_component = "breakout.jitmeet.example.com"
Component "polls.jitmeet.example.com" "polls_component"

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

@@ -12,13 +12,13 @@
"en": "English",
"eo": "Esperanto",
"es": "Español",
"esUS": "Español (Latinoamérica)",
"es-US": "Español (Latinoamérica)",
"et": "Eesti",
"eu": "Euskara",
"fa": "فارسی",
"fi": "Suomi",
"fr": "Français",
"frCA": "Français (Canada)",
"fr-CA": "Français (Canada)",
"gl": "Galego",
"he": "עברית",
"hi": "हिन्दी",
@@ -43,7 +43,7 @@
"oc": "Occitan",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"pt-BR": "Português (Brasil)",
"ro": "Română",
"ru": "Русский",
"sc": "Sardu",
@@ -56,6 +56,6 @@
"tr": "Türkçe",
"uk": "Українська",
"vi": "Tiếng Việt",
"zhCN": "中文(简体)",
"zhTW": "中文(繁體)"
"zh-CN": "中文(简体)",
"zh-TW": "中文(繁體)"
}

File diff suppressed because it is too large Load Diff

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:",
@@ -279,7 +280,6 @@
"Submit": "Submit",
"Understand": "I understand, keep me muted for now",
"UnderstandAndUnmute": "I understand, please unmute me",
"WaitForHostMsg": "The conference has not yet started because no moderators have yet arrived. If you'd like to become a moderator please log-in. Otherwise, please wait.",
"WaitForHostNoAuthMsg": "The conference has not yet started because no moderators have yet arrived. Please wait.",
"WaitingForHostButton": "Wait for moderator",
"WaitingForHostTitle": "Waiting for a moderator…",
@@ -522,6 +522,7 @@
"tokenAuthFailedWithReasons": "Sorry, you're not allowed to join this call. Possible reasons: {{reason}}",
"tokenAuthUnsupported": "Token URL is not supported.",
"transcribing": "Transcribing",
"unauthenticatedAccessDisabled": "This call requires authentication. Please login in order to proceed.",
"unlockRoom": "Remove meeting $t(lockRoomPassword)",
"user": "User",
"userIdentifier": "User identifier",
@@ -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"
@@ -745,7 +748,8 @@
"notificationTitle": "Lobby",
"passwordJoinButton": "Join",
"title": "Lobby",
"toggleLabel": "Enable lobby"
"toggleLabel": "Enable lobby",
"waitForModerator": "The conference has not yet started because no moderators have yet arrived. If you'd like to become a moderator please log-in. Otherwise, please wait."
},
"localRecording": {
"clientState": {
@@ -862,6 +866,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",
@@ -960,6 +965,9 @@
"by": "By {{ name }}",
"closeButton": "Close poll",
"create": {
"accessibilityLabel": {
"send": "Send poll"
},
"addOption": "Add option",
"answerPlaceholder": "Option {{index}}",
"cancel": "Cancel",
@@ -968,8 +976,7 @@
"pollQuestion": "Poll Question",
"questionPlaceholder": "Ask a question",
"removeOption": "Remove option",
"save": "Save",
"send": "Send"
"save": "Save"
},
"errors": {
"notUniqueOption": "Options must be unique"
@@ -1376,6 +1383,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",
@@ -1416,8 +1437,8 @@
"linkToSalesforce": "Link to Salesforce",
"lobbyButtonDisable": "Disable lobby mode",
"lobbyButtonEnable": "Enable lobby mode",
"login": "Log-in",
"logout": "Log-out",
"login": "Log In",
"logout": "Log Out",
"love": "Heart",
"lowerYourHand": "Lower your hand",
"moreActions": "More actions",
@@ -1447,6 +1468,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 +1620,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

184
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/v2051.0.0+ccc06e83/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2097.0.0+58646fc3/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/v2051.0.0+ccc06e83/lib-jitsi-meet.tgz",
"integrity": "sha512-PUxlLnE3gFZ9RiUpJrnkfRjkRHgwo1jN9BKSVVEh1GaAI4vQhtNzy6zI/sASludRXSUuKDBSAnIQ1K1u+X9/YA==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2097.0.0+58646fc3/lib-jitsi-meet.tgz",
"integrity": "sha512-CViaK78aH8jmlmUkx+J3StpYFDDWyd5ry2CIoBEJx9uZtSnqczVjOBkbx/9VFifd8ZTr+VClfDRM/ZpkJye8rg==",
"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",
@@ -18216,7 +18273,6 @@
"base64-js": "1.5.1",
"current-executing-script": "0.1.3",
"emoji-regex": "10.4.0",
"jquery": "3.6.1",
"lodash-es": "4.17.21",
"sdp-transform": "2.3.0",
"strophe.js": "https://github.com/jitsi/strophejs/releases/download/v1.5-jitsi-3/strophe.js-1.5.0.tgz",
@@ -18235,6 +18291,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 +22535,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 +24531,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 +27732,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 +28237,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 +30059,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 +32490,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 +35638,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 +39715,11 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2051.0.0+ccc06e83/lib-jitsi-meet.tgz",
"integrity": "sha512-PUxlLnE3gFZ9RiUpJrnkfRjkRHgwo1jN9BKSVVEh1GaAI4vQhtNzy6zI/sASludRXSUuKDBSAnIQ1K1u+X9/YA==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2097.0.0+58646fc3/lib-jitsi-meet.tgz",
"integrity": "sha512-CViaK78aH8jmlmUkx+J3StpYFDDWyd5ry2CIoBEJx9uZtSnqczVjOBkbx/9VFifd8ZTr+VClfDRM/ZpkJye8rg==",
"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",
@@ -39612,7 +39727,6 @@
"base64-js": "1.5.1",
"current-executing-script": "0.1.3",
"emoji-regex": "10.4.0",
"jquery": "3.6.1",
"lodash-es": "4.17.21",
"sdp-transform": "2.3.0",
"strophe.js": "https://github.com/jitsi/strophejs/releases/download/v1.5-jitsi-3/strophe.js-1.5.0.tgz",
@@ -39630,6 +39744,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 +42753,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 +44118,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/v2051.0.0+ccc06e83/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2097.0.0+58646fc3/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",
@@ -213,8 +217,6 @@
"lint-fix": "eslint --ext .js,.ts,.tsx --max-warnings 0 --fix .",
"postinstall": "patch-package --error-on-fail && jetify",
"validate": "npm ls",
"tsc-test:web": "tsc --project tsconfig.web.json --listFilesOnly | grep -v node_modules | grep native",
"tsc-test:native": "tsc --project tsconfig.native.json --listFilesOnly | grep -v node_modules | grep web",
"start": "make dev",
"test": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.conf.ts",
"test-single": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.conf.ts --spec",

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

@@ -46,6 +46,15 @@ export const SET_TOKEN_AUTH_URL_SUCCESS = 'SET_TOKEN_AUTH_URL_SUCCESS';
*/
export const STOP_WAIT_FOR_OWNER = 'STOP_WAIT_FOR_OWNER';
/**
* The type of (redux) action which disables moderator login.
*
* {
* type: DISABLE_MODERATOR_LOGIN
* }
*/
export const DISABLE_MODERATOR_LOGIN = 'DISABLE_MODERATOR_LOGIN';
/**
* The type of (redux) action which informs that the authentication and role
* upgrade process has finished either with success or with a specific error.
@@ -74,6 +83,15 @@ export const UPGRADE_ROLE_FINISHED = 'UPGRADE_ROLE_FINISHED';
*/
export const UPGRADE_ROLE_STARTED = 'UPGRADE_ROLE_STARTED';
/**
* The type of (redux) action which enables moderator login.
*
* {
* type: ENABLE_MODERATOR_LOGIN
* }
*/
export const ENABLE_MODERATOR_LOGIN = 'ENABLE_MODERATOR_LOGIN';
/**
* The type of (redux) action that sets delayed handler which will check if
* the conference has been created and it's now possible to join from anonymous

View File

@@ -4,12 +4,15 @@ import { IJitsiConference } from '../base/conference/reducer';
import { hideDialog, openDialog } from '../base/dialog/actions';
import {
DISABLE_MODERATOR_LOGIN,
ENABLE_MODERATOR_LOGIN,
LOGIN,
LOGOUT,
SET_TOKEN_AUTH_URL_SUCCESS,
STOP_WAIT_FOR_OWNER,
UPGRADE_ROLE_FINISHED,
UPGRADE_ROLE_STARTED, WAIT_FOR_OWNER
UPGRADE_ROLE_STARTED,
WAIT_FOR_OWNER
} from './actionTypes';
import { LoginDialog, WaitForOwnerDialog } from './components';
import logger from './logger';
@@ -165,6 +168,30 @@ export function logout() {
};
}
/**
* Disables moderator login.
*
* @returns {{
* type: DISABLE_MODERATOR_LOGIN
* }}
*/
export function disableModeratorLogin() {
return {
type: DISABLE_MODERATOR_LOGIN
};
}
/**
* Enables moderator login.
*
* @returns {Object}
*/
export function enableModeratorLogin() {
return {
type: ENABLE_MODERATOR_LOGIN
};
}
/**
* Opens {@link WaitForOnwerDialog}.
*
@@ -175,6 +202,7 @@ export function openWaitForOwnerDialog() {
return openDialog(WaitForOwnerDialog);
}
/**
* Stops waiting for the conference owner.
*

View File

@@ -67,7 +67,7 @@ class WaitForOwnerDialog extends Component<IProps> {
<ConfirmDialog
cancelLabel = { this.props._alternativeCancelText ? 'dialog.WaitingForHostButton' : 'dialog.Cancel' }
confirmLabel = 'dialog.IamHost'
descriptionKey = 'dialog.WaitForHostMsg'
descriptionKey = 'lobby.waitForModerator'
isConfirmHidden = { _isConfirmHidden }
onCancel = { this._onCancel }
onSubmit = { this._onLogin } />

View File

@@ -91,7 +91,7 @@ class WaitForOwnerDialog extends PureComponent<IProps> {
onSubmit = { this._onIAmHost }
titleKey = { t('dialog.WaitingForHostTitle') }>
<span>
{ this.props._hideLoginButton ? t('dialog.WaitForHostNoAuthMsg') : t('dialog.WaitForHostMsg') }
{ this.props._hideLoginButton ? t('dialog.WaitForHostNoAuthMsg') : t('lobby.waitForModerator') }
</span>
</Dialog>
);

View File

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

View File

@@ -28,6 +28,8 @@ import {
WAIT_FOR_OWNER
} from './actionTypes';
import {
disableModeratorLogin,
enableModeratorLogin,
hideLoginDialog,
openLoginDialog,
openTokenAuthUrl,
@@ -44,7 +46,7 @@ import logger from './logger';
/**
* Middleware that captures connection or conference failed errors and controls
* {@link WaitForOwnerDialog} and {@link LoginDialog}.
* moderator login availability and {@link LoginDialog}.
*
* FIXME Some of the complexity was introduced by the lack of dialog stacking.
*
@@ -105,11 +107,21 @@ MiddlewareRegistry.register(store => next => action => {
}
recoverable = error.recoverable;
}
if (recoverable) {
store.dispatch(waitForOwner());
} else {
store.dispatch(stopWaitForOwner());
if (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR && lobbyWaitingForHost) {
if (recoverable) {
store.dispatch(enableModeratorLogin());
} else {
store.dispatch(disableModeratorLogin());
}
} else if (error.name === JitsiConferenceErrors.AUTHENTICATION_REQUIRED) {
if (recoverable) {
store.dispatch(waitForOwner());
} else {
store.dispatch(stopWaitForOwner());
}
}
break;
}
@@ -126,6 +138,9 @@ MiddlewareRegistry.register(store => next => action => {
dispatch(setTokenAuthUrlSuccess(true));
}
if (_isWaitingForModerator(store)) {
store.dispatch(disableModeratorLogin());
}
if (_isWaitingForOwner(store)) {
store.dispatch(stopWaitForOwner());
}
@@ -134,6 +149,7 @@ MiddlewareRegistry.register(store => next => action => {
}
case CONFERENCE_LEFT:
store.dispatch(disableModeratorLogin());
store.dispatch(stopWaitForOwner());
break;
@@ -236,7 +252,6 @@ function _clearExistingWaitForOwnerTimeout({ getState }: IStore) {
waitForOwnerTimeoutID && clearTimeout(waitForOwnerTimeoutID);
}
/**
* Checks if the cyclic "wait for conference owner" task is currently scheduled.
*
@@ -247,6 +262,16 @@ function _isWaitingForOwner({ getState }: IStore) {
return Boolean(getState()['features/authentication'].waitForOwnerTimeoutID);
}
/**
* Checks if the cyclic "wait for moderator" task is currently scheduled.
*
* @param {Object} store - The redux store.
* @returns {boolean}
*/
function _isWaitingForModerator({ getState }: IStore) {
return getState()['features/authentication'].showModeratorLogin;
}
/**
* Handles login challenge. Opens login dialog or redirects to token auth URL.
*

View File

@@ -4,6 +4,8 @@ import { assign } from '../base/redux/functions';
import {
CANCEL_LOGIN,
DISABLE_MODERATOR_LOGIN,
ENABLE_MODERATOR_LOGIN,
SET_TOKEN_AUTH_URL_SUCCESS,
STOP_WAIT_FOR_OWNER,
UPGRADE_ROLE_FINISHED,
@@ -14,6 +16,7 @@ import {
export interface IAuthenticationState {
error?: Object | undefined;
progress?: number | undefined;
showModeratorLogin?: boolean;
thenableWithCancel?: {
cancel: Function;
};
@@ -45,6 +48,11 @@ ReducerRegistry.register<IAuthenticationState>('features/authentication',
progress: undefined,
thenableWithCancel: undefined
});
case ENABLE_MODERATOR_LOGIN:
return assign(state, {
showModeratorLogin: true
});
case SET_TOKEN_AUTH_URL_SUCCESS:
return assign(state, {
tokenAuthUrlSuccessful: action.value
@@ -56,6 +64,12 @@ ReducerRegistry.register<IAuthenticationState>('features/authentication',
waitForOwnerTimeoutID: undefined
});
case DISABLE_MODERATOR_LOGIN:
return assign(state, {
error: undefined,
showModeratorLogin: false
});
case UPGRADE_ROLE_FINISHED: {
let { thenableWithCancel } = action;

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

@@ -13,6 +13,7 @@ import {
import { sendAnalytics } from '../../analytics/functions';
import { reloadNow } from '../../app/actions';
import { IStore } from '../../app/types';
import { login } from '../../authentication/actions.any';
import { removeLobbyChatParticipant } from '../../chat/actions.any';
import { openDisplayNamePrompt } from '../../display-name/actions';
import { isVpaasMeeting } from '../../jaas/functions';
@@ -26,7 +27,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 +79,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 +208,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,12 +235,24 @@ 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: {
const [ type, msg ] = error.params;
let descriptionKey;
let descriptionKey, customActionNameKey, customActionHandler;
let titleKey = 'dialog.tokenAuthFailed';
if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.NO_MAIN_PARTICIPANTS) {
@@ -237,9 +264,15 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
descriptionKey = 'visitors.notification.notAllowedPromotion';
} else if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.ROOM_CREATION_RESTRICTION) {
descriptionKey = 'dialog.errorRoomCreationRestriction';
} else if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.ROOM_UNAUTHENTICATED_ACCESS_DISABLED) {
titleKey = 'dialog.unauthenticatedAccessDisabled';
customActionNameKey = [ 'toolbar.login' ];
customActionHandler = [ () => dispatch(login()) ];
}
dispatch(showErrorNotification({
customActionNameKey,
customActionHandler,
descriptionKey,
hideErrorSupportLink: true,
titleKey
@@ -300,6 +333,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

@@ -107,6 +107,7 @@ export interface IJitsiConference {
getParticipantById: Function;
getParticipantCount: Function;
getParticipants: Function;
getPolls: Function;
getRole: Function;
getShortTermCredentials: Function;
getSpeakerStats: () => ISpeakerStats;

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;
@@ -461,6 +464,7 @@ export interface IConfig {
lobby?: {
autoKnock?: boolean;
enableChat?: boolean;
showHangUp?: boolean;
};
localRecording?: {
disable?: boolean;

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

View File

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

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,4 +1,7 @@
import logger from '../app/logger';
import { UPDATE_FLAGS } from './actionTypes';
import * as featureFlags from './constants';
/**
* Updates the current features flags with the given ones. They will be merged.
@@ -10,6 +13,13 @@ import { UPDATE_FLAGS } from './actionTypes';
* }}
*/
export function updateFlags(flags: Object) {
const supportedFlags = Object.values(featureFlags);
const unsupportedFlags = Object.keys(flags).filter(flag => !supportedFlags.includes(flag as any));
if (unsupportedFlags.length > 0) {
logger.warn(`The following feature flags are not supported: ${unsupportedFlags.join(', ')}.`);
}
return {
type: UPDATE_FLAGS,
flags

View File

@@ -41,8 +41,8 @@ const _LANGUAGES = {
},
// Spanish (Latin America)
'esUS': {
main: require('../../../../lang/main-esUS')
'es-US': {
main: require('../../../../lang/main-es-US')
},
// Estonian
@@ -66,8 +66,8 @@ const _LANGUAGES = {
},
// French (Canadian)
'frCA': {
main: require('../../../../lang/main-frCA')
'fr-CA': {
main: require('../../../../lang/main-fr-CA')
},
// Croatian
@@ -116,8 +116,8 @@ const _LANGUAGES = {
},
// Portuguese (Brazil)
'ptBR': {
main: require('../../../../lang/main-ptBR')
'pt-BR': {
main: require('../../../../lang/main-pt-BR')
},
// Romanian
@@ -166,13 +166,13 @@ const _LANGUAGES = {
},
// Chinese (Simplified)
'zhCN': {
main: require('../../../../lang/main-zhCN')
'zh-CN': {
main: require('../../../../lang/main-zh-CN')
},
// Chinese (Traditional)
'zhTW': {
main: require('../../../../lang/main-zhTW')
'zh-TW': {
main: require('../../../../lang/main-zh-TW')
}
};

View File

@@ -1,67 +0,0 @@
declare let navigator: any;
/**
* Custom language detection, just returns the config property if any.
*/
export default {
/**
* Does not support caching.
*
* @returns {void}
*/
cacheUserLanguage: Function.prototype,
/**
* Looks the language up in the config.
*
* @returns {string} The default language if any.
*/
lookup() {
let found = [];
if (typeof navigator !== 'undefined') {
if (navigator.languages) {
// chrome only; not an array, so can't use .push.apply instead of iterating
for (let i = 0; i < navigator.languages.length; i++) {
found.push(navigator.languages[i]);
}
}
if (navigator.userLanguage) {
found.push(navigator.userLanguage);
}
if (navigator.language) {
found.push(navigator.language);
}
}
found = found.map<string>(normalizeLanguage);
return found.length > 0 ? found : undefined;
},
/**
* Name of the language detector.
*/
name: 'customNavigatorDetector'
};
/**
* Normalize language format.
*
* (en-US => enUS)
* (en-gb => enGB)
* (es-es => es).
*
* @param {string} language - Language.
* @returns {string} The normalized language.
*/
function normalizeLanguage(language: string) {
const [ lang, variant ] = language.replace('_', '-').split('-');
if (!variant || lang.toUpperCase() === variant.toUpperCase()) {
return lang;
}
return lang + variant.toUpperCase();
}

View File

@@ -1,7 +1,6 @@
import BrowserLanguageDetector from 'i18next-browser-languagedetector';
import configLanguageDetector from './configLanguageDetector';
import customNavigatorDetector from './customNavigatorDetector';
/**
* The ordered list (by name) of language detectors to be utilized as backends
@@ -16,7 +15,7 @@ const order = [
// Allow i18next to detect the system language reported by the Web browser
// itself.
interfaceConfig.LANG_DETECTION && order.push(customNavigatorDetector.name);
interfaceConfig.LANG_DETECTION && order.push('navigator');
// Default use configured language
order.push(configLanguageDetector.name);
@@ -34,11 +33,6 @@ const languageDetector
order
});
// Add the language detector which looks the language up in the config. Its
// order has already been established above.
// @ts-ignore
languageDetector.addDetector(customNavigatorDetector);
// @ts-ignore
languageDetector.addDetector(configLanguageDetector);

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

@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../../app/types';
import { getLobbyConfig } from '../../../../lobby/functions';
import DeviceStatus from '../../../../prejoin/components/web/preview/DeviceStatus';
import { isRoomNameEnabled } from '../../../../prejoin/functions.web';
import Toolbox from '../../../../toolbox/components/web/Toolbox';
@@ -121,10 +122,9 @@ const useStyles = makeStyles()(theme => {
alignItems: 'center',
flexShrink: 0,
boxSizing: 'border-box',
margin: '0 48px',
padding: '24px 0 16px',
position: 'relative',
width: '300px',
width: '400px',
height: '100%',
zIndex: 252,
@@ -146,10 +146,21 @@ const useStyles = makeStyles()(theme => {
contentControls: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
alignItems: 'stretch',
margin: 'auto',
width: '100%'
},
paddedContent: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '0 50px',
'& > *': {
width: '100%',
boxSizing: 'border-box'
}
},
title: {
...theme.typography.heading4,
color: `${theme.palette.text01}!important`,
@@ -220,34 +231,38 @@ const PreMeetingScreen = ({
{_isPreCallTestEnabled && <ConnectionStatus />}
<div className = { classes.contentControls }>
<h1 className = { classes.title }>
{title}
</h1>
{_roomName && (
<span className = { classes.roomNameContainer }>
{isOverflowing ? (
<Tooltip content = { _roomName }>
<div className = { classes.paddedContent }>
<h1 className = { classes.title }>
{title}
</h1>
{_roomName && (
<span className = { classes.roomNameContainer }>
{isOverflowing ? (
<Tooltip content = { _roomName }>
<span
className = { classes.roomName }
ref = { roomNameRef }>
{_roomName}
</span>
</Tooltip>
) : (
<span
className = { classes.roomName }
ref = { roomNameRef }>
{_roomName}
</span>
</Tooltip>
) : (
<span
className = { classes.roomName }
ref = { roomNameRef }>
{_roomName}
</span>
)}
</span>
)}
{children}
)}
</span>
)}
{children}
</div>
{_buttons.length && <Toolbox toolbarButtons = { _buttons } />}
{skipPrejoinButton}
{showUnsafeRoomWarning && <UnsafeRoomWarning />}
{showDeviceStatus && <DeviceStatus />}
{showRecordingWarning && <RecordingWarning />}
<div className = { classes.paddedContent }>
{skipPrejoinButton}
{showUnsafeRoomWarning && <UnsafeRoomWarning />}
{showDeviceStatus && <DeviceStatus />}
{showRecordingWarning && <RecordingWarning />}
</div>
</div>
</div>
</div>
@@ -269,10 +284,16 @@ const PreMeetingScreen = ({
function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
const { hiddenPremeetingButtons } = state['features/base/config'];
const { toolbarButtons } = state['features/toolbox'];
const { showHangUp = true } = getLobbyConfig(state);
const { knocking } = state['features/lobby'];
const premeetingButtons = (ownProps.thirdParty
? THIRD_PARTY_PREJOIN_BUTTONS
: PREMEETING_BUTTONS).filter((b: any) => !(hiddenPremeetingButtons || []).includes(b));
if (showHangUp && knocking && !premeetingButtons.includes('hangup')) {
premeetingButtons.push('hangup');
}
const { premeetingBackground } = state['features/dynamic-branding'];
return {

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

@@ -22,6 +22,11 @@ interface ICheckboxProps {
*/
disabled?: boolean;
/**
* The id of the input.
*/
id?: string;
/**
* The label of the input.
*/
@@ -147,6 +152,7 @@ const Checkbox = ({
checked,
className,
disabled,
id,
label,
name,
onChange
@@ -160,6 +166,7 @@ const Checkbox = ({
<input
checked = { checked }
disabled = { disabled }
id = { id }
name = { name }
onChange = { onChange }
type = 'checkbox' />

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');

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