Compare commits

..

3 Commits

Author SHA1 Message Date
Avram Tudor
ae03f798ee fix(recording) fix recording suggestion not being shown in some cases
Initial implementation did not account for cases where participants become moderators
2024-02-07 15:38:59 +02:00
Avram Tudor
4ffc2e74da feat(recording) add notification to suggest recording at meeting startup (#14296)
* feat(recording) add notification to suggest recording at meeting startup

* code review changes

* update strings

* fix mobile

* fix lint
2024-02-06 16:13:31 +02:00
Jaya Allamsetty
138f885b70 chore(deps) update lib-jitsi-meet.
Fixes a regression on Safari where it can end up sending low resolution to a p2p peer in some cases.
2024-01-29 11:48:41 -05:00
610 changed files with 19837 additions and 21995 deletions

6
.buckconfig Normal file
View File

@@ -0,0 +1,6 @@
[android]
target = Google Inc.:Google APIs:23
[maven_repositories]
central = https://repo1.maven.org/maven2

View File

@@ -3,7 +3,7 @@
"image": "mcr.microsoft.com/devcontainers/universal:2",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "20"
"version": "16"
}
},
"hostRequirements": {

View File

@@ -6,7 +6,7 @@ charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
max_line_length = 120
max_line_length = 80
trim_trailing_whitespace = true
[*.md]

48
.github/ISSUE_TEMPLATE/1-bug-report.md vendored Normal file
View File

@@ -0,0 +1,48 @@
---
name: Bug report
about: Create a report to help us improve
---
<!--
This issue tracker is only for reporting bugs and tracking issues related to the source code.
Before posting, please make sure to check if the same or similar bugs have already been discussed: https://github.com/jitsi/jitsi-meet/issues
General questions regarding usage, installation, etc. should be posted at https://community.jitsi.org. They will be closed if posted here.
-->
### Description:
<!-- Please describe the bug clearly and concisely. -->
### Steps to reproduce:
1. <!-- Open '...' -->
2. <!-- Click on '...' -->
3. <!-- and so on... -->
### Expected behavior:
<!-- Please describe what should happen. -->
### Actual behavior:
<!-- Please describe what actually happens. -->
<!-- Please attach screenshot if possible. -->
### Server information:
- Jitsi Meet version:
- Operating System:
### Client information:
- Browser / app version:
- Operating System:
### Additional information:
<!-- Please provide additional information about the bug, if any. -->

View File

@@ -1,55 +0,0 @@
name: Bug report
description: File a bug report and help us improve
body:
- type: markdown
attributes:
value: |
This issue tracker is only for reporting bugs and tracking issues related to the source code.
**Before posting, please make sure to check if the same or similar bugs have already been reported.**
⚠️ General questions regarding usage, installation, etc. should be posted in our [community forum](https://community.jitsi.org).
- type: textarea
attributes:
label: What happened?
description: Please describe the problem. Be as detailed as possible.
validations:
required: true
- type: checkboxes
attributes:
label: Platform
description: On what platforms can you reproduce the problem?
options:
- label: Chrome (or Chromium based)
- label: Firefox
- label: Safari
- label: Other desktop browser
- label: Android browser
- label: iOS browser
- label: Electron app
- label: Android mobile app
- label: iOS mobile app
- label: Custom app using a mobile SDK
validations:
required: true
- type: input
attributes:
label: Browser / app / sdk version
description: Please provice the version of the browser / app / sdk where the problem manifests.
validations:
required: true
- type: textarea
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. The browser console JS logs (if applicable) is a good start. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: checkboxes
attributes:
label: Reproducibility
description: Does the problem reproduce on meet.jit.si using Chrome, Firefox or the official mobile apps?
options:
- label: The problem is reproducible on meet.jit.si
- type: textarea
attributes:
label: More details?
description: Please provide more details in case they apply (such as the Jitsi Meet version you are running, if you are hosting your own server).

View File

@@ -0,0 +1,25 @@
---
name: "Feature request"
about: Suggest an idea for this project
title: ''
labels: 'feature-request'
assignees: ''
---
<!--
Thank you for suggesting an idea to make Jitsi Meet better.
Please fill in as much of the template below as you're able.
Note that the ultimate decision for implementing features lies on the Jitsi team, not all feature requests shall be accepted.
-->
**Is your feature request related to a problem you are facing?**
Please describe the problem you are trying to solve.
**Describe the solution you'd like**
Please describe the desired behavior.
**Describe alternatives you've considered**
Please describe alternative solutions or features you have considered.

View File

@@ -1,22 +0,0 @@
name: Feature request
description: Suggest an idea for Jitsi Meet
labels: ["feature-request"]
body:
- type: markdown
attributes:
value: |
Thank you for suggesting an idea to make Jitsi Meet better.
**Note**: the ultimate decision for implementing features lies on the Jitsi team, not all feature requests shall be accepted.
- type: textarea
attributes:
label: What problem are you trying to solve?
description: Tell us what problem your feature request would solve.
- type: textarea
attributes:
label: What solution would you like to see?
description: Please describe the desired behavior or feature.
- type: textarea
attributes:
label: Is there an alternative?
description: Please describe alternative solutions or features you have considered.

View File

@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Need help with your Jitsi Meet installation?
- name: Need help with Jitsi Meet?
url: https://community.jitsi.org
about: Please ask it in our community forum.
about: Please ask it in our community.

View File

@@ -7,7 +7,7 @@ jobs:
name: Luacheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Install luarocks
run: sudo apt-get --install-recommends -y install luarocks

View File

@@ -7,15 +7,11 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
node-version: 16
cache: 'npm'
- name: Check Node / npm versions
run: |
node -v
npm -v
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v41
@@ -38,25 +34,21 @@ jobs:
name: Build Frontend (Linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
node-version: 16
cache: 'npm'
- name: Check Node / npm versions
run: |
node -v
npm -v
- run: npm install
- run: make
macos-ci:
name: Build Frontend (macOS)
runs-on: macOS-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
node-version: 16
cache: 'npm'
- run: npm install
- run: make
@@ -64,45 +56,23 @@ jobs:
name: Build mobile bundle (Android)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
node-version: 16
cache: 'npm'
- name: Check Node / npm versions
run: |
node -v
npm -v
- run: npm install
- run: npx react-native bundle --entry-file react/index.native.js --platform android --bundle-output /tmp/android.bundle --reset-cache
ios-build:
name: Build mobile bundle (iOS)
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-13, macos-14]
runs-on: macOS-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
node-version: 16
cache: 'npm'
- name: Check Node / npm versions
run: |
node -v
npm -v
- run: npm install
- name: setup Xcode
run: |
uname -a
xcode-select -p
sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
xcodebuild -version
- name: setup-cocoapods
uses: maxim-lobanov/setup-cocoapods@v1
with:
podfile-path: ios/Podfile.lock
- name: Install Pods
run: |
pod --version
@@ -115,15 +85,11 @@ jobs:
name: Test Debian packages build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
node-version: 16
cache: 'npm'
- name: Check Node / npm versions
run: |
node -v
npm -v
- run: npm install
- run: make
- run: sudo apt-get install -y debhelper

3
.gitignore vendored
View File

@@ -99,7 +99,10 @@ tsconfig.json
#
react-native-sdk/*.tgz
react-native-sdk/android/src
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetOngoingConferenceService.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetReactNativePackage.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JMOngoingConferenceModule.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/RNOngoingNotification.java
react-native-sdk/images
react-native-sdk/ios
react-native-sdk/lang

2
.nvmrc
View File

@@ -1 +1 @@
20
16

6
.travis.yml Normal file
View File

@@ -0,0 +1,6 @@
osx_image: xcode11.1
language: objective-c
script:
- "./ios/travis-ci/build-ipa.sh"
after_script:
- sleep 10

View File

@@ -1,14 +1,171 @@
# Follow Our Updated Guide to See How You Can Contribute
# How to contribute
We would love to have your help. Before you start working however, please read
and follow this short guide.
Hello there! 👋
# Reporting Issues
Provide as much information as possible. Mention the version of Jitsi Meet,
Jicofo and JVB you are using, and explain (as detailed as you can) how the
problem can be reproduced.
We're thrilled that you're eager to contribute to Jitsi Meet! ❤️
# Code contributions
Found a bug and know how to fix it? Great! Please read on.
Your interest in improving our platform means a lot to us. To ensure your contributions align seamlessly with our goals and processes, we've recently updated our guide. This guide will provide you with clear instructions on how to get involved effectively.
## Contributor License Agreement
While the Jitsi projects are released under the
[Apache License 2.0](https://github.com/jitsi/jitsi-meet/blob/master/LICENSE), the copyright
holder and principal creator is [8x8](https://www.8x8.com/). To
ensure that we can continue making these projects available under an Open Source license,
we need you to sign our Apache-based contributor
license agreement as either a [corporation](https://jitsi.org/ccla) or an
[individual](https://jitsi.org/icla). If you cannot accept the terms laid out
in the agreement, unfortunately, we cannot accept your contribution.
Ready to get started? Head over to our [Jitsi Meet Handbook](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-contributing/) and let's make Jitsi Meet even better together!
## Creating Pull Requests
- Make sure your code passes the linter rules beforehand. The linter is executed
automatically when committing code.
- Perform **one** logical change per pull request.
- Maintain a clean list of commits, squash them if necessary.
- Rebase your topic branch on top of the master branch before creating the pull
request.
### ❗Additional Note
Before sending us your code, double-check that it meets our coding standards. You can do this by running a command: `npm run lint`. If there are any issues, don't worry! You can fix them by running: `npm run lint-fix`. Once your code passes these checks, feel free to submit your pull request.
## Coding style
Happy coding!
### Comments
* Comments documenting the source code are required.
* Comments from which documentation is automatically generated are **not**
subject to case-by-case decisions. Such comments are used, for example, on
types and their members. Examples of tools which automatically generate
documentation from such comments include JSDoc, Javadoc, Doxygen.
* Comments which are not automatically processed are strongly encouraged. They
are subject to case-by-case decisions. Such comments are often observed in
function bodies.
* Comments should be formatted as proper English sentences. Such formatting pays
attention to, for example, capitalization and punctuation.
### Duplication
* Don't copy-paste source code. Reuse it.
### Formatting
* Line length is limited to 120 characters.
* Sort by alphabetical order in order to make the addition of new entities as
easy as looking a word up in a dictionary. Otherwise, one risks duplicate
entries (with conflicting values in the cases of key-value pairs). For
example:
* Within an `import` of multiple names from a module, sort the names in
alphabetical order. (Of course, the default name stays first as required by
the `import` syntax.)
````javascript
import {
DOMINANT_SPEAKER_CHANGED,
JITSI_CLIENT_CONNECTED,
JITSI_CLIENT_CREATED,
JITSI_CLIENT_DISCONNECTED,
JITSI_CLIENT_ERROR,
JITSI_CONFERENCE_JOINED,
MODERATOR_CHANGED,
PEER_JOINED,
PEER_LEFT,
RTC_ERROR
} from './actionTypes';
````
* Within a group of imports (e.g. groups of imports delimited by an empty line
may be: third-party modules, then project modules, and eventually the
private files of a module), sort the module names in alphabetical order.
````javascript
import React, { Component } from 'react';
import { connect } from 'react-redux';
````
### Indentation
* Align `switch` and `case`/`default`. Don't indent the `case`/`default` more
than its `switch`.
````javascript
switch (i) {
case 0:
...
break;
default:
...
}
````
### Naming
* An abstraction should have one name within the project and across multiple
projects. For example:
* The instance of lib-jitsi-meet's `JitsiConnection` type should be named
`connection` or `jitsiConnection` in jitsi-meet, not `client`.
* The class `ReducerRegistry` should be defined in ReducerRegistry.js and its
imports in other files should use the same name. Don't define the class
`Registry` in ReducerRegistry.js and then import it as `Reducers` in other
files.
* The names of global constants (including ES6 module-global constants) should
be written in uppercase with underscores to separate words. For example,
`BACKGROUND_COLOR`.
* The underscore character at the beginning of a name signals that the
respective variable, function, property is non-public i.e. private, protected,
or internal. In contrast, the lack of an underscore at the beginning of a name
signals public API.
### Feature layout
When adding a new feature, this would be the usual layout.
```
react/features/sample/
├── actionTypes.ts
├── actions.js
├── components
│   ├── AnotherComponent.js
│   ├── OneComponent.js
│   └── index.js
├── middleware.js
└── reducer.js
```
The middleware must be imported in `react/features/app/` specifically
in `middlewares.any.ts`, `middlewares.native.ts` or `middlewares.web.ts` where appropriate.
Likewise for the reducer.
An `index.js` file must not be provided for exporting actions, action types and
component. Features / files requiring those must import them explicitly.
This has not always been the case and the entire codebase hasn't been migrated to
this model but new features should follow this new layout.
When working on an old feature, adding the necessary changes to migrate to the new
model is encouraged.
### Avoiding bundle bloat
When adding a new feature it's possible that it triggers a build failure due to the increased bundle size. We have safeguards inplace to avoid bundles growing disproportionatelly. While there are legit reasons for increasing the limits, please analyze the bundle first to make sure no unintended dependencies have been included, causing the increase in size.
First, make a production build with bundle-analysis enabled:
```
npx webpack -p --analyze-bundle
```
Then open the interactive bundle analyzer tool:
```
npx webpack-bundle-analyzer build/app-stats.json
```

View File

@@ -48,6 +48,7 @@ deploy-appbundle:
$(BUILD_DIR)/external_api.min.js.map \
$(BUILD_DIR)/alwaysontop.min.js \
$(BUILD_DIR)/alwaysontop.min.js.map \
$(OUTPUT_DIR)/analytics-ga.js \
$(BUILD_DIR)/analytics-ga.min.js \
$(BUILD_DIR)/analytics-ga.min.js.map \
$(BUILD_DIR)/face-landmarks-worker.min.js \
@@ -122,7 +123,7 @@ deploy-local:
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-olm deploy-tf-wasm deploy-excalidraw-dev deploy-face-landmarks
$(WEBPACK_DEV_SERVER)
source-package: compile deploy
source-package:
mkdir -p source_package/jitsi-meet/css && \
cp -r *.js *.html resources/*.txt fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
cp css/all.css source_package/jitsi-meet/css && \

163
analytics-ga.js Normal file
View File

@@ -0,0 +1,163 @@
/* global ga */
(function(ctx) {
/**
*
*/
function Analytics(options) {
/* eslint-disable */
if (!options.googleAnalyticsTrackingId) {
console.log(
'Failed to initialize Google Analytics handler, no tracking ID');
return;
}
/**
* Google Analytics
* TODO: Keep this local, there's no need to add it to window.
*/
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', options.googleAnalyticsTrackingId, 'auto');
ga('send', 'pageview');
/* eslint-enable */
}
/**
* Extracts the integer to use for a Google Analytics event's value field
* from a lib-jitsi-meet analytics event.
* @param {Object} event - The lib-jitsi-meet analytics event.
* @returns {Object} - The integer to use for the 'value' of a Google
* Analytics event.
* @private
*/
Analytics.prototype._extractAction = function(event) {
// Page events have a single 'name' field.
if (event.type === 'page') {
return event.name;
}
// All other events have action, actionSubject, and source fields. All
// three fields are required, and the often jitsi-meet and
// lib-jitsi-meet use the same value when separate values are not
// necessary (i.e. event.action == event.actionSubject).
// Here we concatenate these three fields, but avoid adding the same
// value twice, because it would only make the GA event's action harder
// to read.
let action = event.action;
if (event.actionSubject && event.actionSubject !== event.action) {
// Intentionally use string concatenation as analytics needs to
// work on IE but this file does not go through babel. For some
// reason disabling this globally for the file does not have an
// effect.
// eslint-disable-next-line prefer-template
action = event.actionSubject + '.' + action;
}
if (event.source && event.source !== event.action
&& event.source !== event.action) {
// eslint-disable-next-line prefer-template
action = event.source + '.' + action;
}
return action;
};
/**
* Extracts the integer to use for a Google Analytics event's value field
* from a lib-jitsi-meet analytics event.
* @param {Object} event - The lib-jitsi-meet analytics event.
* @returns {Object} - The integer to use for the 'value' of a Google
* Analytics event, or NaN if the lib-jitsi-meet event doesn't contain a
* suitable value.
* @private
*/
Analytics.prototype._extractValue = function(event) {
let value = event && event.attributes && event.attributes.value;
// Try to extract an integer from the "value" attribute.
value = Math.round(parseFloat(value));
return value;
};
/**
* Extracts the string to use for a Google Analytics event's label field
* from a lib-jitsi-meet analytics event.
* @param {Object} event - The lib-jitsi-meet analytics event.
* @returns {string} - The string to use for the 'label' of a Google
* Analytics event.
* @private
*/
Analytics.prototype._extractLabel = function(event) {
let label = '';
// The label field is limited to 500B. We will concatenate all
// attributes of the event, except the user agent because it may be
// lengthy and is probably included from elsewhere.
for (const property in event.attributes) {
if (property !== 'permanent_user_agent'
&& property !== 'permanent_callstats_name'
&& event.attributes.hasOwnProperty(property)) {
// eslint-disable-next-line prefer-template
label += property + '=' + event.attributes[property] + '&';
}
}
if (label.length > 0) {
label = label.slice(0, -1);
}
return label;
};
/**
* This is the entry point of the API. The function sends an event to
* google analytics. The format of the event is described in
* AnalyticsAdapter in lib-jitsi-meet.
* @param {Object} event - the event in the format specified by
* lib-jitsi-meet.
*/
Analytics.prototype.sendEvent = function(event) {
if (!event || !ga) {
return;
}
const ignoredEvents
= [ 'e2e_rtt', 'rtp.stats', 'rtt.by.region', 'available.device',
'stream.switch.delay', 'ice.state.changed', 'ice.duration' ];
// Temporary removing some of the events that are too noisy.
if (ignoredEvents.indexOf(event.action) !== -1) {
return;
}
const gaEvent = {
'eventCategory': 'jitsi-meet',
'eventAction': this._extractAction(event),
'eventLabel': this._extractLabel(event)
};
const value = this._extractValue(event);
if (!isNaN(value)) {
gaEvent.eventValue = value;
}
ga('send', 'event', gaEvent);
};
if (typeof ctx.JitsiMeetJS === 'undefined') {
ctx.JitsiMeetJS = {};
}
if (typeof ctx.JitsiMeetJS.app === 'undefined') {
ctx.JitsiMeetJS.app = {};
}
if (typeof ctx.JitsiMeetJS.app.analyticsHandlers === 'undefined') {
ctx.JitsiMeetJS.app.analyticsHandlers = [];
}
ctx.JitsiMeetJS.app.analyticsHandlers.push(Analytics);
})(window);
/* eslint-enable prefer-template */

View File

@@ -35,8 +35,10 @@ import org.jitsi.meet.sdk.JitsiMeetActivity;
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
/**
* The one and only Activity that the Jitsi Meet app needs. The
@@ -73,6 +75,7 @@ public class MainActivity extends JitsiMeetActivity {
*/
private String defaultURL;
// JitsiMeetActivity overrides
//
@@ -144,12 +147,12 @@ public class MainActivity extends JitsiMeetActivity {
}
private void setJitsiMeetConferenceDefaultOptions() {
// Set default options
JitsiMeetConferenceOptions defaultOptions
= new JitsiMeetConferenceOptions.Builder()
.setServerURL(buildURL(defaultURL))
.setFeatureFlag("welcomepage.enabled", true)
.setFeatureFlag("resolution", 360)
.setFeatureFlag("server-url-change.enabled", !configurationByRestrictions)
.build();
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
@@ -224,7 +227,7 @@ public class MainActivity extends JitsiMeetActivity {
private @Nullable URL buildURL(String urlStr) {
try {
return new URL(urlStr);
} catch (Exception e) {
} catch (MalformedURLException e) {
return null;
}
}

View File

@@ -44,7 +44,7 @@ ext {
googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild
//React Native Version
rnVersion = "0.73.8"
rnVersion = "0.72.9"
}
allprojects {

View File

@@ -11,7 +11,7 @@
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
@@ -26,5 +26,5 @@ android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
appVersion=24.3.0
sdkVersion=9.3.0
appVersion=99.0.0
sdkVersion=99.0.0

View File

@@ -47,7 +47,7 @@ dependencies {
implementation 'com.facebook.fresco:animated-gif:2.5.0'
implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation 'com.jakewharton.timber:timber:4.7.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'

View File

@@ -12,10 +12,8 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-feature
android:glEsVersion="0x00020000"
@@ -51,7 +49,11 @@
<service
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
android:foregroundServiceType="mediaPlayback|microphone" />
android:foregroundServiceType="mediaPlayback" />
<service
android:name="org.jitsi.meet.sdk.JitsiMeetMediaProjectionService"
android:foregroundServiceType="mediaProjection" />
<provider
android:name="com.reactnativecommunity.webview.RNCWebViewFileProvider"

View File

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

View File

@@ -89,9 +89,7 @@ public class BroadcastEvent {
CHAT_MESSAGE_RECEIVED("org.jitsi.meet.CHAT_MESSAGE_RECEIVED"),
CHAT_TOGGLED("org.jitsi.meet.CHAT_TOGGLED"),
VIDEO_MUTED_CHANGED("org.jitsi.meet.VIDEO_MUTED_CHANGED"),
READY_TO_CLOSE("org.jitsi.meet.READY_TO_CLOSE"),
TRANSCRIPTION_CHUNK_RECEIVED("org.jitsi.meet.TRANSCRIPTION_CHUNK_RECEIVED"),
CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED("org.jitsi.meet.CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED");
READY_TO_CLOSE("org.jitsi.meet.READY_TO_CLOSE");
private static final String CONFERENCE_BLURRED_NAME = "CONFERENCE_BLURRED";
private static final String CONFERENCE_FOCUSED_NAME = "CONFERENCE_FOCUSED";
@@ -108,8 +106,6 @@ public class BroadcastEvent {
private static final String CHAT_TOGGLED_NAME = "CHAT_TOGGLED";
private static final String VIDEO_MUTED_CHANGED_NAME = "VIDEO_MUTED_CHANGED";
private static final String READY_TO_CLOSE_NAME = "READY_TO_CLOSE";
private static final String TRANSCRIPTION_CHUNK_RECEIVED_NAME = "TRANSCRIPTION_CHUNK_RECEIVED";
private static final String CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED_NAME = "CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED";
private final String action;
@@ -162,10 +158,6 @@ public class BroadcastEvent {
return VIDEO_MUTED_CHANGED;
case READY_TO_CLOSE_NAME:
return READY_TO_CLOSE;
case TRANSCRIPTION_CHUNK_RECEIVED_NAME:
return TRANSCRIPTION_CHUNK_RECEIVED;
case CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED_NAME:
return CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED;
}
return null;

View File

@@ -255,14 +255,6 @@ public class JitsiMeetActivity extends AppCompatActivity
finish();
}
// protected void onTranscriptionChunkReceived(HashMap<String, Object> extraData) {
// JitsiMeetLogger.i("Transcription chunk received: " + extraData);
// }
// protected void onCustomOverflowMenuButtonPressed(HashMap<String, Object> extraData) {
// JitsiMeetLogger.i("Custom overflow menu button pressed: " + extraData);
// }
// Activity lifecycle methods
//
@@ -294,8 +286,8 @@ public class JitsiMeetActivity extends AppCompatActivity
@Override
protected void onUserLeaveHint() {
if (this.jitsiView != null) {
this.jitsiView.enterPictureInPicture();
if (this.jitsiView != null) {
this.jitsiView .enterPictureInPicture();
}
}
@@ -346,12 +338,6 @@ public class JitsiMeetActivity extends AppCompatActivity
case READY_TO_CLOSE:
onReadyToClose();
break;
// case TRANSCRIPTION_CHUNK_RECEIVED:
// onTranscriptionChunkReceived(event.getData());
// break;
// case CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED:
// onCustomOverflowMenuButtonPressed(event.getData());
// break;
}
}
}

View File

@@ -21,7 +21,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import java.net.URL;
import java.util.ArrayList;
/**
@@ -230,12 +229,6 @@ public class JitsiMeetConferenceOptions implements Parcelable {
return this;
}
public Builder setConfigOverride(String config, ArrayList<Bundle> arrayList) {
this.config.putParcelableArrayList(config, arrayList);
return this;
}
/**
* Builds the immutable {@link JitsiMeetConferenceOptions} object with the configuration
* that this {@link Builder} instance specified.

View File

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

View File

@@ -0,0 +1,100 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.IBinder;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
* responsible for presenting an ongoing notification when a conference is in progress.
* The service will help keep the app running while in the background.
*
* See: https://developer.android.com/guide/components/services
*/
public class JitsiMeetMediaProjectionService extends Service {
private static final String TAG = JitsiMeetMediaProjectionService.class.getSimpleName();
public static void launch(Context context) {
OngoingNotification.createOngoingConferenceNotificationChannel();
Intent intent = new Intent(context, JitsiMeetMediaProjectionService.class);
ComponentName componentName;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
componentName = context.startForegroundService(intent);
} else {
componentName = context.startService(intent);
}
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
return;
}
if (componentName == null) {
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetMediaProjectionService.class);
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
Notification notification = OngoingNotification.buildOngoingConferenceNotification(null);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(OngoingNotification.NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -16,10 +16,6 @@
package org.jitsi.meet.sdk;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.Manifest.permission.RECORD_AUDIO;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
@@ -27,7 +23,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.Bundle;
@@ -35,14 +30,9 @@ import android.os.IBinder;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.facebook.react.modules.core.PermissionListener;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
@@ -60,15 +50,10 @@ public class JitsiMeetOngoingConferenceService extends Service
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver();
private static final int PERMISSIONS_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE);
private boolean isAudioMuted;
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
private static void doLaunch(Context context, HashMap<String, Object> extraData) {
OngoingNotification.createNotificationChannel((Activity) context);
public static void launch(Context context, HashMap<String, Object> extraData) {
OngoingNotification.createOngoingConferenceNotificationChannel();
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
@@ -96,55 +81,6 @@ public class JitsiMeetOngoingConferenceService extends Service
}
}
public static void launch(Context context, HashMap<String, Object> extraData) {
List<String> permissionsList = new ArrayList<>();
PermissionListener listener = new PermissionListener() {
@Override
public boolean onRequestPermissionsResult(int i, String[] strings, int[] results) {
int counter = 0;
if (results.length > 0) {
for (int result : results) {
if (result == PackageManager.PERMISSION_GRANTED) {
counter++;
}
}
if (counter == results.length){
doLaunch(context, extraData);
JitsiMeetLogger.w(TAG + " Service launched, permissions were granted");
} else {
JitsiMeetLogger.w(TAG + " Couldn't launch service, permissions were not granted");
}
}
return true;
}
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissionsList.add(POST_NOTIFICATIONS);
permissionsList.add(RECORD_AUDIO);
}
String[] permissionsArray = new String[ permissionsList.size() ];
permissionsArray = permissionsList.toArray( permissionsArray );
if (permissionsArray.length > 0) {
JitsiMeetActivityDelegate.requestPermissions(
(Activity) context,
permissionsArray,
PERMISSIONS_REQUEST_CODE,
listener
);
} else {
doLaunch(context, extraData);
JitsiMeetLogger.w(TAG + " Service launched");
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
context.stopService(intent);
@@ -154,17 +90,15 @@ public class JitsiMeetOngoingConferenceService extends Service
public void onCreate() {
super.onCreate();
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this);
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(OngoingNotification.NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(NOTIFICATION_ID, notification);
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
}
}
@@ -196,13 +130,13 @@ public class JitsiMeetOngoingConferenceService extends Service
if (isAudioMuted != null) {
this.isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this);
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, notification);
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
}
}
@@ -282,13 +216,13 @@ public class JitsiMeetOngoingConferenceService extends Service
@Override
public void onReceive(Context context, Intent intent) {
isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, context);
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't update service, notification is null");
} else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, notification);
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " audio muted changed");
}

View File

@@ -0,0 +1,10 @@
package org.jitsi.meet.sdk;
import java.util.ArrayList;
import java.util.List;
public class NotificationChannels {
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
public static List<String> allIds = new ArrayList<String>() {{ add(ONGOING_CONFERENCE_CHANNEL_ID); }};
}

View File

@@ -16,21 +16,22 @@
package org.jitsi.meet.sdk;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import static org.jitsi.meet.sdk.NotificationChannels.ONGOING_CONFERENCE_CHANNEL_ID;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import android.os.Build;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.Random;
/**
* Helper class for creating the ongoing notification which is used with
@@ -40,15 +41,15 @@ import android.os.Build;
class OngoingNotification {
private static final String TAG = OngoingNotification.class.getSimpleName();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
private static long startingTime = 0;
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
static void createNotificationChannel(Activity context) {
static void createOngoingConferenceNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
Context context = ReactInstanceManagerHolder.getCurrentActivity();
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;
@@ -59,13 +60,12 @@ class OngoingNotification {
NotificationChannel channel
= notificationManager.getNotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID);
if (channel != null) {
// The channel was already created, no need to do it again.
return;
}
channel = new NotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID, context.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT);
channel = new NotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID, context.getString(R.string.ongoing_notification_action_unmute), NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(false);
channel.enableVibration(false);
channel.setShowBadge(false);
@@ -73,8 +73,8 @@ class OngoingNotification {
notificationManager.createNotificationChannel(channel);
}
static Notification buildOngoingConferenceNotification(Boolean isMuted, Context context) {
static Notification buildOngoingConferenceNotification(Boolean isMuted) {
Context context = ReactInstanceManagerHolder.getCurrentActivity();
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
@@ -92,7 +92,7 @@ class OngoingNotification {
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setContentText(isMuted != null ? context.getString(R.string.ongoing_notification_text) : context.getString(R.string.ongoing_notification_action_screenshare))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setOngoing(true)
@@ -103,6 +103,10 @@ class OngoingNotification {
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
if (isMuted == null) {
return builder.build();
}
NotificationCompat.Action hangupAction = createAction(context, JitsiMeetOngoingConferenceService.Action.HANGUP, R.string.ongoing_notification_action_hang_up);
JitsiMeetOngoingConferenceService.Action toggleAudioAction = isMuted

View File

@@ -37,7 +37,6 @@ import com.oney.WebRTCModule.webrtcutils.H264AndSoftwareVideoEncoderFactory;
import org.devio.rn.splashscreen.SplashScreenModule;
import org.webrtc.EglBase;
import org.webrtc.Logging;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
@@ -68,6 +67,7 @@ class ReactInstanceManagerHolder {
new DropboxModule(reactContext),
new ExternalAPIModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new JitsiMeetMediaProjectionModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),
new SplashScreenModule(reactContext),
@@ -241,8 +241,6 @@ class ReactInstanceManagerHolder {
options.videoDecoderFactory = new H264AndSoftwareVideoDecoderFactory(eglContext);
options.videoEncoderFactory = new H264AndSoftwareVideoEncoderFactory(eglContext);
options.enableMediaProjectionService = true;
// options.loggingSeverity = Logging.Severity.LS_INFO;
Log.d(TAG, "initializing RN with Activity");

View File

@@ -1,12 +1,11 @@
<resources>
<string name="app_name">Jitsi Meet SDK</string>
<string name="dropbox_app_key"></string>
<string name="media_projection_notification_title">Media projection</string>
<string name="media_projection_notification_text">You are currently sharing your screen.</string>
<string name="ongoing_notification_title">Ongoing meeting</string>
<string name="ongoing_notification_text">You are currently in a meeting. Tap to return to it.</string>
<string name="ongoing_notification_action_hang_up">Hang up</string>
<string name="ongoing_notification_action_mute">Mute</string>
<string name="ongoing_notification_action_screenshare">You are currently screen-sharing. Tap to return to the meeting.</string>
<string name="ongoing_notification_action_unmute">Unmute</string>
<string name="ongoing_notification_channel_name">Ongoing Conference Notifications</string>
</resources>

View File

@@ -11,7 +11,7 @@ project(':react-native-background-timer').projectDir = new File(rootProject.proj
include ':react-native-calendar-events'
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')
include ':react-native-community_clipboard'
project(':react-native-community_clipboard').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-clipboard/clipboard/android')
project(':react-native-community_clipboard').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/clipboard/android')
include ':react-native-community_netinfo'
project(':react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android')
include ':react-native-default-preference'

View File

@@ -2,8 +2,10 @@
import { jitsiLocalStorage } from '@jitsi/js-utils';
import Logger from '@jitsi/logger';
import EventEmitter from 'events';
import { ENDPOINT_TEXT_MESSAGE_NAME } from './modules/API/constants';
import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from './modules/UI/UIErrors';
import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
import Recorder from './modules/recorder/Recorder';
import { createTaskQueue } from './modules/util/helpers';
@@ -16,6 +18,7 @@ import {
import { sendAnalytics } from './react/features/analytics/functions';
import {
maybeRedirectToWelcomePage,
redirectToStaticPage,
reloadWithStoredParams
} from './react/features/app/actions';
import { showModeratedNotification } from './react/features/av-moderation/actions';
@@ -35,7 +38,6 @@ import {
dataChannelClosed,
dataChannelOpened,
e2eRttChanged,
endpointMessageReceived,
kickedOut,
lockStateChanged,
nonParticipantMessageReceived,
@@ -53,7 +55,7 @@ import {
getConferenceOptions,
sendLocalParticipant
} from './react/features/base/conference/functions';
import { getReplaceParticipant, getSsrcRewritingFeatureFlag } from './react/features/base/config/functions';
import { getReplaceParticipant } from './react/features/base/config/functions';
import { connect } from './react/features/base/connection/actions.web';
import {
checkAndNotifyForNewDevice,
@@ -75,6 +77,7 @@ import {
JitsiConferenceEvents,
JitsiE2ePingEvents,
JitsiMediaDevicesEvents,
JitsiTrackErrors,
JitsiTrackEvents,
browser
} from './react/features/base/lib-jitsi-meet';
@@ -83,12 +86,11 @@ import {
setAudioAvailable,
setAudioMuted,
setAudioUnmutePermissions,
setInitialGUMPromise,
setVideoAvailable,
setVideoMuted,
setVideoUnmutePermissions
} from './react/features/base/media/actions';
import { MEDIA_TYPE, VIDEO_TYPE } from './react/features/base/media/constants';
import { MEDIA_TYPE } from './react/features/base/media/constants';
import {
getStartWithAudioMuted,
getStartWithVideoMuted,
@@ -116,11 +118,8 @@ import {
import { updateSettings } from './react/features/base/settings/actions';
import {
addLocalTrack,
createInitialAVTracks,
destroyLocalTracks,
displayErrorsForCreateInitialLocalTracks,
replaceLocalTrack,
setGUMPendingStateOnFailedTracks,
toggleScreensharing as toggleScreensharingA,
trackAdded,
trackRemoved
@@ -155,17 +154,24 @@ import {
import { isModerationNotificationDisplayed } from './react/features/notifications/functions';
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
import { suspendDetected } from './react/features/power-monitor/actions';
import { initPrejoin, isPrejoinPageVisible } from './react/features/prejoin/functions';
import { initPrejoin } from './react/features/prejoin/actions';
import { isPrejoinPageVisible } from './react/features/prejoin/functions';
import { disableReceiver, stopReceiver } from './react/features/remote-control/actions';
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 { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect';
import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
import { endpointMessageReceived } from './react/features/subtitles/actions.any';
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
import { muteLocal } from './react/features/video-menu/actions.any';
import { iAmVisitor } from './react/features/visitors/functions';
import UIEvents from './service/UI/UIEvents';
const logger = Logger.getLogger(__filename);
const eventEmitter = new EventEmitter();
let room;
/*
@@ -275,6 +281,12 @@ class ConferenceConnector {
switch (err) {
case JitsiConferenceErrors.NOT_ALLOWED_ERROR: {
// let's show some auth not allowed page
APP.store.dispatch(redirectToStaticPage('static/authError.html'));
break;
}
case JitsiConferenceErrors.RESERVATION_ERROR: {
const [ code, msg ] = params;
@@ -384,6 +396,21 @@ function disconnect() {
return APP.connection.disconnect().then(onDisconnected, onDisconnected);
}
/**
* Sets the GUM pending state for the tracks that have failed.
*
* NOTE: Some of the track that we will be setting to GUM pending state NONE may not have failed but they may have
* been requested. This won't be a problem because their current GUM pending state will be NONE anyway.
* @param {JitsiLocalTrack} tracks - The tracks that have been created.
* @returns {void}
*/
function setGUMPendingStateOnFailedTracks(tracks) {
const tracksTypes = tracks.map(track => track.getType());
const nonPendingTracks = [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ].filter(type => !tracksTypes.includes(type));
APP.store.dispatch(gumPending(nonPendingTracks, IGUMPendingState.NONE));
}
export default {
/**
* Flag used to delay modification of the muted status of local media tracks
@@ -482,12 +509,57 @@ export default {
return [];
});
} else if (requestedAudio || requestedVideo) {
tryCreateLocalTracks = APP.store.dispatch(createInitialAVTracks({
APP.store.dispatch(gumPending(initialDevices, IGUMPendingState.PENDING_UNMUTE));
tryCreateLocalTracks = createLocalTracksF({
devices: initialDevices,
timeout,
firePermissionPromptIsShownEvent: true
})).then(({ tracks, errors: pErrors }) => {
Object.assign(errors, pErrors);
})
.catch(async error => {
if (error.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
errors.audioAndVideoError = error;
return [];
}
// Retry with separate gUM calls.
const gUMPromises = [];
const tracks = [];
if (requestedAudio) {
gUMPromises.push(createLocalTracksF(audioOptions));
}
if (requestedVideo) {
gUMPromises.push(createLocalTracksF({
devices: [ MEDIA_TYPE.VIDEO ],
timeout,
firePermissionPromptIsShownEvent: true
}));
}
const results = await Promise.allSettled(gUMPromises);
let errorMsg;
results.forEach((result, idx) => {
if (result.status === 'fulfilled') {
tracks.push(result.value[0]);
} else {
errorMsg = result.reason;
const isAudio = idx === 0;
logger.error(`${isAudio ? 'Audio' : 'Video'} track creation failed with error ${errorMsg}`);
if (isAudio) {
errors.audioOnlyError = errorMsg;
} else {
errors.videoOnlyError = errorMsg;
}
}
});
if (errors.audioOnlyError && errors.videoOnlyError) {
errors.audioAndVideoError = errorMsg;
}
return tracks;
});
@@ -508,6 +580,42 @@ export default {
};
},
/**
* Displays error notifications according to the state carried by {@code errors} object returned
* by {@link createInitialLocalTracks}.
* @param {Object} errors - the errors (if any) returned by {@link createInitialLocalTracks}.
*
* @returns {void}
* @private
*/
_displayErrorsForCreateInitialLocalTracks(errors) {
const {
audioAndVideoError,
audioOnlyError,
screenSharingError,
videoOnlyError
} = errors;
// FIXME If there will be microphone error it will cover any screensharing dialog, but it's still better than in
// the reverse order where the screensharing dialog will sometimes be closing the microphone alert
// ($.prompt.close(); is called). Need to figure out dialogs chaining to fix that.
if (screenSharingError) {
this._handleScreenSharingError(screenSharingError);
}
if (audioAndVideoError || audioOnlyError) {
if (audioOnlyError || videoOnlyError) {
// If both requests for 'audio' + 'video' and 'audio' only failed, we assume that there are some
// problems with user's microphone and show corresponding dialog.
APP.store.dispatch(notifyMicError(audioOnlyError));
APP.store.dispatch(notifyCameraError(videoOnlyError));
} else {
// If request for 'audio' + 'video' failed, but request for 'audio' only was OK, we assume that we had
// problems with camera and show corresponding dialog.
APP.store.dispatch(notifyCameraError(audioAndVideoError));
}
}
},
startConference(tracks) {
tracks.forEach(track => {
if ((track.isAudioTrack() && this.isLocalAudioMuted())
@@ -571,8 +679,6 @@ export default {
startWithVideoMuted: getStartWithVideoMuted(state) || isUserInteractionRequiredForUnmute(state)
};
logger.debug(`Executed conference.init with roomName: ${roomName}`);
this.roomName = roomName;
try {
@@ -591,7 +697,11 @@ export default {
const handleInitialTracks = (options, tracks) => {
let localTracks = tracks;
if (options.startWithAudioMuted) {
// No local tracks are added when user joins as a visitor.
if (iAmVisitor(state)) {
return [];
}
if (options.startWithAudioMuted || room?.isStartAudioMuted()) {
// Always add the track on Safari because of a known issue where audio playout doesn't happen
// if the user joins audio and video muted, i.e., if there is no local media capture.
if (browser.isWebKitBased()) {
@@ -600,38 +710,58 @@ export default {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
}
}
if (room?.isStartVideoMuted()) {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
}
return localTracks;
};
const { dispatch, getState } = APP.store;
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
dispatch(setInitialGUMPromise(tryCreateLocalTracks.then(async tr => {
const tracks = handleInitialTracks(initialOptions, tr);
if (isPrejoinPageVisible(state)) {
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
const localTracks = await tryCreateLocalTracks;
// Initialize device list a second time to ensure device labels get populated in case of an initial gUM
// acceptance; otherwise they may remain as empty strings.
this._initDeviceList(true);
if (isPrejoinPageVisible(getState())) {
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
dispatch(setInitialGUMPromise());
if (isPrejoinPageVisible(state)) {
APP.store.dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
// Note: Not sure if initPrejoin needs to be async. But let's wait for it just to be sure the
// tracks are added.
initPrejoin(tracks, errors, dispatch);
} else {
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
setGUMPendingStateOnFailedTracks(tracks, APP.store.dispatch);
return APP.store.dispatch(initPrejoin(localTracks, errors));
}
return {
tracks,
errors
};
})));
logger.debug('Prejoin screen no longer displayed at the time when tracks were created');
if (!isPrejoinPageVisible(getState())) {
dispatch(connect());
this._displayErrorsForCreateInitialLocalTracks(errors);
const tracks = handleInitialTracks(initialOptions, localTracks);
setGUMPendingStateOnFailedTracks(tracks);
return this._setLocalAudioVideoStreams(tracks);
}
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
return Promise.all([
tryCreateLocalTracks.then(tr => {
this._displayErrorsForCreateInitialLocalTracks(errors);
return tr;
}).then(tr => {
this._initDeviceList(true);
const filteredTracks = handleInitialTracks(initialOptions, tr);
setGUMPendingStateOnFailedTracks(filteredTracks);
return filteredTracks;
}),
APP.store.dispatch(connect())
]).then(([ tracks, _ ]) => {
this.startConference(tracks).catch(logger.error);
});
},
/**
@@ -685,13 +815,11 @@ export default {
/**
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
*
* @param {boolean} mute true for mute and false for unmute.
* @param {boolean} [showUI] when set to false will not display any error
* dialogs in case of media permissions error.
* @returns {Promise}
*/
async muteAudio(mute, showUI = true) {
muteAudio(mute, showUI = true) {
const state = APP.store.getState();
if (!mute
@@ -731,8 +859,7 @@ export default {
};
APP.store.dispatch(gumPending([ MEDIA_TYPE.AUDIO ], IGUMPendingState.PENDING_UNMUTE));
await createLocalTracksF({ devices: [ 'audio' ] })
createLocalTracksF({ devices: [ 'audio' ] })
.then(([ audioTrack ]) => audioTrack)
.catch(error => {
maybeShowErrorDialog(error);
@@ -1108,7 +1235,7 @@ export default {
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(options);
const localTracks = await tryCreateLocalTracks;
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
this._displayErrorsForCreateInitialLocalTracks(errors);
localTracks.forEach(track => {
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|| (track.isVideoTrack() && this.isLocalVideoMuted())) {
@@ -1126,31 +1253,13 @@ export default {
room = APP.connection.initJitsiConference(APP.conference.roomName, this._getConferenceOptions());
// Filter out the tracks that are muted (except on Safari).
let tracks = localTracks;
if (!browser.isWebKitBased()) {
const mutedTrackTypes = [];
tracks = localTracks.filter(track => {
if (!track.isMuted()) {
return true;
}
if (track.getVideoType() !== VIDEO_TYPE.DESKTOP) {
mutedTrackTypes.push(track.getType());
}
return false;
});
APP.store.dispatch(gumPending(mutedTrackTypes, IGUMPendingState.NONE));
}
const tracks = browser.isWebKitBased() ? localTracks : localTracks.filter(track => !track.isMuted());
this._setLocalAudioVideoStreams(tracks);
this._room = room; // FIXME do not use this
APP.store.dispatch(_conferenceWillJoin(room));
this._setLocalAudioVideoStreams(tracks);
sendLocalParticipant(APP.store, room);
this._setupListeners();
@@ -1208,11 +1317,12 @@ export default {
* @returns {Promise}
*/
useVideoStream(newTrack) {
const state = APP.store.getState();
logger.debug(`useVideoStream: ${newTrack}`);
return new Promise((resolve, reject) => {
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
const state = APP.store.getState();
const oldTrack = getLocalJitsiVideoTrack(state);
logger.debug(`useVideoStream: Replacing ${oldTrack} with ${newTrack}`);
@@ -1261,7 +1371,8 @@ export default {
return;
}
APP.store.dispatch(replaceLocalTrack(oldTrack, newTrack, room))
APP.store.dispatch(
replaceLocalTrack(oldTrack, newTrack, room))
.then(() => {
this.updateAudioIconEnabled();
})
@@ -1448,6 +1559,50 @@ export default {
});
},
/**
* Handles {@link JitsiTrackError} returned by the lib-jitsi-meet when
* trying to create screensharing track. It will either do nothing if
* the dialog was canceled on user's request or display an error if
* screensharing couldn't be started.
* @param {JitsiTrackError} error - The error returned by
* {@link _createDesktopTrack} Promise.
* @private
*/
_handleScreenSharingError(error) {
if (error.name === JitsiTrackErrors.SCREENSHARING_USER_CANCELED) {
return;
}
logger.error('failed to share local desktop', error);
// Handling:
// JitsiTrackErrors.CONSTRAINT_FAILED
// JitsiTrackErrors.PERMISSION_DENIED
// JitsiTrackErrors.SCREENSHARING_GENERIC_ERROR
// and any other
let descriptionKey;
let titleKey;
if (error.name === JitsiTrackErrors.PERMISSION_DENIED) {
descriptionKey = 'dialog.screenSharingPermissionDeniedError';
titleKey = 'dialog.screenSharingFailedTitle';
} else if (error.name === JitsiTrackErrors.CONSTRAINT_FAILED) {
descriptionKey = 'dialog.cameraConstraintFailedError';
titleKey = 'deviceError.cameraError';
} else if (error.name === JitsiTrackErrors.SCREENSHARING_GENERIC_ERROR) {
descriptionKey = 'dialog.screenSharingFailed';
titleKey = 'dialog.screenSharingFailedTitle';
} else if (error === AUDIO_ONLY_SCREEN_SHARE_NO_TRACK) {
descriptionKey = 'notify.screenShareNoAudio';
titleKey = 'notify.screenShareNoAudioTitle';
}
APP.store.dispatch(showErrorNotification({
descriptionKey,
titleKey
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
},
/**
* Setup interaction between conference and UI.
*/
@@ -1620,11 +1775,7 @@ export default {
room.on(
JitsiConferenceEvents.CONFERENCE_CREATED_TIMESTAMP,
conferenceTimestamp => {
APP.store.dispatch(conferenceTimestampChanged(conferenceTimestamp));
APP.API.notifyConferenceCreatedTimestamp(conferenceTimestamp);
}
);
conferenceTimestamp => APP.store.dispatch(conferenceTimestampChanged(conferenceTimestamp)));
room.on(
JitsiConferenceEvents.DISPLAY_NAME_CHANGED,
@@ -1659,18 +1810,6 @@ export default {
});
}
);
room.on(
JitsiConferenceEvents.SILENT_STATUS_CHANGED,
(id, isSilent) => {
APP.store.dispatch(participantUpdated({
conference: room,
id,
isSilent
}));
}
);
room.on(
JitsiConferenceEvents.BOT_TYPE_CHANGED,
(id, botType) => {
@@ -1685,24 +1824,28 @@ export default {
room.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(participant, data) => {
APP.store.dispatch(endpointMessageReceived(participant, data));
if (data?.name === ENDPOINT_TEXT_MESSAGE_NAME) {
APP.API.notifyEndpointTextMessageReceived({
senderInfo: {
jid: participant.getJid(),
id: participant.getId()
},
eventData: data
});
(...args) => {
APP.store.dispatch(endpointMessageReceived(...args));
if (args && args.length >= 2) {
const [ sender, eventData ] = args;
if (eventData.name === ENDPOINT_TEXT_MESSAGE_NAME) {
APP.API.notifyEndpointTextMessageReceived({
senderInfo: {
jid: sender._jid,
id: sender._id
},
eventData
});
}
}
});
room.on(
JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
(id, data) => {
APP.store.dispatch(nonParticipantMessageReceived(id, data));
APP.API.notifyNonParticipantMessageReceived(id, data);
(...args) => {
APP.store.dispatch(nonParticipantMessageReceived(...args));
APP.API.notifyNonParticipantMessageReceived(...args);
});
room.on(
@@ -1758,12 +1901,21 @@ export default {
JitsiE2ePingEvents.E2E_RTT_CHANGED,
(...args) => APP.store.dispatch(e2eRttChanged(...args)));
APP.UI.addListener(UIEvents.AUDIO_MUTED, muted => {
this.muteAudio(muted);
});
APP.UI.addListener(UIEvents.VIDEO_MUTED, (muted, showUI = false) => {
this.muteVideo(muted, showUI);
});
room.addCommandListener(this.commands.defaults.ETHERPAD,
({ value }) => {
APP.UI.initEtherpad(value);
}
);
APP.UI.addListener(UIEvents.EMAIL_CHANGED,
this.changeLocalEmail.bind(this));
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
APP.store.dispatch(participantUpdated({
conference: room,
@@ -1783,6 +1935,9 @@ export default {
}));
});
APP.UI.addListener(UIEvents.NICKNAME_CHANGED,
this.changeLocalDisplayName.bind(this));
room.on(
JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
({ audio, video }) => {
@@ -1829,146 +1984,123 @@ export default {
room.on(
JitsiConferenceEvents.DATA_CHANNEL_CLOSED, ev => {
const state = APP.store.getState();
const { dataChannelOpen } = state['features/base/conference'];
const timeout = typeof dataChannelOpen === 'undefined' ? 15000 : 60000;
// Show the notification only when the data channel connection doesn't get re-established in 60 secs if
// it was already established at the beginning of the call, show it sooner otherwise. This notification
// can be confusing and alarming to users even when there is no significant impact to user experience
// if the the reconnect happens immediately.
setTimeout(() => {
const { dataChannelOpen: open } = APP.store.getState()['features/base/conference'];
if (!open) {
const descriptionKey = getSsrcRewritingFeatureFlag(state)
? 'notify.dataChannelClosedDescriptionWithAudio' : 'notify.dataChannelClosedDescription';
const titleKey = getSsrcRewritingFeatureFlag(state)
? 'notify.dataChannelClosedWithAudio' : 'notify.dataChannelClosed';
APP.store.dispatch(dataChannelClosed(ev.code, ev.reason));
APP.store.dispatch(showWarningNotification({
descriptionKey,
titleKey,
uid: DATA_CHANNEL_CLOSED_NOTIFICATION_ID
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}
}, timeout);
APP.store.dispatch(dataChannelClosed(ev.code, ev.reason));
APP.store.dispatch(showWarningNotification({
descriptionKey: 'notify.dataChannelClosedDescription',
titleKey: 'notify.dataChannelClosed',
uid: DATA_CHANNEL_CLOSED_NOTIFICATION_ID
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}
);
},
/**
* Handles audio device changes.
*
* @param {string} cameraDeviceId - The new device id.
* @returns {Promise}
*/
async onAudioDeviceChanged(micDeviceId) {
const audioWasMuted = this.isLocalAudioMuted();
// Disable noise suppression if it was enabled on the previous track.
await APP.store.dispatch(setNoiseSuppressionEnabled(false));
// When the 'default' mic needs to be selected, we need to pass the real device id to gUM instead of
// 'default' in order to get the correct MediaStreamTrack from chrome because of the following bug.
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689.
const isDefaultMicSelected = micDeviceId === 'default';
const selectedDeviceId = isDefaultMicSelected
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: micDeviceId;
logger.info(`Switching audio input device to ${selectedDeviceId}`);
sendAnalytics(createDeviceChangedEvent('audio', 'input'));
createLocalTracksF({
devices: [ 'audio' ],
micDeviceId: selectedDeviceId
})
.then(([ stream ]) => {
// if audio was muted before changing the device, mute
// with the new device
if (audioWasMuted) {
return stream.mute()
.then(() => stream);
}
return stream;
})
.then(async stream => {
await this._maybeApplyAudioMixerEffect(stream);
return this.useAudioStream(stream);
})
.then(() => {
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
if (localAudio && isDefaultMicSelected) {
// workaround for the default device to be shown as selected in the
// settings even when the real device id was passed to gUM because of the
// above mentioned chrome bug.
localAudio._realDeviceId = localAudio.deviceId = 'default';
}
})
.catch(err => {
logger.error(`Failed to switch to selected audio input device ${selectedDeviceId}, error=${err}`);
APP.store.dispatch(notifyMicError(err));
// call hangup
APP.UI.addListener(UIEvents.HANGUP, () => {
this.hangup(true);
});
},
/**
* Handles video device changes.
*
* @param {string} cameraDeviceId - The new device id.
* @returns {void}
*/
onVideoDeviceChanged(cameraDeviceId) {
const videoWasMuted = this.isLocalVideoMuted();
const localVideoTrack = getLocalJitsiVideoTrack(APP.store.getState());
APP.UI.addListener(
UIEvents.VIDEO_DEVICE_CHANGED,
cameraDeviceId => {
const videoWasMuted = this.isLocalVideoMuted();
const localVideoTrack = getLocalJitsiVideoTrack(APP.store.getState());
if (localVideoTrack?.getDeviceId() === cameraDeviceId) {
return;
}
if (localVideoTrack?.getDeviceId() === cameraDeviceId) {
return;
}
sendAnalytics(createDeviceChangedEvent('video', 'input'));
sendAnalytics(createDeviceChangedEvent('video', 'input'));
createLocalTracksF({
devices: [ 'video' ],
cameraDeviceId
})
.then(([ stream ]) => {
// if we are in audio only mode or video was muted before
// changing device, then mute
if (this.isAudioOnly() || videoWasMuted) {
return stream.mute()
.then(() => stream);
createLocalTracksF({
devices: [ 'video' ],
cameraDeviceId
})
.then(([ stream ]) => {
// if we are in audio only mode or video was muted before
// changing device, then mute
if (this.isAudioOnly() || videoWasMuted) {
return stream.mute()
.then(() => stream);
}
return stream;
})
.then(stream => {
logger.info(`Switching the local video device to ${cameraDeviceId}.`);
return this.useVideoStream(stream);
})
.catch(error => {
logger.error(`Failed to switch to selected camera:${cameraDeviceId}, error:${error}`);
return APP.store.dispatch(notifyCameraError(error));
});
}
);
return stream;
})
.then(stream => {
logger.info(`Switching the local video device to ${cameraDeviceId}.`);
APP.UI.addListener(
UIEvents.AUDIO_DEVICE_CHANGED,
async micDeviceId => {
const audioWasMuted = this.isLocalAudioMuted();
return this.useVideoStream(stream);
})
.catch(error => {
logger.error(`Failed to switch to selected camera:${cameraDeviceId}, error:${error}`);
// Disable noise suppression if it was enabled on the previous track.
await APP.store.dispatch(setNoiseSuppressionEnabled(false));
return APP.store.dispatch(notifyCameraError(error));
// When the 'default' mic needs to be selected, we need to pass the real device id to gUM instead of
// 'default' in order to get the correct MediaStreamTrack from chrome because of the following bug.
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689.
const isDefaultMicSelected = micDeviceId === 'default';
const selectedDeviceId = isDefaultMicSelected
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: micDeviceId;
logger.info(`Switching audio input device to ${selectedDeviceId}`);
sendAnalytics(createDeviceChangedEvent('audio', 'input'));
createLocalTracksF({
devices: [ 'audio' ],
micDeviceId: selectedDeviceId
})
.then(([ stream ]) => {
// if audio was muted before changing the device, mute
// with the new device
if (audioWasMuted) {
return stream.mute()
.then(() => stream);
}
return stream;
})
.then(async stream => {
await this._maybeApplyAudioMixerEffect(stream);
return this.useAudioStream(stream);
})
.then(() => {
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
if (localAudio && isDefaultMicSelected) {
// workaround for the default device to be shown as selected in the
// settings even when the real device id was passed to gUM because of the
// above mentioned chrome bug.
localAudio._realDeviceId = localAudio.deviceId = 'default';
}
})
.catch(err => {
logger.error(`Failed to switch to selected audio input device ${selectedDeviceId}, error=${err}`);
APP.store.dispatch(notifyMicError(err));
});
}
);
APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, () => {
// Immediately update the UI by having remote videos and the large video update themselves.
const displayedUserId = APP.UI.getLargeVideoID();
if (displayedUserId) {
APP.UI.updateLargeVideo(displayedUserId, true);
}
});
},
/**
* Handles audio only changes.
*/
onToggleAudioOnly() {
// Immediately update the UI by having remote videos and the large video update themselves.
const displayedUserId = APP.UI.getLargeVideoID();
if (displayedUserId) {
APP.UI.updateLargeVideo(displayedUserId, true);
}
},
/**
* Cleanups local conference on suspend.
*/
@@ -2261,6 +2393,8 @@ export default {
this.deviceChangeListener);
}
APP.UI.removeAllListeners();
let feedbackResultPromise = Promise.resolve({});
if (requestFeedback) {
@@ -2367,6 +2501,37 @@ export default {
room.sendEndpointMessage(to, payload);
},
/**
* Adds new listener.
* @param {String} eventName the name of the event
* @param {Function} listener the listener.
*/
addListener(eventName, listener) {
eventEmitter.addListener(eventName, listener);
},
/**
* Removes listener.
* @param {String} eventName the name of the event that triggers the
* listener
* @param {Function} listener the listener.
*/
removeListener(eventName, listener) {
eventEmitter.removeListener(eventName, listener);
},
/**
* Changes the display name for the local user
* @param nickname {string} the new display name
*/
changeLocalDisplayName(nickname = '') {
const formattedNickname = getNormalizedDisplayName(nickname);
APP.store.dispatch(updateSettings({
displayName: formattedNickname
}));
},
/**
* Callback invoked by the external api create or update a direct connection
* from the local client to an external client.

View File

@@ -59,10 +59,6 @@ var config = {
// https://github.com/jitsi/jitsi-meet/issues/7376
// focusUserJid: 'focus@auth.jitsi-meet.example.com',
// Option to send conference requests to jicofo over http (requires nginx rule for it)
// conferenceRequestUrl:
// 'https://<!--# echo var="http_host" default="jitsi-meet.example.com" -->/' + subdir + 'conference-request/v1',
// Options related to the bridge (colibri) data channel
bridgeChannel: {
// If the backend advertises multiple colibri websockets, this options allows
@@ -88,8 +84,11 @@ var config = {
// issues related to insertable streams.
// disableE2EE: false,
// Enables the use of the codec selection API supported by the browsers .
// enableCodecSelectionAPI: false,
// Enables supports for AV1 codec.
// enableAv1: false,
// Enables XMPP WebSocket (as opposed to BOSH) for the given amount of users.
// mobileXmppWsThreshold: 10, // enable XMPP WebSockets on mobile for 10% of the users
// P2P test mode disables automatic switching to P2P when there are 2
// participants in the conference.
@@ -104,9 +103,6 @@ var config = {
// Experiment: Whether to skip interim transcriptions.
// skipInterimTranscriptions: false,
// Dump transcripts to a <transcript> element for debugging.
// dumpTranscript: false,
},
// Disables moderator indicators.
@@ -121,9 +117,6 @@ var config = {
// Disables polls feature.
// disablePolls: false,
// Disables demote button from self-view
// disableSelfDemote: false,
// Disables self-view tile. (hides it from tile view and from filmstrip)
// disableSelfView: false,
@@ -333,14 +326,9 @@ var config = {
// configuration for all things recording related. Existing settings will be migrated here in the future.
// recordings: {
// // IF true (default) recording audio and video is selected by default in the recording dialog.
// // recordAudioAndVideo: true,
// // If true, shows a notification at the start of the meeting with a call to action button
// // to start recording (for users who can do so).
// // suggestRecording: true,
// // If true, shows a warning label in the prejoin screen to point out the possibility that
// // the call you're joining might be recorded.
// // showPrejoinWarning: true,
// },
// recordingService: {
@@ -432,6 +420,9 @@ var config = {
// // ./src/react/features/transcribing/transcriber-langs.json.
// preferredLanguage: 'en-US',
// // Disable start transcription for all participants.
// disableStartForAll: false,
// // Enables automatic turning on transcribing when recording is started
// autoTranscribeOnRecord: false,
// },
@@ -461,18 +452,12 @@ var config = {
// // Provides a way to set the codec preference on desktop based endpoints.
// codecPreferenceOrder: [ 'VP9', 'VP8', 'H264' ],
//
// // Provides a way to set the codec for screenshare.
// screenshareCodec: 'AV1',
// mobileScreenshareCodec: 'VP8',
//
// // Codec specific settings for scalability modes and max bitrates.
// av1: {
// maxBitratesVideo: {
// low: 100000,
// standard: 300000,
// high: 1000000,
// fullHd: 2000000,
// ultraHd: 4000000,
// ssHigh: 2500000
// },
// scalabilityModeEnabled: true,
@@ -484,8 +469,6 @@ var config = {
// low: 200000,
// standard: 500000,
// high: 1500000,
// fullHd: 3000000,
// ultraHd: 6000000,
// ssHigh: 2500000
// },
// scalabilityModeEnabled: true
@@ -495,8 +478,6 @@ var config = {
// low: 200000,
// standard: 500000,
// high: 1500000,
// fullHd: 3000000,
// ultraHd: 6000000,
// ssHigh: 2500000
// },
// scalabilityModeEnabled: false
@@ -506,14 +487,12 @@ var config = {
// low: 100000,
// standard: 300000,
// high: 1200000,
// fullHd: 2500000,
// ultraHd: 5000000,
// ssHigh: 2500000
// },
// scalabilityModeEnabled: true,
// useSimulcast: false,
// useKSVC: true
// },
// }
//
// DEPRECATED! Use `codec specific settings` instead.
// // Provides a way to configure the maximum bitrates that will be enforced on the simulcast streams for
@@ -668,7 +647,7 @@ var config = {
// // Whether to disable welcome page. In case it's disabled a random room
// // will be joined when no room is specified.
// disabled: false,
// // If set, landing page will redirect to this URL.
// // If set,landing page will redirect to this URL.
// customUrl: ''
// },
@@ -741,7 +720,7 @@ var config = {
// Configs for prejoin page.
// prejoinConfig: {
// // When 'true', it shows an intermediate page before joining, where the user can configure their devices.
// // This replaces `prejoinPageEnabled`. Defaults to true.
// // This replaces `prejoinPageEnabled`.
// enabled: true,
// // Hides the participant name editing field in the prejoin screen.
// // If requireDisplayName is also set as true, a name should still be provided through
@@ -852,22 +831,6 @@ var config = {
// autoHideWhileChatIsOpen: false,
// },
// Overrides the buttons displayed in the main toolbar. Depending on the screen size the number of displayed
// buttons varies from 2 buttons to 8 buttons. Every array in the mainToolbarButtons array will replace the
// corresponding default buttons configuration matched by the number of buttons specified in the array. Arrays with
// more than 8 buttons or less then 2 buttons will be ignored. When there there isn't an override for a cerain
// configuration (for example when 3 buttons are displayed) the default jitsi-meet configuration will be used.
// The order of the buttons in the array is preserved.
// mainToolbarButtons: [
// [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'reactions', 'participants-pane', 'tileview' ],
// [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants-pane', 'tileview' ],
// [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants-pane' ],
// [ 'microphone', 'camera', 'desktop', 'chat', 'participants-pane' ],
// [ 'microphone', 'camera', 'chat', 'participants-pane' ],
// [ 'microphone', 'camera', 'chat' ],
// [ 'microphone', 'camera' ]
// ],
// Toolbar buttons which have their click/tap event exposed through the API on
// `toolbarButtonClicked`. Passing a string for the button key will
// prevent execution of the click/tap routine; passing an object with `key` and
@@ -1054,10 +1017,6 @@ var config = {
// Provides a way to set the codec preference on desktop based endpoints.
// codecPreferenceOrder: [ 'VP9', 'VP8', 'H264 ],
// Provides a way to set the codec for screenshare.
// screenshareCodec: 'AV1',
// mobileScreenshareCodec: 'VP8',
// How long we're going to wait, before going back to P2P after the 3rd
// participant has left the conference (to filter out page reload).
// backToP2PDelay: 5,
@@ -1249,12 +1208,6 @@ var config = {
// desktop: {
// appName: 'Jitsi Meet',
// appScheme: 'jitsi-meet,
// download: {
// linux:
// 'https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet-x86_64.AppImage',
// macos: 'https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet.dmg',
// windows: 'https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet.exe'
// },
// enabled: false
// },
// // If true, any checks to handoff to another application will be prevented
@@ -1338,8 +1291,6 @@ var config = {
// remoteVideoMenu: {
// // Whether the remote video context menu to be rendered or not.
// disabled: true,
// // If set to true the 'Switch to visitor' button will be disabled.
// disableDemote: true,
// // If set to true the 'Kick out' button will be disabled.
// disableKick: true,
// // If set to true the 'Grant moderator' button will be disabled.
@@ -1428,8 +1379,6 @@ var config = {
// Options related to the participants pane.
// participantsPane: {
// // Enables feature
// enabled: true,
// // Hides the moderator settings tab.
// hideModeratorSettingsTab: false,
// // Hides the more actions button.
@@ -1481,6 +1430,7 @@ var config = {
// 'conference-timer',
// 'participants-count',
// 'e2ee',
// 'transcribing',
// 'video-quality',
// 'insecure-room',
// 'highlight-moment',
@@ -1778,7 +1728,7 @@ var config = {
// // to control the performance.
// userLimit: 25,
// // The url for more info about the whiteboard and its usage limitations.
// limitUrl: 'https://example.com/blog/whiteboard-limits',
// limitUrl: 'https://example.com/blog/whiteboard-limits,
// },
// The watchRTC initialize config params as described :
@@ -1814,9 +1764,6 @@ var config = {
// collectionInterval?: number;
// logGetStats?: boolean;
// },
// Hide login button on auth dialog, you may want to enable this if you are using JWT tokens to authenticate users
// hideLoginButton: true,
};
// Temporary backwards compatibility with old mobile clients.

View File

@@ -102,7 +102,7 @@
}
.usermessage {
color: #ffffff;
color: red;
padding: 0;
}
}

View File

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

View File

@@ -41,6 +41,7 @@
position: absolute;
top: 0;
height: 48px;
max-width: calc(100% - 24px);
}
@keyframes hideSubject {

View File

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

View File

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

2
debian/control vendored
View File

@@ -3,7 +3,7 @@ Section: net
Priority: extra
Maintainer: Jitsi Team <dev@jitsi.org>
Uploaders: Emil Ivov <emcho@jitsi.org>, Damian Minkov <damencho@jitsi.org>
Build-Depends: debhelper (>= 8.0.0)
Build-Depends: debhelper (>= 8.0.0), nodejs
Standards-Version: 3.9.6
Homepage: https://jitsi.org/meet

View File

@@ -138,8 +138,10 @@ case "$1" in
PROSODY_CONFIG_PRESENT="false"
fi
# creates the user if it does not exist
echo -e "$JVB_SECRET\n$JVB_SECRET" | prosodyctl adduser jvb@$JICOFO_AUTH_DOMAIN > /dev/null || true
USER_EXISTS_CHECK=`prosodyctl adduser jvb@$JICOFO_AUTH_DOMAIN < /dev/null || true`
if [ ! "$USER_EXISTS_CHECK" = "That user already exists" ]; then
prosodyctl register jvb $JICOFO_AUTH_DOMAIN $JVB_SECRET || true
fi
# Check whether prosody config has the internal muc, if not add it,
# as we are migrating configs

View File

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

View File

@@ -150,15 +150,6 @@ server {
# alias /usr/share/jitsi-meet/load-test/libs/$1;
#}
location ~ ^/conference-request/v1(\/.*)?$ {
proxy_pass http://127.0.0.1:8888/conference-request/v1$1;
add_header "Cache-Control" "no-cache, no-store";
add_header 'Access-Control-Allow-Origin' '*';
}
location ~ ^/([^/?&:'"]+)/conference-request/v1(\/.*)?$ {
rewrite ^/([^/?&:'"]+)/conference-request/v1(\/.*)?$ /conference-request/v1$2;
}
location ~ ^/([^/?&:'"]+)$ {
set $roomname "$1";
try_files $uri @root_path;

1
globals.d.ts vendored
View File

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

3
globals.native.d.ts vendored
View File

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

View File

@@ -10,10 +10,6 @@ workspace 'jitsi-meet'
install! 'cocoapods', :deterministic_uuids => false
def cocoa_utilities
pod 'CocoaLumberjack', '3.7.4'
end
target 'JitsiMeet' do
project 'app/app.xcodeproj'
@@ -49,7 +45,7 @@ target 'JitsiMeetSDK' do
# Native pod dependencies
#
cocoa_utilities
pod 'CocoaLumberjack', '3.7.2'
pod 'ObjectiveDropboxOfficial', '6.2.3'
end
@@ -74,7 +70,7 @@ target 'JitsiMeetSDKLite' do
# Native pod dependencies
#
cocoa_utilities
pod 'CocoaLumberjack', '3.7.2'
end
post_install do |installer|
@@ -83,6 +79,7 @@ post_install do |installer|
use_native_modules![:reactNativePath],
:mac_catalyst_enabled => false
)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
installer.pods_project.targets.each do |target|
# https://github.com/CocoaPods/CocoaPods/issues/11402
if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"

File diff suppressed because it is too large Load Diff

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -151,7 +151,6 @@
4EB0603A260E09D000F524C5 /* DarwinNotificationCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarwinNotificationCenter.swift; sourceTree = "<group>"; };
4EB0603B260E09D000F524C5 /* SampleUploader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleUploader.swift; sourceTree = "<group>"; };
4EC49B8625BED71300E76218 /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; };
6132EF172BDFF13200BBE14D /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ../PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
756FCE06C08D9B947653C98A /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
B3B083EB1D4955FF0069CEE7 /* app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = app.entitlements; sourceTree = "<group>"; };
D6152FF9E9F7B0E86F70A21D /* libPods-JitsiMeet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JitsiMeet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -294,7 +293,6 @@
0BEA5C351F7B8F73000D0AB4 /* WatchKit extension */,
4EB06025260E026600F524C5 /* JitsiMeetBroadcast Extension */,
CDD71F5E1157E9F283DF92A8 /* Pods */,
6132EF172BDFF13200BBE14D /* PrivacyInfo.xcprivacy */,
);
indentWidth = 2;
sourceTree = "<group>";
@@ -369,13 +367,13 @@
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
0B26BE701EC5BC3C00EEFB41 /* Embed Frameworks */,
0BB7DA181EC9E695007AAE98 /* Adjust ATS */,
DEF4813D224925A2002AD03A /* Copy Google Plist file */,
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
DE4F6D6E22005C0400DE699E /* Setup Dropbox */,
4E81688528A408E600F8FA9E /* Update App Entitlements */,
0BEA5C491F7B8F73000D0AB4 /* Embed Watch Content */,
4EC49B9025BED71300E76218 /* Embed App Extensions */,
4E81688528A408E600F8FA9E /* Update App Entitlements */,
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
0BB7DA181EC9E695007AAE98 /* Adjust ATS */,
DE4F6D6E22005C0400DE699E /* Setup Dropbox */,
E9D850368D253EFA8AB3B8D1 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -413,7 +411,7 @@
attributes = {
LastSwiftUpdateCheck = 1240;
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = Jitsi;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
0BEA5C241F7B8F73000D0AB4 = {
CreatedOnToolsVersion = 9.0;
@@ -501,12 +499,10 @@
/* Begin PBXShellScriptBuildPhase section */
0BB7DA181EC9E695007AAE98 /* Adjust ATS */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH",
);
name = "Adjust ATS";
outputPaths = (
@@ -517,7 +513,6 @@
};
0BBA83C41EC9F7600075A103 /* Run React packager */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -532,14 +527,12 @@
};
4E81688528A408E600F8FA9E /* Update App Entitlements */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$PROJECT_DIR/app.entitlements",
);
name = "Update App Entitlements";
outputFileListPaths = (
@@ -574,14 +567,12 @@
};
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH",
);
name = "Setup Google reverse URL handler";
outputFileListPaths = (
@@ -590,18 +581,16 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "GOOGLE_PLIST_NAME=\"GoogleService-Info.plist\"\nGOOGLE_PLIST=\"$PROJECT_DIR/$GOOGLE_PLIST_NAME\"\nBUILD_APP_DIR=\"$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.app\"\nINFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\n\nif [[ -f $GOOGLE_PLIST ]]; then\n cp $GOOGLE_PLIST \"$BUILD_APP_DIR/$GOOGLE_PLIST_NAME\"\n REVERSED_CLIENT_ID=$(/usr/libexec/PlistBuddy -c \"Print :REVERSED_CLIENT_ID:\" $GOOGLE_PLIST)\n /usr/libexec/PlistBuddy -c \"Set :CFBundleURLTypes:1:CFBundleURLSchemes:0 $REVERSED_CLIENT_ID\" $INFO_PLIST\nfi\n";
shellScript = "INFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nGOOGLE_PLIST=\"$PROJECT_DIR/GoogleService-Info.plist\"\n\nif [[ -f $GOOGLE_PLIST ]]; then\n REVERSED_CLIENT_ID=$(/usr/libexec/PlistBuddy -c \"Print :REVERSED_CLIENT_ID:\" $GOOGLE_PLIST)\n /usr/libexec/PlistBuddy -c \"Set :CFBundleURLTypes:1:CFBundleURLSchemes:0 $REVERSED_CLIENT_ID\" $INFO_PLIST\nfi\n";
};
DE4F6D6E22005C0400DE699E /* Setup Dropbox */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH",
);
name = "Setup Dropbox";
outputFileListPaths = (
@@ -612,22 +601,23 @@
shellPath = /bin/sh;
shellScript = "INFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nDROPBOX_KEY_FILE=\"$PROJECT_DIR/dropbox.key\"\n\nif [[ -f $DROPBOX_KEY_FILE ]]; then\n /usr/libexec/PlistBuddy -c \"Delete :LSApplicationQueriesSchemes\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes array\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes:0 string 'dbapi-2'\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes:1 string 'dbapi-8-emm'\" $INFO_PLIST\n\n DROPBOX_KEY=$(head -n 1 $DROPBOX_KEY_FILE)\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLName string dropbox\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLSchemes array\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLSchemes:0 string $DROPBOX_KEY\" $INFO_PLIST\nfi\n";
};
E9D850368D253EFA8AB3B8D1 /* [CP] Copy Pods Resources */ = {
DEF4813D224925A2002AD03A /* Copy Google Plist file */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
inputPaths = (
);
name = "Copy Google Plist file";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources.sh\"\n";
showEnvVarsInLog = 0;
shellScript = "GOOGLE_PLIST_NAME=\"GoogleService-Info.plist\"\nGOOGLE_PLIST=\"$PROJECT_DIR/$GOOGLE_PLIST_NAME\"\nBUILD_APP_DIR=\"$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.app\"\n\nif [[ -f $GOOGLE_PLIST ]]; then\n cp $GOOGLE_PLIST \"$BUILD_APP_DIR/$GOOGLE_PLIST_NAME\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
@@ -986,7 +976,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -1039,11 +1029,14 @@
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
USE_HERMES = false;
};
name = Debug;
};
@@ -1052,7 +1045,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -1100,12 +1093,15 @@
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
TARGETED_DEVICE_FAMILY = "1,2";
USE_HERMES = false;
VALIDATE_PRODUCT = YES;
};
name = Release;

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>24.3.0</string>
<string>99.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>

View File

@@ -35,6 +35,7 @@
jitsiMeet.defaultConferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
[builder setFeatureFlag:@"welcomepage.enabled" withBoolean:YES];
[builder setFeatureFlag:@"resolution" withValue:@(360)];
[builder setFeatureFlag:@"ios.screensharing.enabled" withBoolean:YES];
[builder setFeatureFlag:@"ios.recording.enabled" withBoolean:YES];
}];

View File

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

View File

@@ -88,10 +88,6 @@
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_WILL_JOIN" withData:data];
}
// - (void)customOverflowMenuButtonPressed:(NSDictionary *)data {
// [self _onJitsiMeetViewDelegateEvent:@"CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED" withData:data];
// }
#if 0
- (void)enterPictureInPicture:(NSDictionary *)data {
[self _onJitsiMeetViewDelegateEvent:@"ENTER_PICTURE_IN_PICTURE" withData:data];
@@ -102,10 +98,6 @@
[self _onJitsiMeetViewDelegateEvent:@"READY_TO_CLOSE" withData:data];
}
// - (void)transcriptionChunkReceived:(NSDictionary *)data {
// [self _onJitsiMeetViewDelegateEvent:@"TRANSCRIPTION_CHUNK_RECEIVED" withData:data];
// }
- (void)participantJoined:(NSDictionary *)data {
NSLog(@"%@%@", @"Participant joined: ", data[@"participantId"]);
}
@@ -138,7 +130,6 @@
NSLog(@"%@%@", @"Video muted changed: ", data[@"muted"]);
}
#pragma mark - Helpers
- (void)terminate {

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>compileBitcode</key>
<false/>
<key>method</key>
<string>development</string>
<key>signingStyle</key>
<string>automatic</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>YOUR_TEAM_ID</string>
<key>thinning</key>
<string>&lt;none&gt;</string>
</dict>
</plist>

103
ios/ci/build-ipa.sh Executable file
View File

@@ -0,0 +1,103 @@
#!/bin/bash
set -e
# Mandatory arguments with no default values provided:
# PR_REPO_SLUG - the Github name of the repo to be merged into the origin/master
# PR_BRANCH - the branch to be merged, if set to "master" no merge will happen
# IPA_DEPLOY_LOCATION - the location understandable by the "scp" command
# executed at the end of the script to deploy the output .ipa file
# LIB_JITSI_MEET_PKG (optional) - the npm package for lib-jitsi-meet which will
# be put in place of the current version in the package.json file.
#
# Other than that the script requires the following env variables to be set:
#
# DEPLOY_SSH_CERT_URL - the SSH private key used by the 'scp' command to deploy
# the .ipa. It is expected to be encrypted with the $ENCRYPTION_PASSWORD.
# ENCRYPTION_PASSWORD - the password used to decrypt certificate/key files used
# in the script.
# IOS_TEAM_ID - the team ID inserted into build-ipa-.plist.template file in
# place of "YOUR_TEAM_ID".
function echoAndExit1() {
echo $1
exit 1
}
if [ -z $PR_REPO_SLUG ]; then
echoAndExit1 "No PR_REPO_SLUG defined"
fi
if [ -z $PR_BRANCH ]; then
echoAndExit1 "No PR_BRANCH defined"
fi
if [ -z $IPA_DEPLOY_LOCATION ]; then
echoAndExit1 "No IPA_DEPLOY_LOCATION defined"
fi
echo "PR_REPO_SLUG=${PR_REPO_SLUG} PR_BRANCH=${PR_BRANCH}"
# do the merge and git log
if [ $PR_BRANCH != "master" ]; then
echo "Will merge ${PR_REPO_SLUG}/${PR_BRANCH} into master"
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch origin master
git checkout master
git pull https://github.com/${PR_REPO_SLUG}.git $PR_BRANCH --no-edit
fi
# Link this lib-jitsi-meet checkout in jitsi-meet through the package.json
if [ ! -z ${LIB_JITSI_MEET_PKG} ];
then
echo "Adjusting lib-jitsi-meet package in package.json to ${LIB_JITSI_MEET_PKG}"
# escape for the sed
LIB_JITSI_MEET_PKG=$(echo $LIB_JITSI_MEET_PKG | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')
sed -i.bak -e "s/\"lib-jitsi-meet.*/\"lib-jitsi-meet\"\: \"${LIB_JITSI_MEET_PKG}\",/g" package.json
echo "Package.json lib-jitsi-meet line:"
grep lib-jitsi-meet package.json
else
echo "LIB_JITSI_MEET_PKG var not set - will not modify the package.json"
fi
git log -20 --graph --pretty=format':%C(yellow)%h%Cblue%d%Creset %s %C(white) %an, %ar%Creset'
# certificates
CERT_DIR="ios/ci/certs"
mkdir -p $CERT_DIR
curl -L -o ${CERT_DIR}/id_rsa.enc ${DEPLOY_SSH_CERT_URL}
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/id_rsa.enc -d -a -out ${CERT_DIR}/id_rsa
chmod 0600 ${CERT_DIR}/id_rsa
ssh-add ${CERT_DIR}/id_rsa
npm install
# Ever since the Apple Watch app has been added the bitcode for WebRTC needs to be downloaded in order to build successfully
./node_modules/react-native-webrtc/tools/downloadBitcode.sh
cd ios
pod install --repo-update --no-ansi
cd ..
mkdir -p /tmp/jitsi-meet/
xcodebuild archive -quiet -workspace ios/jitsi-meet.xcworkspace -scheme jitsi-meet -configuration Release -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive
sed -e "s/YOUR_TEAM_ID/${IOS_TEAM_ID}/g" ios/ci/build-ipa.plist.template > ios/ci/build-ipa.plist
IPA_EXPORT_DIR=/tmp/jitsi-meet/jitsi-meet-ipa
xcodebuild -quiet -exportArchive -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive -exportPath $IPA_EXPORT_DIR -exportOptionsPlist ios/ci/build-ipa.plist
echo "Will try deploy the .ipa to: ${IPA_DEPLOY_LOCATION}"
if [ ! -z ${SCP_PROXY_HOST} ];
then
scp -o ProxyCommand="ssh -t -A -l %r ${SCP_PROXY_HOST} -o \"StrictHostKeyChecking no\" -o \"BatchMode yes\" -W %h:%p" -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
else
scp -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
fi
rm -r /tmp/jitsi-meet/
rm -r $CERT_DIR

100
ios/ci/setup-certificates.sh Executable file
View File

@@ -0,0 +1,100 @@
# The script is based on tutorial written by Antonis Tsakiridis published at:
# https://medium.com/@atsakiridis/continuous-deployment-for-ios-using-travis-ci-55dcea342d9
#
# APPLE_CERT_URL - the URL pointing to Apple certificate (set to
# http://developer.apple.com/certificationauthority/AppleWWDRCA.cer by default)
# DEPLOY_SSH_CERT_URL - the SSH private key used by the 'scp' command to deploy
# the .ipa. It is expected to be encrypted with the $ENCRYPTION_PASSWORD.
# ENCRYPTION_PASSWORD - the password used to decrypt certificate/key files used
# in the script.
# IOS_DEV_CERT_KEY_URL - URL pointing to provisioning profile certificate key
# file (development-key.p12.enc from the tutorial) encrypted with the
# $ENCRYPTION_PASSWORD.
# IOS_DEV_CERT_URL - URL pointing to provisioning profile certificate file
# (development-cert.cer.enc from the tutorial) encrypted with the
# $ENCRYPTION_PASSWORD.
# IOS_DEV_PROV_PROFILE_URL - URL pointing to provisioning profile file
# (profile-development-olympus.mobileprovision.enc from the tutorial) encrypted
# IOS_DEV_WATCH_PROV_PROFILE_URL - URL pointing to watch app provisioning profile file(encrypted).
# with the $ENCRYPTION_PASSWORD.
# IOS_SIGNING_CERT_PASSWORD - the password to the provisioning profile
# certificate key (used to open development-key.p12 from the tutorial).
function echoAndExit1() {
echo $1
exit 1
}
CERT_DIR=$1
if [ -z $CERT_DIR ]; then
echoAndExit1 "First argument must be certificates directory"
fi
if [ -z $APPLE_CERT_URL ]; then
APPLE_CERT_URL="http://developer.apple.com/certificationauthority/AppleWWDRCA.cer"
fi
if [ -z $DEPLOY_SSH_CERT_URL ]; then
echoAndExit1 "DEPLOY_SSH_CERT_URL env var is not defined"
fi
if [ -z $ENCRYPTION_PASSWORD ]; then
echoAndExit1 "ENCRYPTION_PASSWORD env var is not defined"
fi
if [ -z $IOS_DEV_CERT_KEY_URL ]; then
echoAndExit1 "IOS_DEV_CERT_KEY_URL env var is not defined"
fi
if [ -z $IOS_DEV_CERT_URL ]; then
echoAndExit1 "IOS_DEV_CERT_URL env var is not defined"
fi
if [ -z $IOS_DEV_PROV_PROFILE_URL ]; then
echoAndExit1 "IOS_DEV_PROV_PROFILE_URL env var is not defined"
fi
if [ -z $IOS_DEV_WATCH_PROV_PROFILE_URL ]; then
echoAndExit1 "IOS_DEV_WATCH_PROV_PROFILE_URL env var is not defined"
fi
if [ -z $IOS_SIGNING_CERT_PASSWORD ]; then
echoAndExit1 "IOS_SIGNING_CERT_PASSWORD env var is not defined"
fi
# certificates
curl -L -o ${CERT_DIR}/AppleWWDRCA.cer 'http://developer.apple.com/certificationauthority/AppleWWDRCA.cer'
curl -L -o ${CERT_DIR}/dev-cert.cer.enc ${IOS_DEV_CERT_URL}
curl -L -o ${CERT_DIR}/dev-key.p12.enc ${IOS_DEV_CERT_KEY_URL}
curl -L -o ${CERT_DIR}/dev-profile.mobileprovision.enc ${IOS_DEV_PROV_PROFILE_URL}
curl -L -o ${CERT_DIR}/dev-watch-profile.mobileprovision.enc ${IOS_DEV_WATCH_PROV_PROFILE_URL}
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-cert.cer.enc -d -a -out ${CERT_DIR}/dev-cert.cer
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-key.p12.enc -d -a -out ${CERT_DIR}/dev-key.p12
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-profile.mobileprovision.enc -d -a -out ${CERT_DIR}/dev-profile.mobileprovision
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-watch-profile.mobileprovision.enc -d -a -out ${CERT_DIR}/dev-watch-profile.mobileprovision
security create-keychain -p $ENCRYPTION_PASSWORD ios-build.keychain
security default-keychain -s ios-build.keychain
security unlock-keychain -p $ENCRYPTION_PASSWORD ios-build.keychain
security set-keychain-settings -t 3600 -l ~/Library/Keychains/ios-build.keychain
echo "importing Apple cert"
security import ${CERT_DIR}/AppleWWDRCA.cer -k ios-build.keychain -A
echo "importing dev-cert.cer"
security import ${CERT_DIR}/dev-cert.cer -k ios-build.keychain -A
echo "importing dev-key.p12"
security import ${CERT_DIR}/dev-key.p12 -k ios-build.keychain -P $IOS_SIGNING_CERT_PASSWORD -A
echo "will set-key-partition-list"
# Fix for OS X Sierra that hangs in the codesign step
security set-key-partition-list -S apple-tool:,apple: -s -k $ENCRYPTION_PASSWORD ios-build.keychain > /dev/null
echo "done set-key-partition-list"
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp "${CERT_DIR}/dev-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/
cp "${CERT_DIR}/dev-watch-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/

View File

@@ -79,8 +79,7 @@ platform :ios do
build_app(
scheme: "JitsiMeet",
include_symbols: true,
export_xcargs: "-allowProvisioningUpdates",
xcodebuild_formatter: ""
export_xcargs: "-allowProvisioningUpdates"
)
# Upload the build to TestFlight

View File

@@ -145,7 +145,6 @@
4ED4FFF12721B9B90074E620 /* JitsiAudioSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiAudioSession.h; sourceTree = "<group>"; };
4ED4FFF22721B9B90074E620 /* JitsiAudioSession.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiAudioSession.m; sourceTree = "<group>"; };
4ED4FFF52721BAE10074E620 /* JitsiAudioSession+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiAudioSession+Private.h"; sourceTree = "<group>"; };
6132EF172BDFF13200BBE14D /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ../PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
86389F55993FAAF6AEB3FA3E /* Pods-JitsiMeetSDKLite.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeetSDKLite.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeetSDKLite/Pods-JitsiMeetSDKLite.release.xcconfig"; sourceTree = "<group>"; };
891FE43DAD30BC8976683100 /* Pods-JitsiMeetSDK.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeetSDK.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeetSDK/Pods-JitsiMeetSDK.release.xcconfig"; sourceTree = "<group>"; };
8F48C340DE0D91D1012976C5 /* Pods-JitsiMeetSDKLite.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeetSDKLite.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeetSDKLite/Pods-JitsiMeetSDKLite.debug.xcconfig"; sourceTree = "<group>"; };
@@ -231,7 +230,6 @@
0BD906E61EC0C00300C8C18E /* Products */,
0BCA49681EC4BBE500B793EE /* Resources */,
0BD906E71EC0C00300C8C18E /* src */,
6132EF172BDFF13200BBE14D /* PrivacyInfo.xcprivacy */,
);
sourceTree = "<group>";
};
@@ -631,7 +629,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "WITH_ENVIRONMENT=\"../../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
shellScript = "export NODE_BINARY=node\nexport NODE_ARGS=\"--max_old_space_size=4096\"\n../../node_modules/react-native/scripts/react-native-xcode.sh\n";
};
DE9A016B289A9A9A00E41CBB /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
@@ -730,7 +728,7 @@
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -781,11 +779,14 @@
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
USE_HERMES = false;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
@@ -798,7 +799,7 @@
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -845,12 +846,15 @@
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
TARGETED_DEVICE_FAMILY = "1,2";
USE_HERMES = false;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>9.3.0</string>
<string>99.0.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -62,7 +62,7 @@
// Initialize the one and only bridge for interfacing with React Native.
_bridgeWrapper = [[RCTBridgeWrapper alloc] init];
// Initialize the listener for handling start/stop screensharing notifications.
_screenshareEventEmiter = [[ScheenshareEventEmiter alloc] init];
@@ -141,7 +141,7 @@
if (_bridgeWrapper != nil) {
return;
};
_bridgeWrapper = [[RCTBridgeWrapper alloc] init];
}
@@ -231,7 +231,7 @@
}
- (void)setDefaultConferenceOptions:(JitsiMeetConferenceOptions *)defaultConferenceOptions {
if (defaultConferenceOptions != nil && defaultConferenceOptions.room != nil) {
if (defaultConferenceOptions != nil && _defaultConferenceOptions.room != nil) {
@throw [NSException exceptionWithName:@"RuntimeError"
reason:@"'room' must be null in the default conference options"
userInfo:nil];

View File

@@ -26,7 +26,7 @@
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
/**
* Joins the conference specified by the given options. The given options will
* Joins the conference specified by the given options. The gievn options will
* be merged with the defaultConferenceOptions (if set) in JitsiMeet. If there
* is an already active conference it will be automatically left prior to
* joining the new one.

View File

@@ -116,18 +116,4 @@
*/
- (void)readyToClose:(NSDictionary *)data;
/**
* Called when the transcription chunk was received.
*
* The `data` dictionary contains a `messageID`, `language`, `participant` key.
*/
- (void)transcriptionChunkReceived:(NSDictionary *)data;
/**
* Called when the custom overflow menu button is pressed.
*
* The `data` dictionary contains a `id`, `text` key.
*/
- (void)customOverflowMenuButtonPressed:(NSDictionary *)data;
@end

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>9.3.0</string>
<string>99.0.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -26,7 +26,6 @@
"hsb": "Hornjoserbšćina",
"hu": "Magyar",
"hy": "Հայերեն",
"id": "Bahasa",
"is": "Íslenska",
"it": "Italiano",
"ja": "日本語",

View File

@@ -432,11 +432,6 @@
},
"passwordDigitsOnly": "",
"passwordSetRemotely": "",
"polls": {
"errors": {
"notUniqueOption": "Opsies moet uniek wees"
}
},
"poweredby": "aangedryf deur",
"presenceStatus": {
"busy": "Besig",

View File

@@ -765,9 +765,6 @@
"removeOption": "إزالة خيار",
"send": "أرسل"
},
"errors": {
"notUniqueOption": "يجب أن تكون الخيارات فريدة"
},
"notification": {
"description": "افتح علامة تبويب الاقتراع للتصويت",
"title": "تمت إضافة اقتراع جديد إلى هذا المُلتقى"

View File

@@ -478,11 +478,6 @@
},
"passwordDigitsOnly": "Да {{number}} лічбаў",
"passwordSetRemotely": "устаноўлены іншым удзельнікам",
"polls": {
"errors": {
"notUniqueOption": "Варыянты павінны быць унікальнымі"
}
},
"poweredby": "працуе на",
"presenceStatus": {
"busy": "Заняты",

View File

@@ -527,11 +527,6 @@
},
"passwordDigitsOnly": "До {{number}} цифри",
"passwordSetRemotely": "зададена от друг участник",
"polls": {
"errors": {
"notUniqueOption": "Опциите трябва да са уникални"
}
},
"poweredby": "с подкрепата на",
"presenceStatus": {
"busy": "Зает",

View File

@@ -772,9 +772,6 @@
"removeOption": "Elimina l'opció",
"send": "Envia"
},
"errors": {
"notUniqueOption": "Les opcions han de ser úniques"
},
"notification": {
"description": "Obre la pestanya de les enquestes per a votar",
"title": "S'ha afegit una nova enquesta en aquesta reunió"

View File

@@ -747,9 +747,6 @@
"removeOption": "",
"send": ""
},
"errors": {
"notUniqueOption": "Možnosti musí být jedinečné"
},
"notification": {
"description": "",
"title": ""

View File

@@ -464,11 +464,6 @@
},
"passwordDigitsOnly": "Op til {{number}} tal",
"passwordSetRemotely": "Sat af et andet medlem",
"polls": {
"errors": {
"notUniqueOption": "Valgmulighederne skal være unikke"
}
},
"poweredby": "Powered by",
"presenceStatus": {
"busy": "Optaget",

View File

@@ -128,7 +128,6 @@
"privateNotice": "Private Nachricht an {{recipient}}",
"sendButton": "Senden",
"smileysPanel": "Emoji-Auswahl",
"systemDisplayName": "System",
"tabs": {
"chat": "Chatten",
"polls": "Umfragen"
@@ -210,7 +209,7 @@
"appNotInstalled": "Sie benötigen die „{{app}}“-App, um der Konferenz auf dem Smartphone beizutreten.",
"description": "Nichts passiert? Wir haben versucht, die Konferenz in {{app}} zu öffnen. Versuchen Sie es erneut oder treten Sie der Konferenz in {{app}} im Web bei.",
"descriptionNew": "Nichts passiert? Wir haben versucht, die Konferenz in {{app}} zu öffnen. <br /><br /> Versuchen Sie es erneut oder treten Sie der Konferenz im Web bei.",
"descriptionWithoutWeb": "Ist nichts passiert? Wir haben versucht, Ihre Konferenz in der „{{app}}“-Desktop-App zu starten.",
"descriptionWithoutWeb": "Ist nichts passiert? Wir haben versucht, Ihre Besprechung in der „{{app}}“-Desktop-App zu starten.",
"downloadApp": "App herunterladen",
"downloadMobileApp": "Aus dem App Store herunterladen",
"ifDoNotHaveApp": "Wenn Sie die App noch nicht haben:",
@@ -220,13 +219,11 @@
"joinInBrowser": "Im Browser",
"launchMeetingLabel": "Wie möchten Sie an der Konferenz teilnehmen?",
"launchWebButton": "Im Web öffnen",
"noDesktopApp": "Sie haben die App noch nicht installiert?",
"noMobileApp": "Sie haben die App noch nicht installiert?",
"or": "oder",
"termsAndConditions": "Indem Sie fortfahren, stimmen Sie unseren <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>Nutzungsbedingungen</a> zu.",
"title": "Die Konferenz wird in {{app}} geöffnet …",
"titleNew": "Konferenz starten ...",
"tryAgainButton": "Erneut versuchen",
"tryAgainButton": "Erneut mit der nativen Applikation versuchen",
"unsupportedBrowser": "Sie verwenden einen Browser, der noch nicht unterstützt wird."
},
"defaultLink": "Bsp.: {{url}}",
@@ -264,7 +261,6 @@
"Share": "Teilen",
"Submit": "OK",
"WaitForHostMsg": "Die Konferenz wurde noch nicht gestartet. Falls Sie die Konferenz leiten, authentifizieren Sie sich bitte. Warten Sie andernfalls, bis die Konferenz gestartet wird.",
"WaitForHostNoAuthMsg": "Die Konferenz wurde noch nicht gestartet. Bitte warten Sie, bis die Konferenz gestartet wird.",
"WaitingForHostButton": "Auf Moderation warten",
"WaitingForHostTitle": "Warten auf den Beginn der Konferenz …",
"Yes": "Ja",
@@ -307,8 +303,6 @@
"contactSupport": "Support kontaktieren",
"copied": "Kopiert",
"copy": "Kopieren",
"demoteParticipantDialog": "Sind Sie sicher, dass Sie diese Person zu den Gästen verschieben möchten?",
"demoteParticipantTitle": "Zu Gästen verschieben",
"dismiss": "OK",
"displayNameRequired": "Hallo! Wie ist Ihr Name?",
"done": "Fertig",
@@ -320,7 +314,6 @@
"embedMeeting": "Besprechung einbetten",
"enterDisplayName": "Bitte geben Sie hier Ihren Namen ein",
"error": "Fehler",
"errorRoomCreationRestriction": "Sie haben versucht, zu schnell beizutreten, bitte versuchen Sie es gleich noch einmal.",
"gracefulShutdown": "Der Dienst steht momentan wegen Wartungsarbeiten nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",
"grantModeratorDialog": "Möchten Sie wirklich Moderationsrechte an diese Person vergeben?",
"grantModeratorTitle": "Moderationsrechte vergeben",
@@ -541,7 +534,7 @@
"conferenceURL": "Link:",
"copyNumber": "Nummer kopieren",
"country": "Land",
"dialANumber": "Um an der Konferenz teilzunehmen, müssen Sie eine dieser Nummern wählen und dann die PIN eingeben.",
"dialANumber": "Um am Meeting teilzunehmen, müssen Sie eine dieser Nummern wählen und dann die PIN eingeben.",
"dialInConferenceID": "PIN:",
"dialInNotSupported": "Entschuldigung, leider wird das Einwählen derzeit nicht unterstützt.",
"dialInNumber": "Einwählen:",
@@ -565,7 +558,6 @@
"noNumbers": "Keine Telefonnummern verfügbar.",
"noPassword": "Kein Passwort benötigt",
"noRoom": "Keine Konferenz für die Einwahlinformationen angegeben.",
"noWhiteboard": "Whiteboard konnte nicht geladen werden.",
"numbers": "Einwahlnummern",
"password": "$t(lockRoomPasswordUppercase):",
"reachedLimit": "Sie haben die Grenzen Ihres Tarifs erreicht.",
@@ -573,8 +565,7 @@
"sipAudioOnly": "SIP-Adresse (nur Ton)",
"title": "Teilen",
"tooltip": "Freigabe-Link und Einwahlinformationen für dieses Meeting",
"upgradeOptions": "Bitte prüfen Sie Ihre Upgrade-Optionen auf",
"whiteboardError": "Whiteboard konnte nicht geladen werden. Bitte versuchen Sie es später erneut."
"upgradeOptions": "Bitte prüfen Sie Ihre Upgrade-Optionen auf"
},
"inlineDialogFailure": {
"msg": "Es ist ein Fehler aufgetreten.",
@@ -738,8 +729,6 @@
"connectedTwoMembers": "{{first}} und {{second}} nehmen am Meeting teil",
"dataChannelClosed": "Schlechte Videoqualität",
"dataChannelClosedDescription": "Die Steuerungsverbindung (Bridge Channel) wurde unterbrochen, daher ist die Videoqulität auf die schlechteste Stufe limitiert.",
"dataChannelClosedDescriptionWithAudio": "Die Steuerungsverbindung (Bridge Channel) wurde unterbrochen, daher können Video- und Tonprobleme auftreten.",
"dataChannelClosedWithAudio": "Ton- und Videoqualität können beeinträchtigt sein",
"disabledIframe": "Die Einbettung ist nur für Demo-Zwecke vorgesehen. Diese Konferenz wird in {{timeout}} Minuten beendet.",
"disabledIframeSecondary": "Die Einbettung von {{domain}} ist nur für Demo-Zwecke vorgesehen. Diese Konferenz wird in {{timeout}} Minuten beendet. Bitte nutzen Sie <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> für produktive Zwecke!",
"disconnected": "getrennt",
@@ -811,16 +800,12 @@
"startSilentTitle": "Sie sind ohne Audioausgabe beigetreten!",
"suboptimalBrowserWarning": "Tut uns leid, aber die Konferenz wird mit {{appName}} kein großartiges Erlebnis. Wir versuchen immer die Situation zu verbessern, bis dahin empfehlen wir aber die Verwendung einer der <a href=\"{{recommendedBrowserPageLink}}\" target=\"_blank\">vollständig unterstützen Browser</a>.",
"suboptimalExperienceTitle": "Browserwarnung",
"suggestRecordingAction": "Starten",
"suggestRecordingDescription": "Möchten Sie eine Aufzeichnung starten?",
"suggestRecordingTitle": "Konferenz aufzeichnen",
"unmute": "Stummschaltung aufheben",
"videoMutedRemotelyDescription": "Sie können sie jederzeit wieder einschalten.",
"videoMutedRemotelyTitle": "Ihre Kamera wurde von {{participantDisplayName}} ausgeschaltet!",
"videoUnmuteBlockedDescription": "Die Kamera und Bildschirmfreigabe kann aus Überlastungsschutzgründen temporär nicht eingeschaltet werden.",
"videoUnmuteBlockedTitle": "Kamera und Bildschirmfreigabe kann nicht aktiviert werden!",
"viewLobby": "Lobby ansehen",
"viewVisitors": "Gäste anzeigen",
"waitingParticipants": "{{waitingParticipants}} Personen",
"whiteboardLimitDescription": "Bitte speichern Sie Ihre Inhalte, da das Nutzungslimit bald erreicht wird und dann Ihr Whiteboard geschlossen wird.",
"whiteboardLimitTitle": "Whiteboard-Nutzung"
@@ -880,9 +865,6 @@
"removeOption": "Antwort entfernen",
"send": "Erstellen"
},
"errors": {
"notUniqueOption": "Optionen müssen einzigartig sein"
},
"notification": {
"description": "Öffnen Sie das Umfragen-Tab um abzustimmen",
"title": "Dieser Konferenz wurde eine Umfrage hinzugefügt"
@@ -950,7 +932,6 @@
"or": "oder",
"premeeting": "Vorschau",
"proceedAnyway": "Trotzdem fortsetzen",
"recordingWarning": "Diese Konferenz wird möglicherweise von anderen Personen aufgezeichnet",
"screenSharingError": "Fehler bei Bildschirmfreigabe:",
"showScreen": "Konferenzvorschau aktivieren",
"startWithPhone": "Mit Telefonaudio starten",
@@ -1017,6 +998,7 @@
"limitNotificationDescriptionNative": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} Min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <3>{{app}}</3>.",
"limitNotificationDescriptionWeb": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} Min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"linkGenerated": "Link zur Aufzeichnung wurde generiert.",
"live": "LIVE",
"localRecordingNoNotificationWarning": "Die Aufzeichnung wird anderen Anwesenden nicht mitgeteilt. Sie müssen diese selbst darauf hinweisen, dass die Konferenz aufgezeichnet wird.",
"localRecordingNoVideo": "Videos werden nicht aufgenommen",
"localRecordingStartWarning": "Bitte beenden Sie die Aufzeichnung vor dem Verlassen der Konferenz, um die Aufzeichnung zu speichern.",
@@ -1033,6 +1015,7 @@
"onBy": "{{name}} startete die Aufnahme",
"onlyRecordSelf": "Nur eigenes Kamerabild und Ton aufzeichnen",
"pending": "Aufzeichnung des Meetings wird vorbereitet…",
"rec": "AUFZ",
"recordAudioAndVideo": "Kamera und Ton aufzeichnen",
"recordTranscription": "Transkription aufzeichnen",
"saveLocalRecording": "Aufzeichnung lokal abspeichern",
@@ -1189,7 +1172,7 @@
"audioOnly": "„Nur Audio“ ein-/ausschalten",
"audioRoute": "Audiogerät auswählen",
"boo": "Buhen",
"breakoutRooms": "Breakout-Räume",
"breakoutRoom": "Breakout-Räume betreten/verlassen",
"callQuality": "Qualitätseinstellungen",
"carmode": "Automodus",
"cc": "Untertitel ein-/ausschalten",
@@ -1372,9 +1355,13 @@
},
"transcribing": {
"ccButtonTooltip": "Untertitel ein-/ausschalten",
"error": "Die Aufzeichnung ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
"expandedLabel": "Transkribieren ist derzeit eingeschaltet",
"failedToStart": "Transkribieren konnte nicht gestartet werden",
"labelToolTip": "Das Meeting wird transkribiert",
"off": "Transkribieren gestoppt",
"on": "Transkribieren gestartet",
"pending": "Transkribieren des Meetings wird vorbereitet…",
"sourceLanguageDesc": "Aktuell ist die Sprache der Konferenz auf <b>{{sourceLanguage}}</b> eingestellt. <br/> Sie könne dies hier ",
"sourceLanguageHere": "ändern",
"start": "Anzeige der Untertitel starten",
@@ -1430,7 +1417,6 @@
},
"videothumbnail": {
"connectionInfo": "Verbindungsinformationen",
"demote": "Zu Gästen verschieben",
"domute": "Stummschalten",
"domuteOthers": "Alle anderen stummschalten",
"domuteVideo": "Kamera ausschalten",
@@ -1485,12 +1471,7 @@
"chatIndicator": "(Gast)",
"labelTooltip": "Anzahl Gäste: {{count}}",
"notification": {
"demoteDescription": "Hierhin verschoben von {{actor}}, bitte melden Sie sich um teilzunehmen",
"description": "Bitte melden Sie sich um teilzunehmen",
"noMainParticipantsDescription": "Eine Person muss die Konferenz starten. Bitte versuchen Sie es gleich noch einmal.",
"noMainParticipantsTitle": "Diese Konferenz wurde noch nicht gestartet.",
"noVisitorLobby": "Sie können nicht teilnehmen, solange die Lobby für diese Konferenz aktiviert ist.",
"notAllowedPromotion": "Eine Person muss Ihre Anfrage erst erlauben.",
"title": "Sie sind Gast in der Konferenz"
}
},
@@ -1550,7 +1531,6 @@
"whiteboard": {
"accessibilityLabel": {
"heading": "Whiteboard"
},
"screenTitle": "Whiteboard"
}
}
}

View File

@@ -775,9 +775,6 @@
"removeOption": "wótegrono wulašowaś",
"send": "wótpósłaś"
},
"errors": {
"notUniqueOption": "Opcije musy byś jedynsće"
},
"notification": {
"description": "Wótcyńśo kórtu wopšašowanjow, aby zgłosowali",
"title": "Za tu konferencu jo nowe wopšašowanje pśigótowane"

View File

@@ -792,9 +792,6 @@
"removeOption": "Αφαιρέστε την επιλογή",
"send": "Αποστολή"
},
"errors": {
"notUniqueOption": "Οι επιλογές πρέπει να είναι μοναδικές"
},
"notification": {
"description": "Ανοίξτε τη σελίδα ψηφοφοριών για να ψηφίσετε",
"title": "Μια νέα ψηφοφορία προστέθηκε στη σύσκεψη"

View File

@@ -864,9 +864,6 @@
"removeOption": "Forigi opcion",
"send": "Sendu"
},
"errors": {
"notUniqueOption": "Ebloj devas esti unikaj"
},
"notification": {
"description": "Malfermu la enketan langeton por voĉdoni",
"title": "Oni aldonis novan enketon en la kunveno"
@@ -925,7 +922,7 @@
"iWantToDialIn": "Mi volas alvoki",
"initiated": "Voko komencita",
"joinAudioByPhone": "Aliĝu kun telefona mikrofono",
"joinMeeting": "Aliĝu al la kunveno",
"joinMeeting": "Aliĝu al la kunvenon",
"joinMeetingInLowBandwidthMode": "Aliĝu en malaltkapacita modo",
"joinWithoutAudio": "Aliĝu sen mikrofono",
"keyboardShortcuts": "Ŝaltu fulmoklavojn",

View File

@@ -815,9 +815,6 @@
"removeOption": "Eliminar la opción",
"send": "Enviar"
},
"errors": {
"notUniqueOption": "Las opciones deben ser únicas"
},
"notification": {
"description": "Abre la pestaña de encuestas para votar",
"title": "Se ha añadido una nueva encuesta a esta reunión"

View File

@@ -691,9 +691,6 @@
"removeOption": "Eliminar la opción",
"send": "Enviar"
},
"errors": {
"notUniqueOption": "Las opciones deben ser únicas"
},
"notification": {
"description": "Abre la pestaña de encuestas para votar",
"title": "Se ha añadido una nueva encuesta a esta reunión"

View File

@@ -467,11 +467,6 @@
},
"passwordDigitsOnly": "Kuni {{number}} tähemärki",
"passwordSetRemotely": "määratud teise kasutaja poolt",
"polls": {
"errors": {
"notUniqueOption": "Valikud peavad olema ainulaadsed"
}
},
"poweredby": "teieni toodud",
"presenceStatus": {
"busy": "Hõivatud",

View File

@@ -588,11 +588,6 @@
},
"passwordDigitsOnly": "{{number}} digitu arte",
"passwordSetRemotely": "beste parte-hartzaile batek ezarrita",
"polls": {
"errors": {
"notUniqueOption": "Aukerak bakarrak izan behar dira"
}
},
"poweredby": "garatzailea:",
"prejoin": {
"audioAndVideoError": "Errorea audio eta bideoan:",

View File

@@ -823,9 +823,6 @@
"removeOption": "حذف گزینه",
"send": "ارسال"
},
"errors": {
"notUniqueOption": "گزینه ها باید منحصر به فرد باشند"
},
"notification": {
"description": "برای رأی‌دادن، زبانهٔ نظرسنجی‌ها را باز کنید",
"title": "نظرسنجی جدیدی به این جلسه اضافه شد"

View File

@@ -438,11 +438,6 @@
},
"passwordDigitsOnly": "",
"passwordSetRemotely": "",
"polls": {
"errors": {
"notUniqueOption": "Vaihtoehtojen on oltava ainutlaatuisia"
}
},
"poweredby": "tukija:",
"presenceStatus": {
"busy": "Varattu",

View File

@@ -74,7 +74,7 @@
"mainRoom": "Salle principale",
"notifications": {
"joined": "Entrée en salle annexe \"{{name}}\"",
"joinedMainRoom": "Retour à la salle principale",
"joinedMainRoom": "Retour à la salle principalem",
"joinedTitle": "Salles annexes"
},
"showParticipantList": "Afficher la liste des participants",
@@ -82,7 +82,7 @@
},
"calendarSync": {
"addMeetingURL": "Ajouter un lien de conférence",
"confirmAddLink": "Voulez-vous ajouter un lien Jitsi à cet événement?",
"confirmAddLink": "Voulez-vous ajouter un lien Jitsi à cet événement ?",
"error": {
"appConfiguration": "L'intégration du calendrier n'est pas correctement configurée.",
"generic": "Une erreur s'est produite. Veuillez vérifier les paramètres de votre calendrier ou tenter de l'actualiser.",
@@ -128,7 +128,6 @@
"privateNotice": "Message privé à {{recipient}}",
"sendButton": "Envoyer",
"smileysPanel": "Panneaux des Émojis",
"systemDisplayName": "Système",
"tabs": {
"chat": "Chat",
"polls": "Sondages"
@@ -139,7 +138,7 @@
},
"chromeExtensionBanner": {
"buttonText": "Installer l'extension Chrome",
"buttonTextEdge": "Installer l'extension Edge",
"buttonTextEdge": "Installer lextension Edge",
"close": "Fermer",
"dontShowAgain": "Ne plus m'afficher ceci",
"installExtensionText": "Installer l'extension pour l'intégration de Google Calendar et Office 365"
@@ -220,9 +219,7 @@
"joinInBrowser": "Rejoindre depuis le navigateur",
"launchMeetingLabel": "Comment voulez-vous rejoindre la réunion ?",
"launchWebButton": "Lancer dans le navigateur",
"noDesktopApp": "Vous n'avez pas l'application ?",
"noMobileApp": "Vous navez pas lapplication ?",
"or": "OU",
"termsAndConditions": "En continuant, vous acceptez nos <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>conditions générales dutilisation.</a>",
"title": "Lancement de votre réunion dans {{app}} en cours ...",
"titleNew": "Lancement de votre réunion ...",
@@ -264,7 +261,6 @@
"Share": "Partager",
"Submit": "Soumettre",
"WaitForHostMsg": "La conférence n'a pas encore commencé. Si vous en êtes l'hôte, veuillez vous authentifier. Sinon, veuillez attendre son arrivée.",
"WaitForHostNoAuthMsg": "La conférence n'a pas encore commencé car aucun modérateur n'est encore arrivé. Veuillez patienter.",
"WaitingForHostButton": "Attendre l'hôte",
"WaitingForHostTitle": "En attente de l'hôte ...",
"Yes": "Oui",
@@ -307,8 +303,6 @@
"contactSupport": "Contacter le support",
"copied": "Copié",
"copy": "Copier",
"demoteParticipantDialog": "Êtes-vous sûr de vouloir déplacer ce participant en visiteur ?",
"demoteParticipantTitle": "Déplacer en visiteur",
"dismiss": "Rejeter",
"displayNameRequired": "Bonjour ! Quel est votre nom ?",
"done": "Terminé",
@@ -320,7 +314,6 @@
"embedMeeting": "Intégrer la réunion",
"enterDisplayName": "Merci de saisir votre nom ici",
"error": "Erreur",
"errorRoomCreationRestriction": "Vous avez essayé de rejoindre trop rapidement, veuillez revenir dans un moment.",
"gracefulShutdown": "Notre service est actuellement en maintenance. Veuillez réessayer plus tard.",
"grantModeratorDialog": "Êtes-vous sûr de vouloir rendre ce participant modérateur ?",
"grantModeratorTitle": "Nommer modérateur",
@@ -431,7 +424,7 @@
"shareAudioAltText": "Pour partager le contenu voulu, naviguer vers \"Onglet du Navigateur\", sélectionner le contenu, activer le bouton \"partager laudio\" et enfin cliquer sur le bouton \"partager\"",
"shareAudioTitle": "Comment partager le son",
"shareAudioWarningD1": "vous devez cesser le partage d'écran avant de partager votre son.",
"shareAudioWarningD2": "vous devez partager votre écran à nouveau et cocher l'option \"Partager l'audio\".",
"shareAudioWarningD2": "viys devez partager votre écran à nouveau et cocher l'ootion \"Partager l'audio\".",
"shareAudioWarningH1": "Si vous voulez partager uniquement de l'audio:",
"shareAudioWarningTitle": "Vous devez cesser de partager l'écran avant de partager l'audio",
"shareMediaWarningGenericH2": "Si vous voulez partager votre écran et l'audio",
@@ -565,7 +558,6 @@
"noNumbers": "Numéros non trouvés",
"noPassword": "Aucun",
"noRoom": "Aucune réunion n'a été spécifiée pour l'appel entrant.",
"noWhiteboard": "Impossible de charger le tableau blanc.",
"numbers": "Numéros d'appel",
"password": "$t(lockRoomPasswordUppercase) :",
"reachedLimit": "Vous avez atteint la limite de votre abonnement.",
@@ -573,8 +565,7 @@
"sipAudioOnly": "Adresse SIP en audio uniquement",
"title": "Partager",
"tooltip": "Partager le lien et les informations de connexion pour cette conférence",
"upgradeOptions": "Veuillez vérifier les options de mise à niveau",
"whiteboardError": "Erreur de chargement du tableau blanc. Veuillez réessayer plus tard."
"upgradeOptions": "Veuillez vérifier les options de mise à niveau"
},
"inlineDialogFailure": {
"msg": "Il y a eu un petit problème.",
@@ -738,8 +729,6 @@
"connectedTwoMembers": "{{first}} et {{second}} ont rejoint la réunion",
"dataChannelClosed": "Qualité vidéo dégradée",
"dataChannelClosedDescription": "Le canal de communication avec le Bridge a été interrompu, la qualité vidéo se trouve limitée à sa valeur la plus faible.",
"dataChannelClosedDescriptionWithAudio": "Le canal de pont est fermé, ce qui peut entraîner des perturbations de l'audio et de la vidéo.",
"dataChannelClosedWithAudio": "La qualité de l'audio et de la vidéo peut être altérée",
"disabledIframe": "L'intégration Iframe est uniquement destinée à des démos, cet appel se terminera dans {{timeout}} minutes.",
"disabledIframeSecondary": "L'intégration Iframe de {{domaine}} est uniquement destinée à des démos, cet appel se terminera dans {{timeout}} minutes.",
"disconnected": "déconnecté",
@@ -811,19 +800,13 @@
"startSilentTitle": "Vous avez rejoint sans sortie audio !",
"suboptimalBrowserWarning": "Nous craignons que votre expérience de réunion en ligne ne soit pas idéale ici. Nous cherchons des moyens d'améliorer cela, mais d'ici-là, essayez d'utiliser l'un des <a href='{{recommendedBrowserPageLink}}' target='_blank'>navigateurs supportés</a>.",
"suboptimalExperienceTitle": "Avertissement du navigateur",
"suggestRecordingAction": "Démarrer",
"suggestRecordingDescription": "Souhaitez-vous démarrer un enregistrement ?",
"suggestRecordingTitle": "Enregistrer cette réunion",
"unmute": "Rétablir le son",
"videoMutedRemotelyDescription": "Vous pouvez toujours la réactiver.",
"videoMutedRemotelyTitle": "Votre caméra a été coupée par {{participantDisplayName}}!",
"videoUnmuteBlockedDescription": "Le rétablissement de la vidéo a été bloqué temporairement en raison de limites système.",
"videoUnmuteBlockedTitle": "Rétablissement de la caméra bloqué !",
"viewLobby": "Voir la salle d'attente",
"viewVisitors": "Voir les visiteurs",
"waitingParticipants": "{{waitingParticipants}} personnes",
"waitingVisitors": "Visiteurs en attente dans la file : {{waitingVisitors}}",
"waitingVisitorsTitle": "La réunion n'est pas encore en direct !",
"whiteboardLimitDescription": "Veuillez sauvegarder votre progression, car la limite dutilisation du tableau blanc sera bientôt atteinte et celui-ci sera fermé.",
"whiteboardLimitTitle": "Utiilisation du tableau blanc"
},
@@ -837,7 +820,6 @@
"audioModeration": "Rouvrir leur micro",
"blockEveryoneMicCamera": "Bloquer tous les micros et caméras",
"breakoutRooms": "Salles annexes",
"goLive": "Passer en direct",
"invite": "Inviter quelqu'un",
"moreModerationActions": "Options de modération supplémentaires",
"moreModerationControls": "Options de modération supplémentaires",
@@ -855,7 +837,6 @@
"headings": {
"lobby": "Salle d'attente ({{count}})",
"participantsList": "Participants de la réunion ({{count}})",
"visitorInQueue": " (en attente {{count}})",
"visitorRequests": "(Demande {{count}} )",
"visitors": "Visiteurs {{count}}",
"waitingLobby": "Dans la salle d'attente ({{count}})"
@@ -869,8 +850,6 @@
"pinnedParticipant": "Participant toujours affiché",
"polls": {
"answer": {
"edit": "Modifier",
"send": "Envoyer",
"skip": "Passer",
"submit": "Envoyer"
},
@@ -884,12 +863,8 @@
"pollQuestion": "Question du sondage",
"questionPlaceholder": "Poser une question",
"removeOption": "Supprimer l'option",
"save": "Enregistrer",
"send": "Envoyer"
},
"errors": {
"notUniqueOption": "Les options doivent être uniques"
},
"notification": {
"description": "Ouvrez l'onglet des sondages pour voter",
"title": "Un nouveau sondage a été ajouté à la réunion"
@@ -957,7 +932,6 @@
"or": "ou",
"premeeting": "Pré-séance",
"proceedAnyway": "Continuer quand même",
"recordingWarning": "D'autres participants peuvent enregistrer cet appel",
"screenSharingError": "Erreur de partage d'écran:",
"showScreen": "Activer l'écran de pré-séance",
"startWithPhone": "Commencez avec l'audio du téléphone",
@@ -1192,7 +1166,7 @@
"toolbar": {
"Settings": "Paramètres",
"accessibilityLabel": {
"Settings": "Ouvrir le menu des paramètres",
"Settings": "Afficher / Masquer le menu des paramètres",
"audioOnly": "Activer / Désactiver le mode voix uniquement",
"audioRoute": "Sélectionner la source audio",
"boo": "Hou",
@@ -1238,7 +1212,7 @@
"moreActions": "Activer / Désactiver le menu d'actions supplémentaires",
"moreActionsMenu": "Menu d'actions supplémentaires",
"moreOptions": "Voir plus d'options",
"mute": "Couper votre micro",
"mute": "Activer / Désactiver l'audio",
"muteEveryone": "Couper le micro de tout le monde",
"muteEveryoneElse": "Couper le micro de tous les autres",
"muteEveryoneElsesVideoStream": "Couper la caméra de tous les autres",
@@ -1274,11 +1248,11 @@
"tileView": "Activer / Désactiver la vue mosaïque",
"toggleCamera": "Changer de caméra",
"toggleFilmstrip": "Afficher ou masquer les vignettes vidéo",
"unmute": "Activer votre micro",
"unmute": "Rétablir le son",
"videoblur": "Activer / désactiver le floutage",
"videomute": "Couper votre vidéo",
"videomute": "Activer / Couper la vidéo",
"videomuteGUMPending": "Connexion de votre caméra",
"videounmute": "Activer votre vidéo"
"videounmute": "Démarrer la vidéo"
},
"addPeople": "Ajouter des personnes à votre appel",
"audioOnlyOff": "Désactiver le mode bande passante réduite",
@@ -1379,9 +1353,13 @@
},
"transcribing": {
"ccButtonTooltip": "Activer / Désactiver les sous-titres",
"error": "Échec de la transcription. Veuillez réessayer.",
"expandedLabel": "La transcription est actuellement activée",
"failedToStart": "Échec de démarrage de la transcription",
"labelToolTip": "La transcription de la réunion est en cours",
"off": "La transcription est désactivée",
"on": "La transcription est activée",
"pending": "Préparation de la transcription de la réunion ...",
"sourceLanguageDesc": "Actuellement, la langue de la réunion est sélectionnée à <b>{{sourceLanguage}}</b>. <br/> Vous pouvez la changer à partir de ",
"sourceLanguageHere": "ici",
"start": "Activer les sous-titres",
@@ -1437,7 +1415,6 @@
},
"videothumbnail": {
"connectionInfo": "Informations de la connexion",
"demote": "Déplacer en visiteur",
"domute": "Couper le micro",
"domuteOthers": "Couper le micro de tous les autres",
"domuteVideo": "Couper la caméra",
@@ -1492,15 +1469,9 @@
"chatIndicator": "(visiteur)",
"labelTooltip": "Nombre de Visiteurs",
"notification": {
"demoteDescription": "Envoyé ici par {{actor}}, levez la main pour participer",
"description": "Pour participer lever la main.",
"noMainParticipantsDescription": "Un participant doit démarrer la réunion. Veuillez réessayer dans un moment.",
"noMainParticipantsTitle": "Cette réunion n'a pas encore commencé.",
"noVisitorLobby": "Vous ne pouvez pas rejoindre tant qu'une salle d'attente est activée pour la réunion.",
"notAllowedPromotion": "Un participant doit d'abord autoriser votre demande.",
"title": "Vous êtes visiteur dans cette réunion"
},
"waitingMessage": "Vous rejoindrez la réunion dès qu'elle sera en direct !"
}
},
"volumeSlider": "Curseur de volume",
"welcomepage": {
@@ -1558,7 +1529,6 @@
"whiteboard": {
"accessibilityLabel": {
"heading": "Tableau blanc"
},
"screenTitle": "Tableau blanc"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -454,11 +454,6 @@
},
"passwordDigitsOnly": "Ata {{number}} díxitos",
"passwordSetRemotely": "estabelecida por outro participante",
"polls": {
"errors": {
"notUniqueOption": "As opcións deben ser únicas"
}
},
"poweredby": "fornecido por",
"presenceStatus": {
"busy": "Ocupado",

View File

@@ -9,7 +9,7 @@
"loading": "מחפש אנשים ומספרי טלפון",
"loadingNumber": "מאמת מספר טלפון",
"loadingPeople": "מחפש אנשים להזמין",
"noResults": "לא נמצאו תוצאות מתאימות",
"noResults": "לא נמצאו תואצות מתאימות",
"noValidNumbers": "אנא הזן מסםר טלפון",
"searchNumbers": "הוסף מספר טלפון",
"searchPeople": "חפש אנשים",
@@ -47,7 +47,7 @@
},
"chat": {
"error": "שגיאה: ההודעה שלך \"{{originalText}}\" לא נשלחה. סיבה: {{error}}",
"fieldPlaceHolder": "הקלד הודעתך כאן",
"fieldPlaceHolder": "הקלד הודעתך כאו",
"messageTo": "הודעה פרטית אל {{recipient}}",
"messagebox": "הקלד הודעה",
"nickname": {
@@ -442,7 +442,7 @@
"me": "אני",
"notify": {
"OldElectronAPPTitle": "פגיעות אבטחה!",
"connectedOneMember": "{{name}} הצטרף למפגש",
"connectedOneMember": "{{name}} הצטרף למפדש",
"connectedThreePlusMembers": "{{name}} ו-{{count}} אחרים הצטרפו למפגש",
"connectedTwoMembers": "{{first}} ו-{{second}} הצטרפו למפגש",
"disconnected": "מנותק",
@@ -477,11 +477,6 @@
},
"passwordDigitsOnly": "עד {{number}} ספרות",
"passwordSetRemotely": "נקבע על ידי חבר אחר",
"polls": {
"errors": {
"notUniqueOption": "האפשרויות חייבות להיות ייחודיות"
}
},
"poweredby": "מופעל על ידי",
"presenceStatus": {
"busy": "עסוק",

View File

@@ -298,7 +298,7 @@
"screenSharingFailed": "उफ़! कुछ गड़बड़ हो गई, हम स्क्रीन शेयरिंग शुरू करने में सक्षम नहीं थे!",
"screenSharingFailedTitle": "Screen sharing failed!",
"screenSharingPermissionDeniedError": "उफ़! आपकी स्क्रीन शेयरिंग अनुमतियों में कुछ गड़बड़ हो गई है। कृपया पुनः लोड करें और पुनः प्रयास करें।",
"sendPrivateMessage": "आपने हाल ही में एक निजी संदेश प्राप्त किया है। क्या आप उसका निजी रूप से जवाब देने का इरादा रखते हैं? या आप अपना संदेश समूह को भेजना चाहते हैं?",
"sendPrivateMessage": "You recently received a private message. Did you intend to reply to that privately, or you want to send your message to the group?",
"sendPrivateMessageCancel": "समूह को भेजें",
"sendPrivateMessageOk": "निजी तौर पर भेजें",
"sendPrivateMessageTitle": "निजी तौर पर भेजें?",
@@ -552,10 +552,10 @@
"somebody": "Somebody",
"startSilentDescription": "ऑडियो सक्षम करने के लिए मीटिंग को फिर से करें",
"startSilentTitle": "आप बिना ऑडियो आउटपुट के साथ शामिल हुए!",
"suboptimalBrowserWarning": "हमें डर है कि आपकी मीटिंग अनुभव यहाँ बहुत अच्छा नहीं होने वाला है। हम इसे सुधारने के तरीके ढूंढ़ रहे हैं, लेकिन उस समय तक कृपया <a href='{{recommendedBrowserPageLink}}' target='_blank'>पूरी तरह से समर्थित ब्राउज़र</a> में से एक का प्रयास करें",
"suboptimalBrowserWarning": "We are afraid your meeting experience isn't going to be that great here. We are looking for ways to improve this, but until then please try using one of the <a href='{{recommendedBrowserPageLink}}' target='_blank'>fully supported browsers</a>.",
"suboptimalExperienceTitle": "ब्राउज़र चेतावनी",
"unmute": "अनम्यूट",
"videoMutedRemotelyDescription": "आप इसे हमेशा फिर से चालू कर सकते हैं।",
"videoMutedRemotelyDescription": "You can always turn it on again.",
"videoMutedRemotelyTitle": "आपका कैमरा {{participantDisplayName}}द्वारा अक्षम कर दिया गया है!"
},
"participantsPane": {
@@ -565,11 +565,6 @@
},
"passwordDigitsOnly": "Up to {{number}} digits",
"passwordSetRemotely": "दूसरे प्रतिभागी द्वारा निर्धारित",
"polls": {
"errors": {
"notUniqueOption": "विकल्प अद्वितीय होना चाहिए"
}
},
"poweredby": "powered by",
"prejoin": {
"audioAndVideoError": "ऑडियो और वीडियो त्रुटि:",
@@ -654,12 +649,12 @@
"availableSpace": "Available space: {{spaceLeft}} MB (approximately {{duration}} minutes of recording)",
"beta": "BETA",
"busy": "We're working on freeing recording resources. Please try again in a few minutes.",
"busyTitle": "सभी रिकॉर्डर अभी व्यस्त हैं",
"error": "रिकॉर्डिंग विफल हुई। कृपया पुन: प्रयास करें।",
"expandedOff": "रिकॉर्डिंग बंद हो गई है",
"busyTitle": "All recorders are currently busy",
"error": "Recording failed. Please try again.",
"expandedOff": "Recording has stopped",
"expandedOn": "The meeting is currently being recorded.",
"expandedPending": "रिकॉर्डिंग शुरू की जा रही है...",
"failedToStart": "रिकॉर्डिंग शुरू करने में विफलता हुई।",
"expandedPending": "Recording is being started...",
"failedToStart": "Recording failed to start",
"fileSharingdescription": "Share recording with meeting participants",
"limitNotificationDescriptionNative": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <3>{{app}}</3>.",
"limitNotificationDescriptionWeb": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
@@ -677,16 +672,16 @@
"signIn": "Sign in",
"signOut": "Sign out",
"title": "रिकॉर्डिंग",
"unavailable": "ओह! {{serviceName}} वर्तमान में अनुपलब्ध है। हम समस्या को हल करने पर काम कर रहे हैं। कृपया बाद में पुनः प्रयास करें।",
"unavailableTitle": "रिकॉर्डिंग उपलब्ध नहीं है"
"unavailable": "Oops! The {{serviceName}} is currently unavailable. We're working on resolving the issue. Please try again later.",
"unavailableTitle": "Recording unavailable"
},
"sectionList": {
"pullToRefresh": "Pull to refresh"
},
"security": {
"about": "आप अपनी मीटिंग में $t(lockRoomPassword) जोड़ सकते हैं। सहभागियों को मीटिंग में शामिल होने से पहले $t(lockRoomPassword) प्रदान करना होगा।",
"about": "You can add a $t(lockRoomPassword) to your meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.",
"aboutReadOnly": "Moderator participants can add a $t(lockRoomPassword) to the meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.",
"insecureRoomNameWarning": "कमरे का नाम असुरक्षित है। अनचाहे सहभागियों की कॉन्फ्रेंस में शामिल हो सकते हैं। सुरक्षा बटन का उपयोग करके अपनी मीटिंग को सुरक्षित बनाने का विचार करें। ",
"insecureRoomNameWarning": "The room name is unsafe. Unwanted participants may join your conference. Consider securing your meeting using the security button.",
"securityOptions": "Security options"
},
"settings": {
@@ -886,26 +881,26 @@
"tr": "TR"
},
"userMedia": {
"androidGrantPermissions": "जब आपका ब्राउज़र स्वीकृति मांगता है, तो <b><i>अनुमति दें</i></b> चुनें।",
"chromeGrantPermissions": "जब आपका ब्राउज़र स्वीकृति मांगता है, तो <b><i>अनुमति दें</i></b> चुनें।",
"edgeGrantPermissions": "जब आपका ब्राउज़र स्वीकृति मांगता है, तो <b><i>हां</i></b> चुनें।",
"electronGrantPermissions": "आपका कैमरा और माइक्रोफोन तक पहुंच करने की कोशिश की जा रही है",
"firefoxGrantPermissions": "जब आपका ब्राउज़र स्वीकृति मांगता है, तो <b><i>चयनित उपकरण साझा करें</i></b> चुनें।",
"iexplorerGrantPermissions": "जब आपका ब्राउज़र स्वीकृति मांगता है, तो <b><i>ठीक है</i></b> चुनें।",
"nwjsGrantPermissions": "कृपया अपने कैमरा और माइक्रोफोन का उपयोग करने के लिए अनुमतियाँ प्रदान करें",
"operaGrantPermissions": "जब आपका ब्राउज़र स्वीकृति मांगता है, तो <b><i>अनुमति दें</i></b> चुनें।",
"react-nativeGrantPermissions": "जब आपका ब्राउज़र स्वीकृति मांगता है, तो <b><i>अनुमति दें</i></b> चुनें।",
"safariGrantPermissions": "जब आपका ब्राउज़र स्वीकृति मांगता है, तो <b><i>ठीक है</i></b> चुनें।"
"androidGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"chromeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"edgeGrantPermissions": "Select <b><i>Yes</i></b> when your browser asks for permissions.",
"electronGrantPermissions": "Trying to access your camera and microphone",
"firefoxGrantPermissions": "Select <b><i>Share Selected Device</i></b> when your browser asks for permissions.",
"iexplorerGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions.",
"nwjsGrantPermissions": "Please grant permissions to use your camera and microphone",
"operaGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"react-nativeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"safariGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions."
},
"videoSIPGW": {
"busy": "हम संसाधनों को मुक्त करने पर काम कर रहे हैं। कृपया कुछ मिनटों बाद पुन: प्रयास करें।",
"busyTitle": "रूम सेवा वर्तमान में व्यस्त है",
"errorAlreadyInvited": "{{displayName}} पहले से ही आमंत्रित हैं",
"errorInvite": "कॉन्फ़्रेंस अब तक स्थापित नहीं हुई है। कृपया बाद में पुनः प्रयास करें।",
"errorInviteFailed": "हम समस्या को हल करने पर काम कर रहे हैं। कृपया बाद में पुनः प्रयास करें।",
"errorInviteFailedTitle": "{{displayName}} को आमंत्रित करने में विफलता",
"errorInviteTitle": "रूम आमंत्रण में त्रुटि",
"pending": "{{displayName}} को आमंत्रित किया गया है"
"busy": "We're working on freeing resources. Please try again in a few minutes.",
"busyTitle": "The Room service is currently busy",
"errorAlreadyInvited": "{{displayName}} already invited",
"errorInvite": "Conference not established yet. Please try again later.",
"errorInviteFailed": "We're working on resolving the issue. Please try again later.",
"errorInviteFailedTitle": "Inviting {{displayName}} failed",
"errorInviteTitle": "Error inviting room",
"pending": "{{displayName}} has been invited"
},
"videoStatus": {
"audioOnly": "AUD",
@@ -928,10 +923,10 @@
"domute": "म्यूट",
"domuteOthers": "सभी को म्यूट करें",
"domuteVideo": "कैमरा अक्षम करें",
"domuteVideoOfOthers": "अन्य सभी के लिए कैमरा बंद करें",
"domuteVideoOfOthers": "Disable camera of everyone else",
"flip": "Flip",
"grantModerator": "Grant Moderator",
"kick": "निकालें",
"kick": "Kick out",
"moderator": "Moderator",
"mute": "प्रतिभागी मौन है",
"muted": "म्यूटेड",
@@ -950,8 +945,8 @@
},
"welcomepage": {
"accessibilityLabel": {
"join": "शामिल होने के लिए टैप करें",
"roomname": "कमरे का नाम लिखे"
"join": "Tap to join",
"roomname": "Enter room name"
},
"appDescription": "आगे बढ़ो, पूरी टीम के साथ वीडियो चैट करें। वास्तव में, हर किसी को जिसे आप जानते हैं, आमंत्रित करें। { {{app}} एक पूरी तरह से एन्क्रिप्टेड, 100% ओपन सोर्स वीडियो कॉन्फ्रेंसिंग समाधान है जिसका आप मुफ्त में - बिना किसी खाते की आवश्यकता के पूरे दिन, हर दिन, उपयोग कर सकते हैं।",
"audioVideoSwitch": {

View File

@@ -770,9 +770,6 @@
"removeOption": "Ukloni opciju",
"send": "Pošalji"
},
"errors": {
"notUniqueOption": "Opcije moraju biti jedinstvene"
},
"notification": {
"description": "Za glasanje otvori karticu ankete",
"title": "Ovom sastanku je dodana nova anketa"

View File

@@ -755,9 +755,6 @@
"removeOption": "wotmołwu wotstronić",
"send": "zestajić"
},
"errors": {
"notUniqueOption": "opcije dyrbja jasne być"
},
"notification": {
"description": "Za wobdźělenje wočińće tab za naprašowanje.",
"title": "Tutej konferency bu naprašowanje přidate."

View File

@@ -184,7 +184,6 @@
"Share": "Megosztás",
"Submit": "Elküldés",
"WaitForHostMsg": "A konferencia még nem kezdődött meg. Ha Ön a házigazda, akkor hitelesítse magát. Ellenkező esetben, kérjük várjon a házigazda érkezésére.",
"WaitForHostNoAuthMsg": "A konferencia még nem kezdődött el, mert nincs elérhető moderátor. Kérlek várj.",
"WaitingForHost": "Várakozás a házigazdára…",
"Yes": "Igen",
"accessibilityLabel": {
@@ -624,9 +623,6 @@
"removeOption": "Opció eltávolítása",
"send": "Küldés"
},
"errors": {
"notUniqueOption": "Az opcióknak egyedinek kell lenniük"
},
"notification": {
"description": "Szavazás megnyitása",
"title": "Új szavazás létrehozva"

View File

@@ -427,11 +427,6 @@
},
"passwordDigitsOnly": "",
"passwordSetRemotely": "Սահմանվել է մեկ այլ մասնակցի կողմից",
"polls": {
"errors": {
"notUniqueOption": "Ընտրանքները պետք է լինեն եզակի"
}
},
"poweredby": "Հիմնված է",
"presenceStatus": {
"busy": "",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -770,9 +770,6 @@
"removeOption": "Elimina risposta",
"send": "Invia"
},
"errors": {
"notUniqueOption": "Le opzioni devono essere uniche"
},
"notification": {
"description": "Apri la scheda sondaggi per votare",
"title": "Un nuovo sondaggio è stato aggiunto alla riunione"

View File

@@ -715,9 +715,6 @@
"removeOption": "選択肢の削除",
"send": "送信"
},
"errors": {
"notUniqueOption": "オプションは一意でなければなりません"
},
"notification": {
"description": "投票するには投票タブを開いてください",
"title": "新しい投票がこのミーティングに追加されました"

View File

@@ -667,9 +667,6 @@
"removeOption": "Kkes aɣewwaṛ",
"send": "Azen"
},
"errors": {
"notUniqueOption": "tifranin ilaq ad ilin d imaynuten"
},
"notification": {
"description": "Ldi iccer n yisenqad i ufran",
"title": "Asenqed amaynut yettwarna ɣer temlilt-a"

View File

@@ -497,11 +497,6 @@
},
"passwordDigitsOnly": "최대 {{number}} 자리",
"passwordSetRemotely": "다른 참가자가 설정",
"polls": {
"errors": {
"notUniqueOption": "옵션은 고유해야합니다"
}
},
"poweredby": "powered by",
"presenceStatus": {
"busy": "바쁨",

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