mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-04 22:02:28 +00:00
Compare commits
89 Commits
android-sd
...
8024
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0368b4d671 | ||
|
|
acccd760d1 | ||
|
|
7696dbc347 | ||
|
|
e25ceebeec | ||
|
|
c7ee6280d2 | ||
|
|
796a2217aa | ||
|
|
e9bf1ada51 | ||
|
|
667b9fba67 | ||
|
|
bbf55e8476 | ||
|
|
11b48a9690 | ||
|
|
3b4554f4cf | ||
|
|
d5dd5e4560 | ||
|
|
46ea1f577c | ||
|
|
be06547cc4 | ||
|
|
bd631f5968 | ||
|
|
bc69a35cd6 | ||
|
|
75e7f64311 | ||
|
|
cdcb4cdbfb | ||
|
|
c31ef2ebc7 | ||
|
|
d49a419284 | ||
|
|
0913554af9 | ||
|
|
9af0003c63 | ||
|
|
3ecc16dc87 | ||
|
|
ef387c939a | ||
|
|
76e3608baf | ||
|
|
68384121cd | ||
|
|
2ed0418bd9 | ||
|
|
5eb4064390 | ||
|
|
3f943feb4a | ||
|
|
861b32367e | ||
|
|
cfca0aea4a | ||
|
|
43ae5915a6 | ||
|
|
7c22bcf827 | ||
|
|
1bb9b261dc | ||
|
|
a95adcdf41 | ||
|
|
6758d13335 | ||
|
|
10ab4df287 | ||
|
|
f3e4a7a9c8 | ||
|
|
854503aec2 | ||
|
|
353c3cdd34 | ||
|
|
8f7ab33508 | ||
|
|
383b534753 | ||
|
|
6691f56d0e | ||
|
|
50d4b6250c | ||
|
|
daa840564c | ||
|
|
2cdf77272c | ||
|
|
53299a19c2 | ||
|
|
e90b270b32 | ||
|
|
24ce8c5831 | ||
|
|
57bd074d00 | ||
|
|
843cdf05f5 | ||
|
|
ede8f7ece9 | ||
|
|
e5b736243d | ||
|
|
1d8a9c11c8 | ||
|
|
72d05bb969 | ||
|
|
2a5fc8cc4a | ||
|
|
aa9bd8c814 | ||
|
|
f279e634e4 | ||
|
|
3f7c8c204b | ||
|
|
375314cbbd | ||
|
|
62d0d25395 | ||
|
|
3047af4df6 | ||
|
|
6682b52a19 | ||
|
|
fee4151f83 | ||
|
|
25f2eb012e | ||
|
|
6976b45789 | ||
|
|
c69962675b | ||
|
|
2bb1d6dee3 | ||
|
|
43c1032b46 | ||
|
|
980153e299 | ||
|
|
d2b4043c7f | ||
|
|
ce727d87a8 | ||
|
|
b540452583 | ||
|
|
f995eb2698 | ||
|
|
393c78aad3 | ||
|
|
6452c998a2 | ||
|
|
038292305e | ||
|
|
aa04692e9b | ||
|
|
219e6ce1ca | ||
|
|
6b2d586aee | ||
|
|
28a9850111 | ||
|
|
d9ef9dc6a0 | ||
|
|
88b6cdf39b | ||
|
|
e3ab6c9f33 | ||
|
|
9bb27b83d9 | ||
|
|
d8b0710a19 | ||
|
|
b02b7ac769 | ||
|
|
b90e187a73 | ||
|
|
582bbf890b |
@@ -3,7 +3,7 @@
|
||||
"image": "mcr.microsoft.com/devcontainers/universal:2",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
"version": "20"
|
||||
}
|
||||
},
|
||||
"hostRequirements": {
|
||||
|
||||
48
.github/ISSUE_TEMPLATE/1-bug-report.md
vendored
48
.github/ISSUE_TEMPLATE/1-bug-report.md
vendored
@@ -1,48 +0,0 @@
|
||||
---
|
||||
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. -->
|
||||
55
.github/ISSUE_TEMPLATE/1-bug.yml
vendored
Normal file
55
.github/ISSUE_TEMPLATE/1-bug.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
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).
|
||||
25
.github/ISSUE_TEMPLATE/2-feature-request.md
vendored
25
.github/ISSUE_TEMPLATE/2-feature-request.md
vendored
@@ -1,25 +0,0 @@
|
||||
---
|
||||
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.
|
||||
|
||||
22
.github/ISSUE_TEMPLATE/2-feature.yml
vendored
Normal file
22
.github/ISSUE_TEMPLATE/2-feature.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
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.
|
||||
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Need help with Jitsi Meet?
|
||||
- name: Need help with your Jitsi Meet installation?
|
||||
url: https://community.jitsi.org
|
||||
about: Please ask it in our community.
|
||||
about: Please ask it in our community forum.
|
||||
|
||||
2
.github/workflows/ci-lua.yml
vendored
2
.github/workflows/ci-lua.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
name: Luacheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install luarocks
|
||||
run: sudo apt-get --install-recommends -y install luarocks
|
||||
|
||||
68
.github/workflows/ci.yml
vendored
68
.github/workflows/ci.yml
vendored
@@ -7,11 +7,15 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version-file: '.nvmrc'
|
||||
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
|
||||
@@ -34,21 +38,25 @@ jobs:
|
||||
name: Build Frontend (Linux)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version-file: '.nvmrc'
|
||||
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@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: make
|
||||
@@ -56,23 +64,41 @@ jobs:
|
||||
name: Build mobile bundle (Android)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version-file: '.nvmrc'
|
||||
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: macOS-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-13, macos-14]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version-file: '.nvmrc'
|
||||
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:
|
||||
@@ -89,11 +115,15 @@ jobs:
|
||||
name: Test Debian packages build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
- name: Check Node / npm versions
|
||||
run: |
|
||||
node -v
|
||||
npm -v
|
||||
- run: npm install
|
||||
- run: make
|
||||
- run: sudo apt-get install -y debhelper
|
||||
|
||||
@@ -37,7 +37,6 @@ import org.jitsi.meet.sdk.JitsiMeetConferenceOptions;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* The one and only Activity that the Jitsi Meet app needs. The
|
||||
@@ -74,7 +73,6 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
*/
|
||||
private String defaultURL;
|
||||
|
||||
|
||||
// JitsiMeetActivity overrides
|
||||
//
|
||||
|
||||
@@ -146,12 +144,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);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx1024m -XX:MaxPermSize=256m
|
||||
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.jvmargs=-Xmx2048m -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
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
<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-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
@@ -49,7 +51,7 @@
|
||||
|
||||
<service
|
||||
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
|
||||
android:foregroundServiceType="mediaPlayback" />
|
||||
android:foregroundServiceType="mediaPlayback|microphone" />
|
||||
|
||||
<provider
|
||||
android:name="com.reactnativecommunity.webview.RNCWebViewFileProvider"
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
/**
|
||||
@@ -229,6 +230,12 @@ 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.
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import static android.Manifest.permission.POST_NOTIFICATIONS;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
@@ -24,6 +26,7 @@ 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;
|
||||
@@ -31,6 +34,8 @@ 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.HashMap;
|
||||
@@ -52,12 +57,13 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
|
||||
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver();
|
||||
|
||||
private static final int POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE);
|
||||
|
||||
private boolean isAudioMuted;
|
||||
|
||||
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
|
||||
|
||||
|
||||
public static void launch(Context context, HashMap<String, Object> extraData) {
|
||||
private static void doLaunch(Context context, HashMap<String, Object> extraData) {
|
||||
|
||||
OngoingNotification.createNotificationChannel((Activity) context);
|
||||
|
||||
@@ -87,6 +93,31 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void launch(Context context, HashMap<String, Object> extraData) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
PermissionListener listener = new PermissionListener() {
|
||||
@Override
|
||||
public boolean onRequestPermissionsResult(int i, String[] strings, int[] results) {
|
||||
if (results.length > 0 && results[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
doLaunch(context, extraData);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
JitsiMeetActivityDelegate.requestPermissions(
|
||||
(Activity) context,
|
||||
new String[]{POST_NOTIFICATIONS},
|
||||
POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE,
|
||||
listener
|
||||
);
|
||||
} else {
|
||||
doLaunch(context, extraData);
|
||||
}
|
||||
}
|
||||
|
||||
public static void abort(Context context) {
|
||||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
context.stopService(intent);
|
||||
@@ -102,7 +133,7 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
459
conference.js
459
conference.js
@@ -2,10 +2,8 @@
|
||||
|
||||
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';
|
||||
@@ -18,7 +16,6 @@ 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';
|
||||
@@ -56,7 +53,7 @@ import {
|
||||
getConferenceOptions,
|
||||
sendLocalParticipant
|
||||
} from './react/features/base/conference/functions';
|
||||
import { getReplaceParticipant } from './react/features/base/config/functions';
|
||||
import { getReplaceParticipant, getSsrcRewritingFeatureFlag } from './react/features/base/config/functions';
|
||||
import { connect } from './react/features/base/connection/actions.web';
|
||||
import {
|
||||
checkAndNotifyForNewDevice,
|
||||
@@ -78,7 +75,6 @@ import {
|
||||
JitsiConferenceEvents,
|
||||
JitsiE2ePingEvents,
|
||||
JitsiMediaDevicesEvents,
|
||||
JitsiTrackErrors,
|
||||
JitsiTrackEvents,
|
||||
browser
|
||||
} from './react/features/base/lib-jitsi-meet';
|
||||
@@ -119,8 +115,11 @@ import {
|
||||
import { updateSettings } from './react/features/base/settings/actions';
|
||||
import {
|
||||
addLocalTrack,
|
||||
createInitialAVTracks,
|
||||
destroyLocalTracks,
|
||||
displayErrorsForCreateInitialLocalTracks,
|
||||
replaceLocalTrack,
|
||||
setGUMPendingStateOnFailedTracks,
|
||||
toggleScreensharing as toggleScreensharingA,
|
||||
trackAdded,
|
||||
trackRemoved
|
||||
@@ -165,12 +164,8 @@ import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/Au
|
||||
import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
|
||||
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
|
||||
import { muteLocal } from './react/features/video-menu/actions.any';
|
||||
import UIEvents from './service/UI/UIEvents';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
let room;
|
||||
|
||||
/*
|
||||
@@ -280,12 +275,6 @@ 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;
|
||||
|
||||
@@ -395,27 +384,6 @@ 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 => {
|
||||
if (track.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
||||
return MEDIA_TYPE.SCREENSHARE;
|
||||
}
|
||||
|
||||
return 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
|
||||
@@ -514,57 +482,12 @@ export default {
|
||||
return [];
|
||||
});
|
||||
} else if (requestedAudio || requestedVideo) {
|
||||
APP.store.dispatch(gumPending(initialDevices, IGUMPendingState.PENDING_UNMUTE));
|
||||
tryCreateLocalTracks = createLocalTracksF({
|
||||
tryCreateLocalTracks = APP.store.dispatch(createInitialAVTracks({
|
||||
devices: initialDevices,
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true
|
||||
})
|
||||
.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;
|
||||
}
|
||||
})).then(({ tracks, errors: pErrors }) => {
|
||||
Object.assign(errors, pErrors);
|
||||
|
||||
return tracks;
|
||||
});
|
||||
@@ -585,42 +508,6 @@ 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())
|
||||
@@ -736,11 +623,11 @@ export default {
|
||||
|
||||
logger.debug('Prejoin screen no longer displayed at the time when tracks were created');
|
||||
|
||||
this._displayErrorsForCreateInitialLocalTracks(errors);
|
||||
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
|
||||
|
||||
const tracks = handleInitialTracks(initialOptions, localTracks);
|
||||
|
||||
setGUMPendingStateOnFailedTracks(tracks);
|
||||
setGUMPendingStateOnFailedTracks(tracks, APP.store.dispatch);
|
||||
|
||||
return this._setLocalAudioVideoStreams(tracks);
|
||||
}
|
||||
@@ -749,7 +636,7 @@ export default {
|
||||
|
||||
return Promise.all([
|
||||
tryCreateLocalTracks.then(tr => {
|
||||
this._displayErrorsForCreateInitialLocalTracks(errors);
|
||||
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
|
||||
|
||||
return tr;
|
||||
}).then(tr => {
|
||||
@@ -757,7 +644,7 @@ export default {
|
||||
|
||||
const filteredTracks = handleInitialTracks(initialOptions, tr);
|
||||
|
||||
setGUMPendingStateOnFailedTracks(filteredTracks);
|
||||
setGUMPendingStateOnFailedTracks(filteredTracks, APP.store.dispatch);
|
||||
|
||||
return filteredTracks;
|
||||
}),
|
||||
@@ -1238,7 +1125,7 @@ export default {
|
||||
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(options);
|
||||
const localTracks = await tryCreateLocalTracks;
|
||||
|
||||
this._displayErrorsForCreateInitialLocalTracks(errors);
|
||||
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
|
||||
localTracks.forEach(track => {
|
||||
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|
||||
|| (track.isVideoTrack() && this.isLocalVideoMuted())) {
|
||||
@@ -1578,50 +1465,6 @@ 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.
|
||||
*/
|
||||
@@ -1794,7 +1637,11 @@ export default {
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.CONFERENCE_CREATED_TIMESTAMP,
|
||||
conferenceTimestamp => APP.store.dispatch(conferenceTimestampChanged(conferenceTimestamp)));
|
||||
conferenceTimestamp => {
|
||||
APP.store.dispatch(conferenceTimestampChanged(conferenceTimestamp));
|
||||
APP.API.notifyConferenceCreatedTimestamp(conferenceTimestamp);
|
||||
}
|
||||
);
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.DISPLAY_NAME_CHANGED,
|
||||
@@ -1916,21 +1763,12 @@ 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,
|
||||
@@ -1950,9 +1788,6 @@ export default {
|
||||
}));
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.NICKNAME_CHANGED,
|
||||
this.changeLocalDisplayName.bind(this));
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
|
||||
({ audio, video }) => {
|
||||
@@ -1999,123 +1834,146 @@ export default {
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.DATA_CHANNEL_CLOSED, ev => {
|
||||
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));
|
||||
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);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// call hangup
|
||||
APP.UI.addListener(UIEvents.HANGUP, () => {
|
||||
this.hangup(true);
|
||||
/**
|
||||
* 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));
|
||||
});
|
||||
},
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.VIDEO_DEVICE_CHANGED,
|
||||
cameraDeviceId => {
|
||||
const videoWasMuted = this.isLocalVideoMuted();
|
||||
const localVideoTrack = getLocalJitsiVideoTrack(APP.store.getState());
|
||||
/**
|
||||
* 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());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
});
|
||||
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);
|
||||
}
|
||||
);
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.AUDIO_DEVICE_CHANGED,
|
||||
async micDeviceId => {
|
||||
const audioWasMuted = this.isLocalAudioMuted();
|
||||
return stream;
|
||||
})
|
||||
.then(stream => {
|
||||
logger.info(`Switching the local video device to ${cameraDeviceId}.`);
|
||||
|
||||
// Disable noise suppression if it was enabled on the previous track.
|
||||
await APP.store.dispatch(setNoiseSuppressionEnabled(false));
|
||||
return this.useVideoStream(stream);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(`Failed to switch to selected camera:${cameraDeviceId}, error:${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);
|
||||
}
|
||||
return APP.store.dispatch(notifyCameraError(error));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -2408,8 +2266,6 @@ export default {
|
||||
this.deviceChangeListener);
|
||||
}
|
||||
|
||||
APP.UI.removeAllListeners();
|
||||
|
||||
let feedbackResultPromise = Promise.resolve({});
|
||||
|
||||
if (requestFeedback) {
|
||||
@@ -2516,37 +2372,6 @@ 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.
|
||||
|
||||
30
config.js
30
config.js
@@ -91,9 +91,6 @@ var config = {
|
||||
// Enables supports for AV1 codec.
|
||||
// enableAv1Support: false,
|
||||
|
||||
// Enables XMPP WebSocket (as opposed to BOSH) for the given amount of users.
|
||||
// mobileXmppWsThreshold: 10, // enable XMPP WebSockets on mobile for 10% of the users
|
||||
|
||||
// P2P test mode disables automatic switching to P2P when there are 2
|
||||
// participants in the conference.
|
||||
// p2pTestMode: false,
|
||||
@@ -467,6 +464,8 @@ var config = {
|
||||
// low: 100000,
|
||||
// standard: 300000,
|
||||
// high: 1000000,
|
||||
// fullHd: 2000000,
|
||||
// ultraHd: 4000000,
|
||||
// ssHigh: 2500000
|
||||
// },
|
||||
// scalabilityModeEnabled: true,
|
||||
@@ -478,6 +477,8 @@ var config = {
|
||||
// low: 200000,
|
||||
// standard: 500000,
|
||||
// high: 1500000,
|
||||
// fullHd: 3000000,
|
||||
// ultraHd: 6000000,
|
||||
// ssHigh: 2500000
|
||||
// },
|
||||
// scalabilityModeEnabled: true
|
||||
@@ -487,6 +488,8 @@ var config = {
|
||||
// low: 200000,
|
||||
// standard: 500000,
|
||||
// high: 1500000,
|
||||
// fullHd: 3000000,
|
||||
// ultraHd: 6000000,
|
||||
// ssHigh: 2500000
|
||||
// },
|
||||
// scalabilityModeEnabled: false
|
||||
@@ -496,6 +499,8 @@ var config = {
|
||||
// low: 100000,
|
||||
// standard: 300000,
|
||||
// high: 1200000,
|
||||
// fullHd: 2500000,
|
||||
// ultraHd: 5000000,
|
||||
// ssHigh: 2500000
|
||||
// },
|
||||
// scalabilityModeEnabled: true,
|
||||
@@ -840,6 +845,22 @@ 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
|
||||
@@ -1782,6 +1803,9 @@ 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.
|
||||
|
||||
2
debian/control
vendored
2
debian/control
vendored
@@ -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), nodejs
|
||||
Build-Depends: debhelper (>= 8.0.0)
|
||||
Standards-Version: 3.9.6
|
||||
Homepage: https://jitsi.org/meet
|
||||
|
||||
|
||||
6
debian/jitsi-meet-prosody.postinst
vendored
6
debian/jitsi-meet-prosody.postinst
vendored
@@ -138,10 +138,8 @@ case "$1" in
|
||||
PROSODY_CONFIG_PRESENT="false"
|
||||
fi
|
||||
|
||||
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
|
||||
# creates the user if it does not exist
|
||||
echo -e "$JVB_SECRET\n$JVB_SECRET" | prosodyctl adduser jvb@$JICOFO_AUTH_DOMAIN > /dev/null || true
|
||||
|
||||
# Check whether prosody config has the internal muc, if not add it,
|
||||
# as we are migrating configs
|
||||
|
||||
365
ios/Podfile.lock
365
ios/Podfile.lock
@@ -14,14 +14,14 @@ PODS:
|
||||
- CocoaLumberjack/Core (= 3.7.2)
|
||||
- CocoaLumberjack/Core (3.7.2)
|
||||
- DoubleConversion (1.1.6)
|
||||
- FBLazyVector (0.72.9)
|
||||
- FBReactNativeSpec (0.72.9):
|
||||
- FBLazyVector (0.72.14)
|
||||
- FBReactNativeSpec (0.72.14):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- RCTRequired (= 0.72.9)
|
||||
- RCTTypeSafety (= 0.72.9)
|
||||
- React-Core (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- ReactCommon/turbomodule/core (= 0.72.9)
|
||||
- RCTRequired (= 0.72.14)
|
||||
- RCTTypeSafety (= 0.72.14)
|
||||
- React-Core (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- ReactCommon/turbomodule/core (= 0.72.14)
|
||||
- Firebase/Analytics (8.15.0):
|
||||
- Firebase/Core
|
||||
- Firebase/Core (8.15.0):
|
||||
@@ -134,7 +134,7 @@ PODS:
|
||||
- AppAuth/Core (~> 1.6)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 1.5)
|
||||
- GTMSessionFetcher/Core (3.2.0)
|
||||
- JitsiWebRTC (118.0.0)
|
||||
- JitsiWebRTC (124.0.0)
|
||||
- libwebp (1.3.2):
|
||||
- libwebp/demux (= 1.3.2)
|
||||
- libwebp/mux (= 1.3.2)
|
||||
@@ -167,26 +167,26 @@ PODS:
|
||||
- DoubleConversion
|
||||
- fmt (~> 6.2.1)
|
||||
- glog
|
||||
- RCTRequired (0.72.9)
|
||||
- RCTTypeSafety (0.72.9):
|
||||
- FBLazyVector (= 0.72.9)
|
||||
- RCTRequired (= 0.72.9)
|
||||
- React-Core (= 0.72.9)
|
||||
- React (0.72.9):
|
||||
- React-Core (= 0.72.9)
|
||||
- React-Core/DevSupport (= 0.72.9)
|
||||
- React-Core/RCTWebSocket (= 0.72.9)
|
||||
- React-RCTActionSheet (= 0.72.9)
|
||||
- React-RCTAnimation (= 0.72.9)
|
||||
- React-RCTBlob (= 0.72.9)
|
||||
- React-RCTImage (= 0.72.9)
|
||||
- React-RCTLinking (= 0.72.9)
|
||||
- React-RCTNetwork (= 0.72.9)
|
||||
- React-RCTSettings (= 0.72.9)
|
||||
- React-RCTText (= 0.72.9)
|
||||
- React-RCTVibration (= 0.72.9)
|
||||
- React-callinvoker (0.72.9)
|
||||
- React-Codegen (0.72.9):
|
||||
- RCTRequired (0.72.14)
|
||||
- RCTTypeSafety (0.72.14):
|
||||
- FBLazyVector (= 0.72.14)
|
||||
- RCTRequired (= 0.72.14)
|
||||
- React-Core (= 0.72.14)
|
||||
- React (0.72.14):
|
||||
- React-Core (= 0.72.14)
|
||||
- React-Core/DevSupport (= 0.72.14)
|
||||
- React-Core/RCTWebSocket (= 0.72.14)
|
||||
- React-RCTActionSheet (= 0.72.14)
|
||||
- React-RCTAnimation (= 0.72.14)
|
||||
- React-RCTBlob (= 0.72.14)
|
||||
- React-RCTImage (= 0.72.14)
|
||||
- React-RCTLinking (= 0.72.14)
|
||||
- React-RCTNetwork (= 0.72.14)
|
||||
- React-RCTSettings (= 0.72.14)
|
||||
- React-RCTText (= 0.72.14)
|
||||
- React-RCTVibration (= 0.72.14)
|
||||
- React-callinvoker (0.72.14)
|
||||
- React-Codegen (0.72.14):
|
||||
- DoubleConversion
|
||||
- FBReactNativeSpec
|
||||
- glog
|
||||
@@ -201,10 +201,10 @@ PODS:
|
||||
- React-rncore
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- React-Core (0.72.9):
|
||||
- React-Core (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default (= 0.72.9)
|
||||
- React-Core/Default (= 0.72.14)
|
||||
- React-cxxreact
|
||||
- React-jsc
|
||||
- React-jsi
|
||||
@@ -214,7 +214,7 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/CoreModulesHeaders (0.72.9):
|
||||
- React-Core/CoreModulesHeaders (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default
|
||||
@@ -227,7 +227,7 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/Default (0.72.9):
|
||||
- React-Core/Default (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-cxxreact
|
||||
@@ -239,22 +239,22 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/DevSupport (0.72.9):
|
||||
- React-Core/DevSupport (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default (= 0.72.9)
|
||||
- React-Core/RCTWebSocket (= 0.72.9)
|
||||
- React-Core/Default (= 0.72.14)
|
||||
- React-Core/RCTWebSocket (= 0.72.14)
|
||||
- React-cxxreact
|
||||
- React-jsc
|
||||
- React-jsi
|
||||
- React-jsiexecutor
|
||||
- React-jsinspector (= 0.72.9)
|
||||
- React-jsinspector (= 0.72.14)
|
||||
- React-perflogger
|
||||
- React-runtimeexecutor
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/RCTActionSheetHeaders (0.72.9):
|
||||
- React-Core/RCTActionSheetHeaders (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default
|
||||
@@ -267,7 +267,7 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/RCTAnimationHeaders (0.72.9):
|
||||
- React-Core/RCTAnimationHeaders (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default
|
||||
@@ -280,7 +280,7 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/RCTBlobHeaders (0.72.9):
|
||||
- React-Core/RCTBlobHeaders (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default
|
||||
@@ -293,7 +293,7 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/RCTImageHeaders (0.72.9):
|
||||
- React-Core/RCTImageHeaders (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default
|
||||
@@ -306,7 +306,7 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/RCTLinkingHeaders (0.72.9):
|
||||
- React-Core/RCTLinkingHeaders (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default
|
||||
@@ -319,7 +319,7 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/RCTNetworkHeaders (0.72.9):
|
||||
- React-Core/RCTNetworkHeaders (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default
|
||||
@@ -332,7 +332,7 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/RCTSettingsHeaders (0.72.9):
|
||||
- React-Core/RCTSettingsHeaders (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default
|
||||
@@ -345,7 +345,7 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/RCTTextHeaders (0.72.9):
|
||||
- React-Core/RCTTextHeaders (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default
|
||||
@@ -358,7 +358,7 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/RCTVibrationHeaders (0.72.9):
|
||||
- React-Core/RCTVibrationHeaders (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default
|
||||
@@ -371,10 +371,10 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-Core/RCTWebSocket (0.72.9):
|
||||
- React-Core/RCTWebSocket (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core/Default (= 0.72.9)
|
||||
- React-Core/Default (= 0.72.14)
|
||||
- React-cxxreact
|
||||
- React-jsc
|
||||
- React-jsi
|
||||
@@ -384,48 +384,48 @@ PODS:
|
||||
- React-utils
|
||||
- SocketRocket (= 0.6.1)
|
||||
- Yoga
|
||||
- React-CoreModules (0.72.9):
|
||||
- React-CoreModules (0.72.14):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- RCTTypeSafety (= 0.72.9)
|
||||
- React-Codegen (= 0.72.9)
|
||||
- React-Core/CoreModulesHeaders (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- RCTTypeSafety (= 0.72.14)
|
||||
- React-Codegen (= 0.72.14)
|
||||
- React-Core/CoreModulesHeaders (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- React-RCTBlob
|
||||
- React-RCTImage (= 0.72.9)
|
||||
- ReactCommon/turbomodule/core (= 0.72.9)
|
||||
- React-RCTImage (= 0.72.14)
|
||||
- ReactCommon/turbomodule/core (= 0.72.14)
|
||||
- SocketRocket (= 0.6.1)
|
||||
- React-cxxreact (0.72.9):
|
||||
- React-cxxreact (0.72.14):
|
||||
- boost (= 1.76.0)
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-callinvoker (= 0.72.9)
|
||||
- React-debug (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- React-jsinspector (= 0.72.9)
|
||||
- React-logger (= 0.72.9)
|
||||
- React-perflogger (= 0.72.9)
|
||||
- React-runtimeexecutor (= 0.72.9)
|
||||
- React-debug (0.72.9)
|
||||
- React-jsc (0.72.9):
|
||||
- React-jsc/Fabric (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- React-jsc/Fabric (0.72.9):
|
||||
- React-jsi (= 0.72.9)
|
||||
- React-jsi (0.72.9):
|
||||
- React-callinvoker (= 0.72.14)
|
||||
- React-debug (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- React-jsinspector (= 0.72.14)
|
||||
- React-logger (= 0.72.14)
|
||||
- React-perflogger (= 0.72.14)
|
||||
- React-runtimeexecutor (= 0.72.14)
|
||||
- React-debug (0.72.14)
|
||||
- React-jsc (0.72.14):
|
||||
- React-jsc/Fabric (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- React-jsc/Fabric (0.72.14):
|
||||
- React-jsi (= 0.72.14)
|
||||
- React-jsi (0.72.14):
|
||||
- boost (= 1.76.0)
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-jsiexecutor (0.72.9):
|
||||
- React-jsiexecutor (0.72.14):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-cxxreact (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- React-perflogger (= 0.72.9)
|
||||
- React-jsinspector (0.72.9)
|
||||
- React-logger (0.72.9):
|
||||
- React-cxxreact (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- React-perflogger (= 0.72.14)
|
||||
- React-jsinspector (0.72.14)
|
||||
- React-logger (0.72.14):
|
||||
- glog
|
||||
- react-native-background-timer (2.4.1):
|
||||
- React-Core
|
||||
@@ -453,12 +453,13 @@ PODS:
|
||||
- react-native-video/Video (6.0.0-alpha.11):
|
||||
- PromisesSwift
|
||||
- React-Core
|
||||
- react-native-webrtc (118.0.7):
|
||||
- JitsiWebRTC (~> 118.0.0)
|
||||
- react-native-webrtc (124.0.0):
|
||||
- JitsiWebRTC (~> 124.0.0)
|
||||
- React-Core
|
||||
- react-native-webview (13.5.1):
|
||||
- react-native-webview (13.8.7):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core
|
||||
- React-NativeModulesApple (0.72.9):
|
||||
- React-NativeModulesApple (0.72.14):
|
||||
- React-callinvoker
|
||||
- React-Core
|
||||
- React-cxxreact
|
||||
@@ -466,17 +467,17 @@ PODS:
|
||||
- React-runtimeexecutor
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- React-perflogger (0.72.9)
|
||||
- React-RCTActionSheet (0.72.9):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.72.9)
|
||||
- React-RCTAnimation (0.72.9):
|
||||
- React-perflogger (0.72.14)
|
||||
- React-RCTActionSheet (0.72.14):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.72.14)
|
||||
- React-RCTAnimation (0.72.14):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- RCTTypeSafety (= 0.72.9)
|
||||
- React-Codegen (= 0.72.9)
|
||||
- React-Core/RCTAnimationHeaders (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- ReactCommon/turbomodule/core (= 0.72.9)
|
||||
- React-RCTAppDelegate (0.72.9):
|
||||
- RCTTypeSafety (= 0.72.14)
|
||||
- React-Codegen (= 0.72.14)
|
||||
- React-Core/RCTAnimationHeaders (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- ReactCommon/turbomodule/core (= 0.72.14)
|
||||
- React-RCTAppDelegate (0.72.14):
|
||||
- RCT-Folly
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@@ -488,81 +489,81 @@ PODS:
|
||||
- React-RCTNetwork
|
||||
- React-runtimescheduler
|
||||
- ReactCommon/turbomodule/core
|
||||
- React-RCTBlob (0.72.9):
|
||||
- React-RCTBlob (0.72.14):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Codegen (= 0.72.9)
|
||||
- React-Core/RCTBlobHeaders (= 0.72.9)
|
||||
- React-Core/RCTWebSocket (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- React-RCTNetwork (= 0.72.9)
|
||||
- ReactCommon/turbomodule/core (= 0.72.9)
|
||||
- React-RCTImage (0.72.9):
|
||||
- React-Codegen (= 0.72.14)
|
||||
- React-Core/RCTBlobHeaders (= 0.72.14)
|
||||
- React-Core/RCTWebSocket (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- React-RCTNetwork (= 0.72.14)
|
||||
- ReactCommon/turbomodule/core (= 0.72.14)
|
||||
- React-RCTImage (0.72.14):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- RCTTypeSafety (= 0.72.9)
|
||||
- React-Codegen (= 0.72.9)
|
||||
- React-Core/RCTImageHeaders (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- React-RCTNetwork (= 0.72.9)
|
||||
- ReactCommon/turbomodule/core (= 0.72.9)
|
||||
- React-RCTLinking (0.72.9):
|
||||
- React-Codegen (= 0.72.9)
|
||||
- React-Core/RCTLinkingHeaders (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- ReactCommon/turbomodule/core (= 0.72.9)
|
||||
- React-RCTNetwork (0.72.9):
|
||||
- RCTTypeSafety (= 0.72.14)
|
||||
- React-Codegen (= 0.72.14)
|
||||
- React-Core/RCTImageHeaders (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- React-RCTNetwork (= 0.72.14)
|
||||
- ReactCommon/turbomodule/core (= 0.72.14)
|
||||
- React-RCTLinking (0.72.14):
|
||||
- React-Codegen (= 0.72.14)
|
||||
- React-Core/RCTLinkingHeaders (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- ReactCommon/turbomodule/core (= 0.72.14)
|
||||
- React-RCTNetwork (0.72.14):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- RCTTypeSafety (= 0.72.9)
|
||||
- React-Codegen (= 0.72.9)
|
||||
- React-Core/RCTNetworkHeaders (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- ReactCommon/turbomodule/core (= 0.72.9)
|
||||
- React-RCTSettings (0.72.9):
|
||||
- RCTTypeSafety (= 0.72.14)
|
||||
- React-Codegen (= 0.72.14)
|
||||
- React-Core/RCTNetworkHeaders (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- ReactCommon/turbomodule/core (= 0.72.14)
|
||||
- React-RCTSettings (0.72.14):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- RCTTypeSafety (= 0.72.9)
|
||||
- React-Codegen (= 0.72.9)
|
||||
- React-Core/RCTSettingsHeaders (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- ReactCommon/turbomodule/core (= 0.72.9)
|
||||
- React-RCTText (0.72.9):
|
||||
- React-Core/RCTTextHeaders (= 0.72.9)
|
||||
- React-RCTVibration (0.72.9):
|
||||
- RCTTypeSafety (= 0.72.14)
|
||||
- React-Codegen (= 0.72.14)
|
||||
- React-Core/RCTSettingsHeaders (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- ReactCommon/turbomodule/core (= 0.72.14)
|
||||
- React-RCTText (0.72.14):
|
||||
- React-Core/RCTTextHeaders (= 0.72.14)
|
||||
- React-RCTVibration (0.72.14):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Codegen (= 0.72.9)
|
||||
- React-Core/RCTVibrationHeaders (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- ReactCommon/turbomodule/core (= 0.72.9)
|
||||
- React-rncore (0.72.9)
|
||||
- React-runtimeexecutor (0.72.9):
|
||||
- React-jsi (= 0.72.9)
|
||||
- React-runtimescheduler (0.72.9):
|
||||
- React-Codegen (= 0.72.14)
|
||||
- React-Core/RCTVibrationHeaders (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- ReactCommon/turbomodule/core (= 0.72.14)
|
||||
- React-rncore (0.72.14)
|
||||
- React-runtimeexecutor (0.72.14):
|
||||
- React-jsi (= 0.72.14)
|
||||
- React-runtimescheduler (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-callinvoker
|
||||
- React-debug
|
||||
- React-jsi
|
||||
- React-runtimeexecutor
|
||||
- React-utils (0.72.9):
|
||||
- React-utils (0.72.14):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-debug
|
||||
- ReactCommon/turbomodule/bridging (0.72.9):
|
||||
- ReactCommon/turbomodule/bridging (0.72.14):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-callinvoker (= 0.72.9)
|
||||
- React-cxxreact (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- React-logger (= 0.72.9)
|
||||
- React-perflogger (= 0.72.9)
|
||||
- ReactCommon/turbomodule/core (0.72.9):
|
||||
- React-callinvoker (= 0.72.14)
|
||||
- React-cxxreact (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- React-logger (= 0.72.14)
|
||||
- React-perflogger (= 0.72.14)
|
||||
- ReactCommon/turbomodule/core (0.72.14):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-callinvoker (= 0.72.9)
|
||||
- React-cxxreact (= 0.72.9)
|
||||
- React-jsi (= 0.72.9)
|
||||
- React-logger (= 0.72.9)
|
||||
- React-perflogger (= 0.72.9)
|
||||
- React-callinvoker (= 0.72.14)
|
||||
- React-cxxreact (= 0.72.14)
|
||||
- React-jsi (= 0.72.14)
|
||||
- React-logger (= 0.72.14)
|
||||
- React-perflogger (= 0.72.14)
|
||||
- RNCalendarEvents (2.2.0):
|
||||
- React
|
||||
- RNCAsyncStorage (1.19.4):
|
||||
@@ -830,8 +831,8 @@ SPEC CHECKSUMS:
|
||||
boost: 7dcd2de282d72e344012f7d6564d024930a6a440
|
||||
CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
FBLazyVector: dc178b8748748c036ef9493a5d59d6d1f91a36ce
|
||||
FBReactNativeSpec: d0aaae78e93c89dc2d691d8052a4d2aeb1b461ee
|
||||
FBLazyVector: d98eefb42c5a64cb28ef966bd9096c76770d8f24
|
||||
FBReactNativeSpec: 53d4eb00e8e1b6e987a3dd5906d2afe131cc54c8
|
||||
Firebase: 5f8193dff4b5b7c5d5ef72ae54bb76c08e2b841d
|
||||
FirebaseAnalytics: 7761cbadb00a717d8d0939363eb46041526474fa
|
||||
FirebaseCore: 5743c5785c074a794d35f2fff7ecc254a91e08b1
|
||||
@@ -849,27 +850,27 @@ SPEC CHECKSUMS:
|
||||
GoogleUtilities: 0759d1a57ebb953965c2dfe0ba4c82e95ccc2e34
|
||||
GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae
|
||||
GTMSessionFetcher: 41b9ef0b4c08a6db4b7eb51a21ae5183ec99a2c8
|
||||
JitsiWebRTC: 3a41671ef65a51d7204323814b055a2690b921c7
|
||||
JitsiWebRTC: 37fb2fb70d42cac58c06948527a5f9e1b3f50812
|
||||
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
|
||||
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
|
||||
ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53
|
||||
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
|
||||
PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265
|
||||
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
|
||||
RCTRequired: f30c3213569b1dc43659ecc549a6536e1e11139e
|
||||
RCTTypeSafety: e1ed3137728804fa98bce30b70e3da0b8e23054e
|
||||
React: 54070abee263d5773486987f1cf3a3616710ed52
|
||||
React-callinvoker: 794ea19cc4d8ce25921893141e131b9d6b7d02eb
|
||||
React-Codegen: d5dc2cae27c93b0fab4241816343c586c58965c1
|
||||
React-Core: f82477baba3526cf8a4f70055a22a8e878e3ef9c
|
||||
React-CoreModules: 87cc386c2200862672b76bb02c4574b4b1d11b3c
|
||||
React-cxxreact: c7163706a8391f373cd513348a8ebbd5a4444626
|
||||
React-debug: 4dca41301a67ab2916b2c99bef60344a7b653ac5
|
||||
React-jsc: 44ec1ce4171f4b6776810618da8e3da45899ea77
|
||||
React-jsi: 799e7004db36b0b6da2d0969ce07bf3a96df8d19
|
||||
React-jsiexecutor: bb34b280cfad63aedb266571a305b6365affb875
|
||||
React-jsinspector: 54205b269da20c51417e0fc02c4cde9f29a4bf1a
|
||||
React-logger: f42d2f2bc4cbb5d19d7c0ce84b8741b1e54e88c8
|
||||
RCTRequired: 264adaca1d8b1a9c078761891898d4142df05313
|
||||
RCTTypeSafety: 279a89da7058a69899778a127be73fab38b84499
|
||||
React: 725b4e11f6ffb43d6f9b14e82879073623db4071
|
||||
React-callinvoker: c2ba5e7e1187d0f37705b9dcaaf9bbf24d3fe9dc
|
||||
React-Codegen: ca5bc760044f8472bf2a78053937307fd2021820
|
||||
React-Core: d0796eb91058542db9e67fa9b1b11c66092de1ac
|
||||
React-CoreModules: 57ea4ca8627be90b1a29916e0640d879e5684305
|
||||
React-cxxreact: bbd34bad63b639dbb2c162cce518e8dba33cef57
|
||||
React-debug: d360c17c84e514b9143e78217072183d4fcfb9c0
|
||||
React-jsc: 6e1aae704b76f5626c00c87439fe405f34a6446e
|
||||
React-jsi: 5fe75c2ff9eea1e90ed5437fcbafb64032383d52
|
||||
React-jsiexecutor: a05da2c08edb1314cc51671c795fd4ed8440c335
|
||||
React-jsinspector: 275d9f80210f15f0af9a4b7fd5683fab9738e28e
|
||||
React-logger: 8da4802de77a0eb62512396ad6bb1769904c2f0e
|
||||
react-native-background-timer: 17ea5e06803401a379ebf1f20505b793ac44d0fe
|
||||
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
|
||||
react-native-keep-awake: afad8a51dfef9fe9655a6344771be32c8596d774
|
||||
@@ -881,25 +882,25 @@ SPEC CHECKSUMS:
|
||||
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
|
||||
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
||||
react-native-video: 472b7c366eaaaa0207e546d9a50410df89790bcf
|
||||
react-native-webrtc: 8b024c7bb9a005d2b9efeba4c691172dbd00268d
|
||||
react-native-webview: 8baa0f5c6d336d6ba488e942bcadea5bf51f050a
|
||||
React-NativeModulesApple: 4225ac31a26696c02c54b471052b3e85e74a9a0c
|
||||
React-perflogger: cb433f318c6667060fc1f62e26eb58d6eb30a627
|
||||
React-RCTActionSheet: 0af3f8ac067e8a1dde902810b7ad169d0a0ec31e
|
||||
React-RCTAnimation: 453a88e76ba6cb49819686acd8b21ce4d9ee4232
|
||||
React-RCTAppDelegate: 009ede96d00f79460a75ab5071eadb4aaa1e5012
|
||||
React-RCTBlob: a64134435f331c7cc716dcea6944a1443af49d32
|
||||
React-RCTImage: 8e059fbdfab18b86127424dc3742532aab960760
|
||||
React-RCTLinking: 05ae2aa525b21a7f1c5069c14330700f470efd97
|
||||
React-RCTNetwork: 7ed9d99d028c53e9a23e318f65937f499ba8a6fd
|
||||
React-RCTSettings: 8b12ebf04d4baa0e259017fcef6cf7abd7d8ac51
|
||||
React-RCTText: a062ade9ff1591c46bcb6c5055fd4f96c154b8aa
|
||||
React-RCTVibration: 87c490b6f01746ab8f9b4e555f514cc030c06731
|
||||
React-rncore: 140bc11b316da7003bf039844aef39e1c242d7ad
|
||||
React-runtimeexecutor: 226ebef5f625878d3028b196cbecbbdeb6f208e4
|
||||
React-runtimescheduler: 75f2210cd7a50c4565dfd218e320479013b82f70
|
||||
React-utils: a3ffbc321572ee91911d7bc30965abe9aa4e16af
|
||||
ReactCommon: d3522e54560e4d940554aa074a0206bf4d39ae5e
|
||||
react-native-webrtc: ae5f9793fc58138ecb5bdf0afe7cfdb3f09888ed
|
||||
react-native-webview: 95464c1249cae28186d3b7c6f494c6a124b16160
|
||||
React-NativeModulesApple: 9b58b8f5c1d8ea68d0a63f569a9e786d01281ea6
|
||||
React-perflogger: daabc494c6328efc1784a4b49b8b74fca305d11c
|
||||
React-RCTActionSheet: 0e0e64a7cf6c07f1de73d1f0a92d26a70262b256
|
||||
React-RCTAnimation: faef65b19e73029c4828167985b0a7c01c62756d
|
||||
React-RCTAppDelegate: 1c4c94324d5dee57c79f4b68dc709b1c290068c1
|
||||
React-RCTBlob: df6a165ecd97eb5a92d659aa91866ee9ee50bd5e
|
||||
React-RCTImage: 15e211cbb629210ab9c1fa37e07e7100362b12ed
|
||||
React-RCTLinking: 50d5faf19b02541cefb78ee5d505029283c8ef95
|
||||
React-RCTNetwork: dfa9fb4ad2ae459b9193a14204b1d9da907d15a7
|
||||
React-RCTSettings: 37611fa97d44a9c5a7ea844cfb953d3513f7ace0
|
||||
React-RCTText: 39ed334f64484d07b85a8159cf117814ff069ff6
|
||||
React-RCTVibration: 62462803b5fe0842e0be6d9ef86dd74e0df4a614
|
||||
React-rncore: 25ad3a3c1e0f4edf77913b9af3af9f497b7f99a5
|
||||
React-runtimeexecutor: e5c2f0a1493d72c61b97465ccfedc339157b3179
|
||||
React-runtimescheduler: 5b3b73748021c7349f06fcd24181df17860b8fb6
|
||||
React-utils: 22a77b05da25ce49c744faa82e73856dcae1734e
|
||||
ReactCommon: 07937a01c967f2023d6f2d07114315f099b4b436
|
||||
RNCalendarEvents: 7e65eb4a94f53c1744d1e275f7fafcfaa619f7a3
|
||||
RNCAsyncStorage: 3a8f7145d17cdd9f88e7b77666c94a09e4f759c8
|
||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
||||
@@ -912,8 +913,8 @@ SPEC CHECKSUMS:
|
||||
RNSVG: ed492aaf3af9ca01bc945f7a149d76d62e73ec82
|
||||
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236
|
||||
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
|
||||
Yoga: eddf2bbe4a896454c248a8f23b4355891eb720a6
|
||||
Yoga: c32e0be1a17f8f1f0e633a3122f7666441f52c82
|
||||
|
||||
PODFILE CHECKSUM: ec00682c7062a323dff24a3c3643ca0bbb420d01
|
||||
|
||||
COCOAPODS: 1.14.3
|
||||
COCOAPODS: 1.15.2
|
||||
|
||||
37
ios/PrivacyInfo.xcprivacy
Normal file
37
ios/PrivacyInfo.xcprivacy
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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>
|
||||
@@ -151,6 +151,7 @@
|
||||
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; };
|
||||
@@ -293,6 +294,7 @@
|
||||
0BEA5C351F7B8F73000D0AB4 /* WatchKit extension */,
|
||||
4EB06025260E026600F524C5 /* JitsiMeetBroadcast Extension */,
|
||||
CDD71F5E1157E9F283DF92A8 /* Pods */,
|
||||
6132EF172BDFF13200BBE14D /* PrivacyInfo.xcprivacy */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
@@ -367,13 +369,12 @@
|
||||
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 */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -411,7 +412,7 @@
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1240;
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = Facebook;
|
||||
ORGANIZATIONNAME = Jitsi;
|
||||
TargetAttributes = {
|
||||
0BEA5C241F7B8F73000D0AB4 = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
@@ -499,10 +500,12 @@
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
0BB7DA181EC9E695007AAE98 /* Adjust ATS */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH",
|
||||
);
|
||||
name = "Adjust ATS";
|
||||
outputPaths = (
|
||||
@@ -513,6 +516,7 @@
|
||||
};
|
||||
0BBA83C41EC9F7600075A103 /* Run React packager */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
@@ -527,12 +531,14 @@
|
||||
};
|
||||
4E81688528A408E600F8FA9E /* Update App Entitlements */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$PROJECT_DIR/app.entitlements",
|
||||
);
|
||||
name = "Update App Entitlements";
|
||||
outputFileListPaths = (
|
||||
@@ -567,12 +573,14 @@
|
||||
};
|
||||
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 = (
|
||||
@@ -581,16 +589,18 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
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";
|
||||
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";
|
||||
};
|
||||
DE4F6D6E22005C0400DE699E /* Setup Dropbox */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH",
|
||||
);
|
||||
name = "Setup Dropbox";
|
||||
outputFileListPaths = (
|
||||
@@ -601,24 +611,6 @@
|
||||
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";
|
||||
};
|
||||
DEF4813D224925A2002AD03A /* Copy Google Plist file */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Copy Google Plist file";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
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\"\n\nif [[ -f $GOOGLE_PLIST ]]; then\n cp $GOOGLE_PLIST \"$BUILD_APP_DIR/$GOOGLE_PLIST_NAME\"\nfi\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -1029,10 +1021,7 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -1092,10 +1081,7 @@
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
|
||||
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];
|
||||
}];
|
||||
|
||||
@@ -79,7 +79,8 @@ platform :ios do
|
||||
build_app(
|
||||
scheme: "JitsiMeet",
|
||||
include_symbols: true,
|
||||
export_xcargs: "-allowProvisioningUpdates"
|
||||
export_xcargs: "-allowProvisioningUpdates",
|
||||
xcodebuild_formatter: ""
|
||||
)
|
||||
|
||||
# Upload the build to TestFlight
|
||||
|
||||
@@ -145,6 +145,7 @@
|
||||
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>"; };
|
||||
@@ -230,6 +231,7 @@
|
||||
0BD906E61EC0C00300C8C18E /* Products */,
|
||||
0BCA49681EC4BBE500B793EE /* Resources */,
|
||||
0BD906E71EC0C00300C8C18E /* src */,
|
||||
6132EF172BDFF13200BBE14D /* PrivacyInfo.xcprivacy */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -779,10 +781,7 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -845,10 +844,7 @@
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
"privateNotice": "Private Nachricht an {{recipient}}",
|
||||
"sendButton": "Senden",
|
||||
"smileysPanel": "Emoji-Auswahl",
|
||||
"systemDisplayName": "System",
|
||||
"tabs": {
|
||||
"chat": "Chatten",
|
||||
"polls": "Umfragen"
|
||||
@@ -263,6 +264,7 @@
|
||||
"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",
|
||||
@@ -305,6 +307,8 @@
|
||||
"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",
|
||||
@@ -316,6 +320,7 @@
|
||||
"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",
|
||||
@@ -560,6 +565,7 @@
|
||||
"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.",
|
||||
@@ -567,7 +573,8 @@
|
||||
"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"
|
||||
"upgradeOptions": "Bitte prüfen Sie Ihre Upgrade-Optionen auf",
|
||||
"whiteboardError": "Whiteboard konnte nicht geladen werden. Bitte versuchen Sie es später erneut."
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "Es ist ein Fehler aufgetreten.",
|
||||
@@ -731,6 +738,8 @@
|
||||
"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",
|
||||
@@ -802,12 +811,16 @@
|
||||
"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"
|
||||
@@ -937,6 +950,7 @@
|
||||
"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",
|
||||
@@ -1003,7 +1017,6 @@
|
||||
"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.",
|
||||
@@ -1020,7 +1033,6 @@
|
||||
"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",
|
||||
@@ -1177,7 +1189,7 @@
|
||||
"audioOnly": "„Nur Audio“ ein-/ausschalten",
|
||||
"audioRoute": "Audiogerät auswählen",
|
||||
"boo": "Buhen",
|
||||
"breakoutRoom": "Breakout-Räume betreten/verlassen",
|
||||
"breakoutRooms": "Breakout-Räume",
|
||||
"callQuality": "Qualitätseinstellungen",
|
||||
"carmode": "Automodus",
|
||||
"cc": "Untertitel ein-/ausschalten",
|
||||
@@ -1360,13 +1372,9 @@
|
||||
},
|
||||
"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",
|
||||
@@ -1422,6 +1430,7 @@
|
||||
},
|
||||
"videothumbnail": {
|
||||
"connectionInfo": "Verbindungsinformationen",
|
||||
"demote": "Zu Gästen verschieben",
|
||||
"domute": "Stummschalten",
|
||||
"domuteOthers": "Alle anderen stummschalten",
|
||||
"domuteVideo": "Kamera ausschalten",
|
||||
@@ -1476,7 +1485,12 @@
|
||||
"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"
|
||||
}
|
||||
},
|
||||
@@ -1536,6 +1550,7 @@
|
||||
"whiteboard": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Whiteboard"
|
||||
}
|
||||
},
|
||||
"screenTitle": "Whiteboard"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
"mainRoom": "Salle principale",
|
||||
"notifications": {
|
||||
"joined": "Entrée en salle annexe \"{{name}}\"",
|
||||
"joinedMainRoom": "Retour à la salle principalem",
|
||||
"joinedMainRoom": "Retour à la salle principale",
|
||||
"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.",
|
||||
@@ -138,7 +138,7 @@
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"buttonText": "Installer l'extension Chrome",
|
||||
"buttonTextEdge": "Installer l’extension Edge",
|
||||
"buttonTextEdge": "Installer l'extension Edge",
|
||||
"close": "Fermer",
|
||||
"dontShowAgain": "Ne plus m'afficher ceci",
|
||||
"installExtensionText": "Installer l'extension pour l'intégration de Google Calendar et Office 365"
|
||||
@@ -424,7 +424,7 @@
|
||||
"shareAudioAltText": "Pour partager le contenu voulu, naviguer vers \"Onglet du Navigateur\", sélectionner le contenu, activer le bouton \"partager l’audio\" et enfin cliquer sur le bouton \"partager\"",
|
||||
"shareAudioTitle": "Comment partager le son",
|
||||
"shareAudioWarningD1": "vous devez cesser le partage d'écran avant de partager votre son.",
|
||||
"shareAudioWarningD2": "viys devez partager votre écran à nouveau et cocher l'ootion \"Partager l'audio\".",
|
||||
"shareAudioWarningD2": "vous devez partager votre écran à nouveau et cocher l'option \"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",
|
||||
@@ -1169,7 +1169,7 @@
|
||||
"toolbar": {
|
||||
"Settings": "Paramètres",
|
||||
"accessibilityLabel": {
|
||||
"Settings": "Afficher / Masquer le menu des paramètres",
|
||||
"Settings": "Ouvrir le menu des paramètres",
|
||||
"audioOnly": "Activer / Désactiver le mode voix uniquement",
|
||||
"audioRoute": "Sélectionner la source audio",
|
||||
"boo": "Hou",
|
||||
@@ -1215,7 +1215,7 @@
|
||||
"moreActions": "Activer / Désactiver le menu d'actions supplémentaires",
|
||||
"moreActionsMenu": "Menu d'actions supplémentaires",
|
||||
"moreOptions": "Voir plus d'options",
|
||||
"mute": "Activer / Désactiver l'audio",
|
||||
"mute": "Couper votre micro",
|
||||
"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",
|
||||
@@ -1251,11 +1251,11 @@
|
||||
"tileView": "Activer / Désactiver la vue mosaïque",
|
||||
"toggleCamera": "Changer de caméra",
|
||||
"toggleFilmstrip": "Afficher ou masquer les vignettes vidéo",
|
||||
"unmute": "Rétablir le son",
|
||||
"unmute": "Activer votre micro",
|
||||
"videoblur": "Activer / désactiver le floutage",
|
||||
"videomute": "Activer / Couper la vidéo",
|
||||
"videomute": "Couper votre vidéo",
|
||||
"videomuteGUMPending": "Connexion de votre caméra",
|
||||
"videounmute": "Démarrer la vidéo"
|
||||
"videounmute": "Activer votre vidéo"
|
||||
},
|
||||
"addPeople": "Ajouter des personnes à votre appel",
|
||||
"audioOnlyOff": "Désactiver le mode bande passante réduite",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -184,6 +184,7 @@
|
||||
"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": {
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
"privateNotice": "Privāta ziņa adresātam {{recipient}}",
|
||||
"sendButton": "Nosūtīt",
|
||||
"smileysPanel": "Emociju panelis",
|
||||
"systemDisplayName": "Sistēma",
|
||||
"tabs": {
|
||||
"chat": "Tērzēšana",
|
||||
"polls": "Aptaujas"
|
||||
@@ -318,6 +319,7 @@
|
||||
"embedMeeting": "Iegult sapulci",
|
||||
"enterDisplayName": "Ievadiet savu vārdu",
|
||||
"error": "Kļūda",
|
||||
"errorRoomCreationRestriction": "Jūs mēģinājāt pievienoties pārāk ātri. Lūdzu, atgriezieties vēlāk.",
|
||||
"gracefulShutdown": "Mūsu serviss pašlaik nedarbojas apkopes dēļ. Lūdzu, pamēģiniet vēlreiz vēlāk.",
|
||||
"grantModeratorDialog": "Vai tiešām vēlaties piešķirt moderatora tiesības dalībniekam {{participantName}}?",
|
||||
"grantModeratorTitle": "Piešķirt moderatora tiesības",
|
||||
@@ -353,28 +355,28 @@
|
||||
"micPermissionDeniedError": "Nav piekļuves mikrofonam. Jūs varat piedalīties sapulcē, bet citi jūs nedzirdēs. Lai to novērstu, izmantojiet kameras ikonu pārlūkprogrammas adrešu joslā.",
|
||||
"micTimeoutError": "Nevarēja palaist audio avotu. Iestājās noildze!",
|
||||
"micUnknownError": "Nevar izmantot mikrofonu nezināma iemesla dēļ.",
|
||||
"moderationAudioLabel": "Ļaujiet dalībniekiem ieslēgt savu mikrofonu",
|
||||
"moderationVideoLabel": "Ļaujiet dalībniekiem ieslēgt savu kameru",
|
||||
"muteEveryoneDialog": "Dalībnieki jebkurā laikā var ieslēgt savu mikrofonu.",
|
||||
"muteEveryoneDialogModerationOn": "Dalībnieki jebkurā laikā var nosūtīt pieprasījumu runāt.",
|
||||
"muteEveryoneElseDialog": "Kad skaņa būs izslēgta, jūs nevarēsit to ieslēgt atpakaļ, taču dalībnieki jebkurā laikā to varēs izdarīt paši.",
|
||||
"moderationAudioLabel": "Atļaut dalībniekiem ieslēgt savu mikrofonu",
|
||||
"moderationVideoLabel": "Atļaut dalībniekiem ieslēgt savu kameru",
|
||||
"muteEveryoneDialog": "Dalībnieki paši var ieslēgt savu mikrofonu.",
|
||||
"muteEveryoneDialogModerationOn": "Dalībnieki var nosūtīt pieprasījumu ieslēgt savu mikrofonu.",
|
||||
"muteEveryoneElseDialog": "Kad skaņa būs izslēgta, jūs nevarēsiet to ieslēgt atpakaļ, taču dalībnieki to varēs izdarīt paši.",
|
||||
"muteEveryoneElseTitle": "Vai izslēgt skaņu visiem, izņemot {{whom}}?",
|
||||
"muteEveryoneElsesVideoDialog": "Kad video būs izslēgts, jūs nevarēsit to ieslēgt atpakaļ, taču dalībnieki jebkurā laikā to varēs izdarīt paši.",
|
||||
"muteEveryoneElsesVideoDialog": "Kad video būs izslēgts, jūs nevarēsiet to ieslēgt atpakaļ, taču dalībnieki to varēs izdarīt paši.",
|
||||
"muteEveryoneElsesVideoTitle": "Vai izslēgt video visiem, izņemot {{whom}}?",
|
||||
"muteEveryoneSelf": "jūs",
|
||||
"muteEveryoneStartMuted": "No šī brīža visi jauni dalībnieki pieslēdzas ar izslēgt skaņu",
|
||||
"muteEveryoneTitle": "Vai izslēgt skaņu visiem?",
|
||||
"muteEveryonesVideoDialog": "Dalībnieki var jebkurā laikā ieslēgt savu video.",
|
||||
"muteEveryonesVideoDialogModerationOn": "Dalībnieki jebkurā laikā var nosūtīt pieprasījumu ieslēgt viņu video.",
|
||||
"muteEveryonesVideoDialog": "Dalībnieki var ieslēgt savu video.",
|
||||
"muteEveryonesVideoDialogModerationOn": "Dalībnieki var nosūtīt pieprasījumu ieslēgt viņu video.",
|
||||
"muteEveryonesVideoDialogOk": "Atspējot",
|
||||
"muteEveryonesVideoTitle": "Vai apturēt ikviena video?",
|
||||
"muteParticipantBody": "Jūs nevarat viņiem ieslēgt skaņu, bet viņi paši to var izdarīt jebkurā laikā.",
|
||||
"muteParticipantBody": "Jūs nevariet viņiem ieslēgt skaņu, bet viņi paši to var izdarīt jebkurā laikā.",
|
||||
"muteParticipantButton": "Izslēgt skaņu",
|
||||
"muteParticipantsVideoBody": "Jūs nevarēsiet atkal ieslēgt kameru, taču viņi var to jebkurā laikā atkal ieslēgt.",
|
||||
"muteParticipantsVideoBodyModerationOn": "Jūs nevarēsiet atkal ieslēgt kameru, un viņi arī nevarēs.",
|
||||
"muteParticipantsVideoBody": "Jūs nevarēsiet kameru ieslēgt atpakaļ, taču viņi paši to varēs izdarīt jebkurā laikā.",
|
||||
"muteParticipantsVideoBodyModerationOn": "Ne Jūs, ne dalībnieki nevarēsiet ieslēgt kameru atpakaļ.",
|
||||
"muteParticipantsVideoButton": "Pārtraukt video",
|
||||
"muteParticipantsVideoDialog": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Jūs nevarēsiet atkal ieslēgt kameru, taču viņi var to jebkurā laikā atkal ieslēgt.",
|
||||
"muteParticipantsVideoDialogModerationOn": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Jūs nevarēsiet atkal ieslēgt kameru, un viņi arī nevarēs.",
|
||||
"muteParticipantsVideoDialog": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Jūs nevarēsiet to ieslēgt atpakaļ, taču dalībnieks pats to varēs izdarīt jebkurā laikā.",
|
||||
"muteParticipantsVideoDialogModerationOn": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Ne Jūs, ne dalībnieks nevarēsiet to ieslēgt atpakaļ.",
|
||||
"muteParticipantsVideoTitle": "Vai izslēgt šī dalībnieka video?",
|
||||
"noDropboxToken": "Nav derīga Dropbox tokena",
|
||||
"password": "Parole",
|
||||
@@ -733,8 +735,10 @@
|
||||
"connectedOneMember": "{{name}} ir pievienojies sapulcei",
|
||||
"connectedThreePlusMembers": "{{name}} un {{count}} citi ir pievienojušies sapulcei",
|
||||
"connectedTwoMembers": "{{first}} un {{second}} ir pievienojušies sapulcei",
|
||||
"dataChannelClosed": "Video kvalitāte ir traucēta",
|
||||
"dataChannelClosedDescription": "Savienojuma kanāls ir atvienots, un tādējādi video kvalitāte ir ierobežota līdz zemākajam iestatījumam.",
|
||||
"dataChannelClosed": "Video kvalitāte var būt traucēta",
|
||||
"dataChannelClosedDescription": "Savienojuma kanāls nedarbojas, tāpēc video kvalitāte var būt ierobežota līdz zemākajam iestatījumam.",
|
||||
"dataChannelClosedDescriptionWithAudio": "Savienojuma kanāls nedarbojas, tāpēc var rasties audio un video traucējumi.",
|
||||
"dataChannelClosedWithAudio": "Audio un video kvalitāte var būt traucēta",
|
||||
"disabledIframe": "Iegulšana ir paredzēta tikai demonstrācijas nolūkiem, tāpēc šis zvans tiks atvienots pēc {{timeout}} minūtēm.",
|
||||
"disabledIframeSecondary": "{{domain}} iegulšana ir paredzēta tikai demonstrācijas nolūkiem, tāpēc šis zvans tiks atvienots pēc {{timeout}} minūtēm. Lūdzu, izmantojiet <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi kā Pakalpojums</a> produkcijas iegulšanai!",
|
||||
"disconnected": "savienojums pārtraukts",
|
||||
@@ -832,7 +836,7 @@
|
||||
"breakoutRooms": "Grupu istabas",
|
||||
"invite": "Uzaicināt",
|
||||
"moreModerationActions": "Vairāk moderēšanas iespēju",
|
||||
"moreModerationControls": "Vairāk moderēšanas kontroļu",
|
||||
"moreModerationControls": "Vairāk moderēšanas iespēju",
|
||||
"moreParticipantOptions": "Vairāk dalībnieku iespēju",
|
||||
"mute": "Apklusināt",
|
||||
"muteAll": "Apklusināt visus",
|
||||
@@ -1482,6 +1486,10 @@
|
||||
"notification": {
|
||||
"demoteDescription": "{{actor}} pārveidoja par apmeklētāju, paceliet roku, lai piedalītos",
|
||||
"description": "Paceliet roku, lai piedalītos",
|
||||
"noMainParticipantsDescription": "Dalībniekam ir jāsāk sapulce. Lūdzu, pēc brīža mēģiniet vēlreiz.",
|
||||
"noMainParticipantsTitle": "Šī sapulce vēl nav sākusies.",
|
||||
"noVisitorLobby": "Jūs nevarat pievienoties, kamēr sapulcei ir iespējots vestibils.",
|
||||
"notAllowedPromotion": "Dalībniekam vispirms ir jāatļauj jūsu pieprasījums.",
|
||||
"title": "Jūs esat sapulces apmeklētājs"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -822,8 +822,8 @@
|
||||
},
|
||||
"participantsPane": {
|
||||
"actions": {
|
||||
"admit": "Признать",
|
||||
"admitAll": "Признать все",
|
||||
"admit": "Разрешить",
|
||||
"admitAll": "Разрешить всем",
|
||||
"allow": "Разрешить",
|
||||
"allowVideo": "Разрешить видео",
|
||||
"askUnmute": "Попросить разрешение включить микрофон",
|
||||
@@ -837,7 +837,7 @@
|
||||
"mute": "Выключить звук",
|
||||
"muteAll": "Выключить звук у всех",
|
||||
"muteEveryoneElse": "Выключить микрофон у остальных",
|
||||
"reject": "Отказать",
|
||||
"reject": "Отклонить",
|
||||
"stopEveryonesVideo": "Выключить у всех камеру",
|
||||
"stopVideo": "Остановить видео",
|
||||
"unblockEveryoneMicCamera": "Разблокировать у всех микрофон и камеру",
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
"privateNotice": "{{recipient}} için özel mesaj",
|
||||
"sendButton": "Gönder",
|
||||
"smileysPanel": "Emoji paneli",
|
||||
"systemDisplayName": "Sistem",
|
||||
"tabs": {
|
||||
"chat": "Sohbet",
|
||||
"polls": "Anket"
|
||||
@@ -735,6 +736,8 @@
|
||||
"connectedTwoMembers": "{{first}} ve {{second}} toplantıya katıldı",
|
||||
"dataChannelClosed": "Video kalitesi bozuldu",
|
||||
"dataChannelClosedDescription": "Köprü kanalının bağlantısı kesildi ve bu nedenle video kalitesi en düşük ayarla sınırlandı.",
|
||||
"dataChannelClosedDescriptionWithAudio": "Köprü kanalı kapalı olduğu için ses ve video kesintileri yaşanabilir.",
|
||||
"dataChannelClosedWithAudio": "Ses ve video kalitesi etkilenebilir.",
|
||||
"disabledIframe": "Yerleştirme yalnızca demo amaçlı olduğundan bu çağrının bağlantısı {{timeout}} dakika içinde kesilecek.",
|
||||
"disabledIframeSecondary": "{{domain}} alanının yerleştirilmesi yalnızca demo amaçlı olduğundan bu çağrının bağlantısı {{timeout}} dakika içinde kesilecektir. Üretim yerleştirme için lütfen <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Hizmet olarak Jitsi</a>'yi kullanın!",
|
||||
"disconnected": "bağlantı kesildi",
|
||||
|
||||
@@ -263,6 +263,7 @@
|
||||
"Share": "Chia sẻ",
|
||||
"Submit": "Đăng ký",
|
||||
"WaitForHostMsg": "Cuộc họp chưa được bắt đầu. Nếu bạn là quản trị viên vui lòng xác thực. Nếu không, vui lòng đợi quản trị viên.",
|
||||
"WaitingForHost": "Đang đợi quản trị viên...",
|
||||
"WaitingForHostButton": "Chờ người điều hành",
|
||||
"WaitingForHostTitle": "Chờ người điều hành ...",
|
||||
"Yes": "Có",
|
||||
|
||||
@@ -264,6 +264,7 @@
|
||||
"Share": "Share",
|
||||
"Submit": "Submit",
|
||||
"WaitForHostMsg": "The conference has not yet started because no moderators have yet arrived. If you'd like to become a moderator please log-in. Otherwise, please wait.",
|
||||
"WaitForHostNoAuthMsg": "The conference has not yet started because no moderators have yet arrived. Please wait.",
|
||||
"WaitingForHostButton": "Wait for moderator",
|
||||
"WaitingForHostTitle": "Waiting for a moderator...",
|
||||
"Yes": "Yes",
|
||||
@@ -319,6 +320,7 @@
|
||||
"embedMeeting": "Embed meeting",
|
||||
"enterDisplayName": "Enter your name",
|
||||
"error": "Error",
|
||||
"errorRoomCreationRestriction": "You tried to join too quickly, please come back in a bit.",
|
||||
"gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
|
||||
"grantModeratorDialog": "Are you sure you want to grant moderator rights to {{participantName}}?",
|
||||
"grantModeratorTitle": "Grant moderator rights",
|
||||
@@ -734,8 +736,10 @@
|
||||
"connectedOneMember": "{{name}} joined the meeting",
|
||||
"connectedThreePlusMembers": "{{name}} and many others joined the meeting",
|
||||
"connectedTwoMembers": "{{first}} and {{second}} joined the meeting",
|
||||
"dataChannelClosed": "Video quality impaired",
|
||||
"dataChannelClosedDescription": "The bridge channel has been disconnected and thus video quality is limited to its lowest setting.",
|
||||
"dataChannelClosed": "Video quality may be impaired",
|
||||
"dataChannelClosedDescription": "The bridge channel is down and thus video quality may be limited to its lowest setting.",
|
||||
"dataChannelClosedDescriptionWithAudio": "The bridge channel is down and thus disruptions to audio and video may occur.",
|
||||
"dataChannelClosedWithAudio": "Audio and video quality may be impaired",
|
||||
"disabledIframe": "Embedding is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes.",
|
||||
"disabledIframeSecondary": "Embedding {{domain}} is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes. Please use <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> for production embedding!",
|
||||
"disconnected": "disconnected",
|
||||
@@ -861,6 +865,8 @@
|
||||
"pinnedParticipant": "The participant is pinned",
|
||||
"polls": {
|
||||
"answer": {
|
||||
"edit": "Edit",
|
||||
"send": "Send",
|
||||
"skip": "Skip",
|
||||
"submit": "Submit"
|
||||
},
|
||||
@@ -874,6 +880,7 @@
|
||||
"pollQuestion": "Poll Question",
|
||||
"questionPlaceholder": "Ask a question",
|
||||
"removeOption": "Remove option",
|
||||
"save": "Save",
|
||||
"send": "Send"
|
||||
},
|
||||
"errors": {
|
||||
@@ -1483,6 +1490,10 @@
|
||||
"notification": {
|
||||
"demoteDescription": "Sent here by {{actor}}, raise your hand to participate",
|
||||
"description": "To participate raise your hand",
|
||||
"noMainParticipantsDescription": "A participant needs to start the meeting. Please try again in a bit.",
|
||||
"noMainParticipantsTitle": "This meeting hasn’t started yet.",
|
||||
"noVisitorLobby": "You cannot join while there is a lobby enabled for the meeting.",
|
||||
"notAllowedPromotion": "A participant needs to allow your request first.",
|
||||
"title": "You are a visitor in the meeting"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"hi": "Hindi",
|
||||
"hmn": "Hmong",
|
||||
"hr": "Croatian",
|
||||
"hsb": "Upper Sorbian",
|
||||
"ht": "Haitian Creole",
|
||||
"hu": "Hungarian",
|
||||
"hy": "Armenian",
|
||||
|
||||
10
metadata/ru/full_description.txt
Normal file
10
metadata/ru/full_description.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
Jitsi Meet позволяет вам оставаться на связи со всеми вашими командами, будь то семья, друзья или коллеги. Мгновенные видеоконференции, эффективно адаптируемые к вашим нуждам.
|
||||
|
||||
* Неограниченные пользователи: нет искусственных ограничений на количество пользователей или участников конференции. Мощность сервера и пропускная способность являются единственными ограничивающими факторами.
|
||||
* Учетная запись не требуется.
|
||||
* Закрытые комнаты: управляйте доступом к вашим конференциям с помощью пароля.
|
||||
* Зашифровано по умолчанию.
|
||||
* Высокое качество: аудио и видео доставляются с чёткостью и возможностями кодеков Opus и VP8.
|
||||
* Работает в веб браузере: вашим друзьям не требуются загрузка чтобы присоединиться к разговору. Jitsi Meet работает непосредственно в их браузерах. Чтобы начать просто поделитесь своей ссылкой на конференцию с другими.
|
||||
* 100% открытый исходный код: поддерживается удивительными сообществами со всего мира. И вашими друзьями из 8x8.
|
||||
* Приглашение по красивому адресу ссылки: вы можете встретиться с легко запоминаемым https://MySite.com/OurConf по своему выбору вместо случайных цифр.
|
||||
1
metadata/ru/short_description.txt
Normal file
1
metadata/ru/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Защищённые, простые и масштабируемые видеоконференции с качественным видео
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
import { LOCAL_PARTICIPANT_DEFAULT_ID } from '../../react/features/base/participants/constants';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getNormalizedDisplayName,
|
||||
getParticipantById,
|
||||
getScreenshareParticipantIds,
|
||||
getVirtualScreenshareParticipantByOwnerId,
|
||||
@@ -76,6 +77,7 @@ import { setMediaEncryptionKey, toggleE2EE } from '../../react/features/e2ee/act
|
||||
import {
|
||||
addStageParticipant,
|
||||
resizeFilmStrip,
|
||||
setFilmstripVisible,
|
||||
setVolume,
|
||||
togglePinStageParticipant
|
||||
} from '../../react/features/filmstrip/actions.web';
|
||||
@@ -199,7 +201,7 @@ function initCommands() {
|
||||
},
|
||||
'display-name': displayName => {
|
||||
sendAnalytics(createApiEvent('display.name.changed'));
|
||||
APP.conference.changeLocalDisplayName(displayName);
|
||||
APP.store.dispatch(updateSettings({ displayName: getNormalizedDisplayName(displayName) }));
|
||||
},
|
||||
'local-subject': localSubject => {
|
||||
sendAnalytics(createApiEvent('local.subject.changed'));
|
||||
@@ -376,7 +378,9 @@ function initCommands() {
|
||||
},
|
||||
'toggle-film-strip': () => {
|
||||
sendAnalytics(createApiEvent('film.strip.toggled'));
|
||||
APP.UI.toggleFilmstrip();
|
||||
const { visible } = APP.store.getState()['features/filmstrip'];
|
||||
|
||||
APP.store.dispatch(setFilmstripVisible(!visible));
|
||||
},
|
||||
|
||||
/*
|
||||
@@ -2134,6 +2138,21 @@ class API {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) the conference
|
||||
* start time.
|
||||
*
|
||||
* @param {number} timestamp - Timestamp conference was created.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyConferenceCreatedTimestamp(timestamp) {
|
||||
this._sendEvent({
|
||||
name: 'conference-created-timestamp',
|
||||
timestamp
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notify the external application (if API is enabled) if the connection type changed.
|
||||
*
|
||||
|
||||
1
modules/API/external/external_api.js
vendored
1
modules/API/external/external_api.js
vendored
@@ -108,6 +108,7 @@ const events = {
|
||||
'camera-error': 'cameraError',
|
||||
'chat-updated': 'chatUpdated',
|
||||
'compute-pressure-changed': 'computePressureChanged',
|
||||
'conference-created-timestamp': 'conferenceCreatedTimestamp',
|
||||
'content-sharing-participants-changed': 'contentSharingParticipantsChanged',
|
||||
'data-channel-closed': 'dataChannelClosed',
|
||||
'data-channel-opened': 'dataChannelOpened',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
const UI = {};
|
||||
|
||||
import Logger from '@jitsi/logger';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import {
|
||||
conferenceWillInit
|
||||
@@ -13,7 +12,6 @@ import { isMobileBrowser } from '../../react/features/base/environment/utils';
|
||||
import { setColorAlpha } from '../../react/features/base/util/helpers';
|
||||
import { sanitizeUrl } from '../../react/features/base/util/uri';
|
||||
import { setDocumentUrl } from '../../react/features/etherpad/actions';
|
||||
import { setFilmstripVisible } from '../../react/features/filmstrip/actions.any';
|
||||
import {
|
||||
setNotificationsEnabled,
|
||||
showNotification
|
||||
@@ -25,7 +23,6 @@ import {
|
||||
setToolboxEnabled,
|
||||
showToolbox
|
||||
} from '../../react/features/toolbox/actions.web';
|
||||
import UIEvents from '../../service/UI/UIEvents';
|
||||
|
||||
import EtherpadManager from './etherpad/Etherpad';
|
||||
import UIUtil from './util/UIUtil';
|
||||
@@ -33,22 +30,8 @@ import VideoLayout from './videolayout/VideoLayout';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
UI.eventEmitter = eventEmitter;
|
||||
|
||||
let etherpadManager;
|
||||
|
||||
const UIListeners = new Map([
|
||||
[
|
||||
UIEvents.ETHERPAD_CLICKED,
|
||||
() => etherpadManager && etherpadManager.toggleEtherpad()
|
||||
], [
|
||||
UIEvents.TOGGLE_FILMSTRIP,
|
||||
() => UI.toggleFilmstrip()
|
||||
]
|
||||
]);
|
||||
|
||||
/**
|
||||
* Indicates if we're currently in full screen mode.
|
||||
*
|
||||
@@ -96,10 +79,11 @@ UI.start = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup some UI event listeners.
|
||||
* Handles etherpad click.
|
||||
*/
|
||||
UI.registerListeners
|
||||
= () => UIListeners.forEach((value, key) => UI.addListener(key, value));
|
||||
UI.onEtherpadClicked = function() {
|
||||
etherpadManager && etherpadManager.toggleEtherpad();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -143,7 +127,7 @@ UI.initEtherpad = name => {
|
||||
}
|
||||
logger.log('Etherpad is enabled');
|
||||
|
||||
etherpadManager = new EtherpadManager(eventEmitter);
|
||||
etherpadManager = new EtherpadManager();
|
||||
|
||||
const url = new URL(name, etherpadBaseUrl);
|
||||
|
||||
@@ -197,15 +181,6 @@ UI.updateUserStatus = (user, status) => {
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles filmstrip.
|
||||
*/
|
||||
UI.toggleFilmstrip = function() {
|
||||
const { visible } = APP.store.getState()['features/filmstrip'];
|
||||
|
||||
APP.store.dispatch(setFilmstripVisible(!visible));
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets muted video state for participant
|
||||
*/
|
||||
@@ -219,33 +194,6 @@ UI.setVideoMuted = function(id) {
|
||||
|
||||
UI.updateLargeVideo = (id, forceUpdate) => VideoLayout.updateLargeVideo(id, forceUpdate);
|
||||
|
||||
/**
|
||||
* Adds a listener that would be notified on the given type of event.
|
||||
*
|
||||
* @param type the type of the event we're listening for
|
||||
* @param listener a function that would be called when notified
|
||||
*/
|
||||
UI.addListener = function(type, listener) {
|
||||
eventEmitter.on(type, listener);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the all listeners for all events.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.removeAllListeners = function() {
|
||||
eventEmitter.removeAllListeners();
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits the event of given type by specifying the parameters in options.
|
||||
*
|
||||
* @param type the type of the event we're emitting
|
||||
* @param options the parameters for the event
|
||||
*/
|
||||
UI.emitEvent = (type, ...options) => eventEmitter.emit(type, ...options);
|
||||
|
||||
// Used by torture.
|
||||
UI.showToolbar = timeout => APP.store.dispatch(showToolbox(timeout));
|
||||
|
||||
|
||||
@@ -138,8 +138,7 @@ export default class EtherpadManager {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(eventEmitter) {
|
||||
this.eventEmitter = eventEmitter;
|
||||
constructor() {
|
||||
this.etherpad = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { createScreenSharingIssueEvent } from '../../../react/features/analytics
|
||||
import { sendAnalytics } from '../../../react/features/analytics/functions';
|
||||
import Avatar from '../../../react/features/base/avatar/components/Avatar';
|
||||
import theme from '../../../react/features/base/components/themes/participantsPaneTheme.json';
|
||||
import { getSsrcRewritingFeatureFlag } from '../../../react/features/base/config/functions.any';
|
||||
import i18next from '../../../react/features/base/i18n/i18next';
|
||||
import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
|
||||
import { VIDEO_TYPE } from '../../../react/features/base/media/constants';
|
||||
@@ -221,10 +222,10 @@ export default class LargeVideoManager {
|
||||
|
||||
this.updateInProcess = true;
|
||||
|
||||
// Include hide()/fadeOut only if we're switching between users
|
||||
// eslint-disable-next-line eqeqeq
|
||||
// Include hide()/fadeOut if we're switching between users or between different sources of the same user.
|
||||
const container = this.getCurrentContainer();
|
||||
const isUserSwitch = this.newStreamData.id !== container.id;
|
||||
const isUserSwitch = container.id !== this.newStreamData.id
|
||||
|| container.stream?.getSourceName() !== this.newStreamData.stream?.getSourceName();
|
||||
const preUpdate = isUserSwitch ? container.hide() : Promise.resolve();
|
||||
|
||||
preUpdate.then(() => {
|
||||
|
||||
@@ -501,8 +501,7 @@ export class VideoContainer extends LargeContainer {
|
||||
* @param {string} videoType video type
|
||||
*/
|
||||
setStream(userID, stream, videoType) {
|
||||
this.userId = userID;
|
||||
if (this.stream === stream && !stream?.forceStreamToReattach) {
|
||||
if (this.userId === userID && this.stream === stream && !stream?.forceStreamToReattach) {
|
||||
logger.debug(`SetStream on the large video for user ${userID} ignored: the stream is not changed!`);
|
||||
|
||||
// Handles the use case for the remote participants when the
|
||||
@@ -516,6 +515,8 @@ export class VideoContainer extends LargeContainer {
|
||||
return;
|
||||
}
|
||||
|
||||
this.userId = userID;
|
||||
|
||||
if (stream?.forceStreamToReattach) {
|
||||
delete stream.forceStreamToReattach;
|
||||
}
|
||||
@@ -540,9 +541,8 @@ export class VideoContainer extends LargeContainer {
|
||||
logger.error(`Attaching the remote track ${stream} to large video has failed with `, error);
|
||||
});
|
||||
|
||||
// Ensure large video gets play() called on it when a new stream is attached to it. This is necessary in the
|
||||
// case of Safari as autoplay doesn't kick-in automatically on Safari 15 and newer versions.
|
||||
browser.isWebKitBased() && this._play();
|
||||
// Ensure large video gets play() called on it when a new stream is attached to it.
|
||||
this._play();
|
||||
|
||||
const flipX = stream.isLocal() && this.localFlipX && !this.isScreenSharing();
|
||||
|
||||
|
||||
3481
package-lock.json
generated
3481
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@@ -23,7 +23,7 @@
|
||||
"@giphy/js-fetch-api": "4.7.1",
|
||||
"@giphy/react-components": "6.8.1",
|
||||
"@giphy/react-native-sdk": "2.3.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.17/jitsi-excalidraw-0.0.17.tgz",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
|
||||
"@jitsi/js-utils": "2.2.1",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
@@ -36,7 +36,6 @@
|
||||
"@react-native-community/netinfo": "11.1.0",
|
||||
"@react-native-community/slider": "4.4.3",
|
||||
"@react-native-google-signin/google-signin": "10.1.0",
|
||||
"@react-native/metro-config": "0.72.9",
|
||||
"@react-navigation/bottom-tabs": "6.5.8",
|
||||
"@react-navigation/elements": "1.3.18",
|
||||
"@react-navigation/material-top-tabs": "6.6.3",
|
||||
@@ -67,7 +66,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1812.0.0+2eddb859/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1830.0.0+5a14bd43/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -82,7 +81,7 @@
|
||||
"react-focus-on": "3.8.1",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "0.72.9",
|
||||
"react-native": "0.72.14",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
@@ -106,8 +105,8 @@
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-video": "6.0.0-alpha.11",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-webrtc": "118.0.7",
|
||||
"react-native-webview": "13.5.1",
|
||||
"react-native-webrtc": "124.0.0",
|
||||
"react-native-webview": "13.8.7",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-textarea-autosize": "8.3.0",
|
||||
@@ -133,6 +132,7 @@
|
||||
"@babel/preset-env": "7.21.5",
|
||||
"@babel/preset-react": "7.16.0",
|
||||
"@jitsi/eslint-config": "4.1.10",
|
||||
"@react-native/metro-config": "0.72.12",
|
||||
"@types/amplitude-js": "8.16.5",
|
||||
"@types/audioworklet": "0.0.29",
|
||||
"@types/dom-screen-wake-lock": "1.0.1",
|
||||
@@ -181,14 +181,14 @@
|
||||
"webpack": "5.76.0",
|
||||
"webpack-bundle-analyzer": "4.4.2",
|
||||
"webpack-cli": "4.9.0",
|
||||
"webpack-dev-server": "4.7.3"
|
||||
"webpack-dev-server": "4.15.2"
|
||||
},
|
||||
"overrides": {
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
"node": ">=20.0.0",
|
||||
"npm": ">=10.0.0"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
|
||||
@@ -95,7 +95,7 @@ https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native
|
||||
|
||||
|
||||
## Using JWT tokens
|
||||
- If you are planning to use tokens or another domain you can do that by updating the following props, as shown below.
|
||||
- If you are planning to use tokens or another domain, you can do that by updating the following props, as shown below.
|
||||
- For example:
|
||||
```javascript
|
||||
<JitsiMeeting
|
||||
@@ -105,7 +105,7 @@ https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native
|
||||
```
|
||||
|
||||
## Using custom overflow menu buttons
|
||||
- If you are planning to use tokens or another domain you can do that by updating the following props, as shown below.
|
||||
- If you are planning to add custom overflow menu buttons, you can do that by updating the ```config``` prop, as shown below.
|
||||
- For example:
|
||||
```javascript
|
||||
<JitsiMeeting
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"@amplitude/react-native": "0.0.0",
|
||||
"@braintree/sanitize-url": "0.0.0",
|
||||
"@giphy/react-native-sdk": "0.0.0",
|
||||
"@react-native/metro-config": "0.0.0",
|
||||
"@react-native/metro-config": "*",
|
||||
"@react-native-async-storage/async-storage": "0.0.0",
|
||||
"@react-native-community/clipboard": "0.0.0",
|
||||
"@react-native-community/netinfo": "0.0.0",
|
||||
|
||||
4
react-native-sdk/prepare_sdk.js
vendored
4
react-native-sdk/prepare_sdk.js
vendored
@@ -114,10 +114,6 @@ copyFolderRecursiveSync(
|
||||
'../react',
|
||||
'.'
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
'../service',
|
||||
'.'
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
'../ios/sdk/sdk.xcodeproj',
|
||||
'./ios'
|
||||
|
||||
5
react-native-sdk/update_dependencies.js
vendored
5
react-native-sdk/update_dependencies.js
vendored
@@ -21,6 +21,11 @@ function updateDependencies() {
|
||||
|
||||
for (const key in RNSDKpackageJSON.peerDependencies) {
|
||||
if (!packageJSON.dependencies.hasOwnProperty(key)) {
|
||||
|
||||
if (packageJSON.devDependencies.hasOwnProperty('@react-native/metro-config')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
|
||||
updated = true;
|
||||
}
|
||||
|
||||
@@ -331,15 +331,17 @@ export function createNetworkInfoEvent({ isOnline, networkType, details }:
|
||||
/**
|
||||
* Creates a "not allowed error" event.
|
||||
*
|
||||
* @param {string} type - The type of the error.
|
||||
* @param {string} reason - The reason for the error.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createNotAllowedErrorEvent(reason: string) {
|
||||
export function createNotAllowedErrorEvent(type: string, reason: string) {
|
||||
return {
|
||||
action: 'not.allowed.error',
|
||||
attributes: {
|
||||
reason
|
||||
reason,
|
||||
type
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
_alternativeCancelText?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to hide the login button.
|
||||
*/
|
||||
_hideLoginButton?: boolean;
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
@@ -80,12 +85,13 @@ class WaitForOwnerDialog extends PureComponent<IProps> {
|
||||
this.props._alternativeCancelText ? 'dialog.WaitingForHostButton' : 'dialog.Cancel' }}
|
||||
disableBackdropClose = { true }
|
||||
hideCloseButton = { true }
|
||||
ok = {{ translationKey: 'dialog.IamHost' }}
|
||||
ok = { this.props._hideLoginButton ? { hidden: true,
|
||||
disabled: true } : { translationKey: 'dialog.IamHost' } }
|
||||
onCancel = { this._onCancelWaitForOwner }
|
||||
onSubmit = { this._onIAmHost }
|
||||
titleKey = { t('dialog.WaitingForHostTitle') }>
|
||||
<span>
|
||||
{ t('dialog.WaitForHostMsg') }
|
||||
{ this.props._hideLoginButton ? t('dialog.WaitForHostNoAuthMsg') : t('dialog.WaitForHostMsg') }
|
||||
</span>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -102,9 +108,11 @@ class WaitForOwnerDialog extends PureComponent<IProps> {
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
const { membersOnly, lobbyWaitingForHost } = state['features/base/conference'];
|
||||
const { hideLoginButton } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_alternativeCancelText: membersOnly && lobbyWaitingForHost
|
||||
_alternativeCancelText: membersOnly && lobbyWaitingForHost,
|
||||
_hideLoginButton: hideLoginButton
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -72,8 +72,7 @@ export const _getTokenAuthState = (
|
||||
// @ts-ignore
|
||||
state['config.startWithVideoMuted'] = true;
|
||||
}
|
||||
|
||||
const params = parseURLParams(locationURL, true);
|
||||
const params = parseURLParams(locationURL);
|
||||
|
||||
for (const key of Object.keys(params)) {
|
||||
// we allow only config and interfaceConfig overrides in the state
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @ts-expect-error
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { createAudioOnlyChangedEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IStore } from '../../app/types';
|
||||
@@ -33,7 +31,7 @@ export function setAudioOnly(audioOnly: boolean) {
|
||||
if (typeof APP !== 'undefined') {
|
||||
// TODO This should be a temporary solution that lasts only until video
|
||||
// tracks and all ui is moved into react/redux on the web.
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_AUDIO_ONLY, audioOnly);
|
||||
APP.conference.onToggleAudioOnly();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @ts-expect-error
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
@@ -12,14 +10,12 @@ import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
|
||||
import { hasAvailableDevices } from '../devices/functions.any';
|
||||
import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
|
||||
import {
|
||||
gumPending,
|
||||
setAudioMuted,
|
||||
setAudioUnmutePermissions,
|
||||
setVideoMuted,
|
||||
setVideoUnmutePermissions
|
||||
} from '../media/actions';
|
||||
import { MEDIA_TYPE, VIDEO_MUTISM_AUTHORITY } from '../media/constants';
|
||||
import { IGUMPendingState } from '../media/types';
|
||||
import { MEDIA_TYPE, MediaType } from '../media/constants';
|
||||
import {
|
||||
dominantSpeakerChanged,
|
||||
participantKicked,
|
||||
@@ -74,6 +70,7 @@ import {
|
||||
SET_START_REACTIONS_MUTED,
|
||||
UPDATE_CONFERENCE_METADATA
|
||||
} from './actionTypes';
|
||||
import { setupVisitorStartupMedia } from './actions';
|
||||
import {
|
||||
AVATAR_URL_COMMAND,
|
||||
EMAIL_COMMAND,
|
||||
@@ -1062,47 +1059,37 @@ export function redirect(vnode: string, focusJid: string, username: string) {
|
||||
.then(() => dispatch(conferenceWillInit()))
|
||||
.then(() => dispatch(connect()))
|
||||
.then(() => {
|
||||
// Clear the gum pending state in case we have set it to pending since we are starting the
|
||||
// conference without tracks.
|
||||
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
|
||||
const media: Array<MediaType> = [];
|
||||
|
||||
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
|
||||
if (typeof APP !== 'undefined') {
|
||||
if (!vnode) {
|
||||
const state = getState();
|
||||
const { enableMediaOnPromote = {} } = state['features/base/config'].visitors ?? {};
|
||||
const { audio = false, video = false } = enableMediaOnPromote;
|
||||
if (!vnode) {
|
||||
const state = getState();
|
||||
const { enableMediaOnPromote = {} } = state['features/base/config'].visitors ?? {};
|
||||
const { audio = false, video = false } = enableMediaOnPromote;
|
||||
|
||||
if (audio) {
|
||||
const { available, muted, unmuteBlocked } = state['features/base/media'].audio;
|
||||
const { startSilent } = state['features/base/config'];
|
||||
if (audio) {
|
||||
const { available, muted, unmuteBlocked } = state['features/base/media'].audio;
|
||||
const { startSilent } = state['features/base/config'];
|
||||
|
||||
// do not unmute the user if he was muted before (on the prejoin, the config
|
||||
// or URL param, etc.)
|
||||
if (!unmuteBlocked && !muted && !startSilent && available) {
|
||||
dispatch(setAudioMuted(false, true));
|
||||
|
||||
// // FIXME: The old conference logic still relies on this event being emitted.
|
||||
typeof APP === 'undefined' || APP.UI.emitEvent(UIEvents.AUDIO_MUTED, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (video) {
|
||||
const { muted, unmuteBlocked } = state['features/base/media'].video;
|
||||
|
||||
// do not unmute the user if he was muted before (on the prejoin, the config, URL param or
|
||||
// audo only, etc)
|
||||
if (!unmuteBlocked && !muted && hasAvailableDevices(state, 'videoInput')) {
|
||||
dispatch(setVideoMuted(false, VIDEO_MUTISM_AUTHORITY.USER, true));
|
||||
|
||||
// // FIXME: The old conference logic still relies on this event being emitted.
|
||||
typeof APP === 'undefined' || APP.UI.emitEvent(UIEvents.VIDEO_MUTED, false);
|
||||
}
|
||||
// do not unmute the user if he was muted before (on the prejoin, the config
|
||||
// or URL param, etc.)
|
||||
if (!unmuteBlocked && !muted && !startSilent && available) {
|
||||
media.push(MEDIA_TYPE.AUDIO);
|
||||
}
|
||||
}
|
||||
|
||||
APP.conference.startConference([]);
|
||||
if (video) {
|
||||
const { muted, unmuteBlocked } = state['features/base/media'].video;
|
||||
|
||||
// do not unmute the user if he was muted before (on the prejoin, the config, URL param or
|
||||
// audo only, etc)
|
||||
if (!unmuteBlocked && !muted && hasAvailableDevices(state, 'videoInput')) {
|
||||
media.push(MEDIA_TYPE.VIDEO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(setupVisitorStartupMedia(media));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
29
react/features/base/conference/actions.native.ts
Normal file
29
react/features/base/conference/actions.native.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { setAudioMuted, setVideoMuted } from '../media/actions';
|
||||
import { MEDIA_TYPE, MediaType, VIDEO_MUTISM_AUTHORITY } from '../media/constants';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Starts audio and/or video for the visitor.
|
||||
*
|
||||
* @param {Array<MediaType>} mediaTypes - The media types that need to be started.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setupVisitorStartupMedia(mediaTypes: Array<MediaType>) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
if (!mediaTypes || !Array.isArray(mediaTypes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mediaTypes.forEach(mediaType => {
|
||||
switch (mediaType) {
|
||||
case MEDIA_TYPE.AUDIO:
|
||||
dispatch(setAudioMuted(false, true));
|
||||
break;
|
||||
case MEDIA_TYPE.VIDEO:
|
||||
dispatch(setVideoMuted(false, VIDEO_MUTISM_AUTHORITY.USER, true));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
31
react/features/base/conference/actions.web.ts
Normal file
31
react/features/base/conference/actions.web.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { gumPending } from '../media/actions';
|
||||
import { MEDIA_TYPE, MediaType } from '../media/constants';
|
||||
import { IGUMPendingState } from '../media/types';
|
||||
import { createAndAddInitialAVTracks } from '../tracks/actions.web';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Starts audio and/or video for the visitor.
|
||||
*
|
||||
* @param {Array<MediaType>} media - The media types that need to be started.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setupVisitorStartupMedia(media: Array<MediaType>) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
// Clear the gum pending state in case we have set it to pending since we are starting the
|
||||
// conference without tracks.
|
||||
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
|
||||
|
||||
if (media && Array.isArray(media) && media.length > 0) {
|
||||
dispatch(createAndAddInitialAVTracks(media));
|
||||
}
|
||||
|
||||
// FIXME: The name of the function doesn't fit the startConference execution but another PR will removes
|
||||
// this and calls startConference based on the connection status. This will stay here temporary.
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.conference.startConference([]);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -165,6 +165,10 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
const result = next(action);
|
||||
const { enableForcedReload } = getState()['features/base/config'];
|
||||
|
||||
if (LocalRecordingManager.isRecordingLocally()) {
|
||||
dispatch(stopLocalVideoRecording());
|
||||
}
|
||||
|
||||
// Handle specific failure reasons.
|
||||
switch (error.name) {
|
||||
case JitsiConferenceErrors.CONFERENCE_RESTARTED: {
|
||||
@@ -220,9 +224,30 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
break;
|
||||
}
|
||||
case JitsiConferenceErrors.NOT_ALLOWED_ERROR: {
|
||||
const [ msg ] = error.params;
|
||||
const [ type, msg ] = error.params;
|
||||
|
||||
let descriptionKey;
|
||||
let titleKey = 'dialog.tokenAuthFailed';
|
||||
|
||||
if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.NO_MAIN_PARTICIPANTS) {
|
||||
descriptionKey = 'visitors.notification.noMainParticipantsDescription';
|
||||
titleKey = 'visitors.notification.noMainParticipantsTitle';
|
||||
} else if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.NO_VISITORS_LOBBY) {
|
||||
descriptionKey = 'visitors.notification.noVisitorLobby';
|
||||
} else if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.PROMOTION_NOT_ALLOWED) {
|
||||
descriptionKey = 'visitors.notification.notAllowedPromotion';
|
||||
} else if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.ROOM_CREATION_RESTRICTION) {
|
||||
descriptionKey = 'dialog.errorRoomCreationRestriction';
|
||||
}
|
||||
|
||||
dispatch(showErrorNotification({
|
||||
descriptionKey,
|
||||
hideErrorSupportLink: true,
|
||||
titleKey
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
|
||||
sendAnalytics(createNotAllowedErrorEvent(type, msg));
|
||||
|
||||
sendAnalytics(createNotAllowedErrorEvent(msg));
|
||||
break;
|
||||
}
|
||||
case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
CONFERENCE_TIMESTAMP_CHANGED,
|
||||
CONFERENCE_WILL_JOIN,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
DATA_CHANNEL_CLOSED,
|
||||
DATA_CHANNEL_OPENED,
|
||||
LOCK_STATE_CHANGED,
|
||||
P2P_STATUS_CHANGED,
|
||||
SET_ASSUMED_BANDWIDTH_BPS,
|
||||
@@ -35,6 +37,7 @@ import { isRoomValid } from './functions';
|
||||
const DEFAULT_STATE = {
|
||||
assumedBandwidthBps: undefined,
|
||||
conference: undefined,
|
||||
dataChannelOpen: undefined,
|
||||
e2eeSupported: undefined,
|
||||
joining: undefined,
|
||||
leaving: undefined,
|
||||
@@ -146,6 +149,7 @@ export interface IConferenceState {
|
||||
authRequired?: IJitsiConference;
|
||||
conference?: IJitsiConference;
|
||||
conferenceTimestamp?: number;
|
||||
dataChannelOpen?: boolean;
|
||||
e2eeSupported?: boolean;
|
||||
error?: Error;
|
||||
followMeEnabled?: boolean;
|
||||
@@ -219,6 +223,12 @@ ReducerRegistry.register<IConferenceState>('features/base/conference',
|
||||
case CONNECTION_WILL_CONNECT:
|
||||
return set(state, 'authRequired', undefined);
|
||||
|
||||
case DATA_CHANNEL_CLOSED:
|
||||
return set(state, 'dataChannelOpen', false);
|
||||
|
||||
case DATA_CHANNEL_OPENED:
|
||||
return set(state, 'dataChannelOpen', true);
|
||||
|
||||
case LOCK_STATE_CHANGED:
|
||||
return _lockStateChanged(state, action);
|
||||
|
||||
|
||||
@@ -396,6 +396,7 @@ export interface IConfig {
|
||||
hideDominantSpeakerBadge?: boolean;
|
||||
hideEmailInSettings?: boolean;
|
||||
hideLobbyButton?: boolean;
|
||||
hideLoginButton?: boolean;
|
||||
hideParticipantsStats?: boolean;
|
||||
hideRecordingLabel?: boolean;
|
||||
hosts?: {
|
||||
@@ -440,6 +441,7 @@ export interface IConfig {
|
||||
};
|
||||
localSubject?: string;
|
||||
locationURL?: URL;
|
||||
mainToolbarButtons?: Array<Array<string>>;
|
||||
maxFullResolutionParticipants?: number;
|
||||
microsoftApiApplicationClientID?: string;
|
||||
moderatedRoomServiceUrl?: string;
|
||||
@@ -542,7 +544,6 @@ export interface IConfig {
|
||||
assumeBandwidth?: boolean;
|
||||
disableE2EE?: boolean;
|
||||
dumpTranscript?: boolean;
|
||||
mobileXmppWsThreshold?: number;
|
||||
noAutoPlayVideo?: boolean;
|
||||
p2pTestMode?: boolean;
|
||||
skipInterimTranscriptions?: boolean;
|
||||
|
||||
@@ -81,6 +81,7 @@ export default [
|
||||
'debug',
|
||||
'debugAudioLevels',
|
||||
'deeplinking.disabled',
|
||||
'deeplinking.desktop.enabled',
|
||||
'defaultLocalDisplayName',
|
||||
'defaultRemoteDisplayName',
|
||||
'deploymentUrls',
|
||||
@@ -184,6 +185,7 @@ export default [
|
||||
'localRecording',
|
||||
'localSubject',
|
||||
'logging',
|
||||
'mainToolbarButtons',
|
||||
'maxFullResolutionParticipants',
|
||||
'mouseMoveCallbackInterval',
|
||||
'notifications',
|
||||
|
||||
@@ -126,13 +126,6 @@ export function constructOptions(state: IReduxState) {
|
||||
const { bosh, preferBosh, flags } = options;
|
||||
let { websocket } = options;
|
||||
|
||||
// TESTING: Only enable WebSocket for some percentage of users.
|
||||
if (websocket && navigator.product === 'ReactNative') {
|
||||
if ((Math.random() * 100) >= (options?.testing?.mobileXmppWsThreshold ?? 0)) {
|
||||
websocket = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (preferBosh) {
|
||||
websocket = undefined;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
// @ts-expect-error
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { IStore } from '../../app/types';
|
||||
import { processExternalDeviceRequest } from '../../device-selection/functions';
|
||||
import { showNotification, showWarningNotification } from '../../notifications/actions';
|
||||
@@ -161,7 +159,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
if (isPrejoinPageVisible(store.getState())) {
|
||||
store.dispatch(replaceAudioTrackById(action.deviceId));
|
||||
} else {
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_DEVICE_CHANGED, action.deviceId);
|
||||
APP.conference.onAudioDeviceChanged(action.deviceId);
|
||||
}
|
||||
break;
|
||||
case SET_VIDEO_INPUT_DEVICE: {
|
||||
@@ -174,7 +172,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
if (isPrejoinPageVisible(store.getState())) {
|
||||
store.dispatch(replaceVideoTrackById(action.deviceId));
|
||||
} else {
|
||||
APP.UI.emitEvent(UIEvents.VIDEO_DEVICE_CHANGED, action.deviceId);
|
||||
APP.conference.onVideoDeviceChanged(action.deviceId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import jwtDecode from 'jwt-decode';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { getLocalParticipant } from '../participants/functions';
|
||||
import { IParticipantFeatures } from '../participants/types';
|
||||
import { parseURLParams } from '../util/parseURLParams';
|
||||
|
||||
import { JWT_VALIDATION_ERRORS, MEET_FEATURES } from './constants';
|
||||
@@ -19,7 +20,11 @@ import logger from './logger';
|
||||
*/
|
||||
export function parseJWTFromURLParams(url: URL | typeof window.location = window.location) {
|
||||
// @ts-ignore
|
||||
return parseURLParams(url, true, 'search').jwt;
|
||||
const jwt = parseURLParams(url, false, 'hash').jwt;
|
||||
|
||||
// TODO: eventually remove the search param and only pull from the hash
|
||||
// @ts-ignore
|
||||
return jwt ? jwt : parseURLParams(url, true, 'search').jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,13 +50,46 @@ export function getJwtName(state: IReduxState) {
|
||||
*/
|
||||
export function isJwtFeatureEnabled(state: IReduxState, feature: string, ifNoToken = false, ifNotInFeatures = false) {
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
const { features } = getLocalParticipant(state) || {};
|
||||
|
||||
return isJwtFeatureEnabledStateless({
|
||||
jwt,
|
||||
localParticipantFeatures: features,
|
||||
feature,
|
||||
ifNoToken,
|
||||
ifNotInFeatures
|
||||
});
|
||||
}
|
||||
|
||||
interface IIsJwtFeatureEnabledStatelessParams {
|
||||
feature: string;
|
||||
ifNoToken?: boolean;
|
||||
ifNotInFeatures?: boolean;
|
||||
jwt?: string;
|
||||
localParticipantFeatures?: IParticipantFeatures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given JWT feature is enabled.
|
||||
*
|
||||
* @param {string | undefined} jwt - The jwt token.
|
||||
* @param {ILocalParticipant} localParticipantFeatures - The features of the local participant.
|
||||
* @param {string} feature - The feature we want to check.
|
||||
* @param {boolean} ifNoToken - Default value if there is no token.
|
||||
* @param {boolean} ifNotInFeatures - Default value if features prop exists but does not have the {@code feature}.
|
||||
* @returns {bolean}
|
||||
*/
|
||||
export function isJwtFeatureEnabledStateless({
|
||||
jwt,
|
||||
localParticipantFeatures: features,
|
||||
feature,
|
||||
ifNoToken = false,
|
||||
ifNotInFeatures = false
|
||||
}: IIsJwtFeatureEnabledStatelessParams) {
|
||||
if (!jwt) {
|
||||
return ifNoToken;
|
||||
}
|
||||
|
||||
const { features } = getLocalParticipant(state) || {};
|
||||
|
||||
// If `features` is undefined, act as if everything is enabled.
|
||||
if (typeof features === 'undefined') {
|
||||
return true;
|
||||
|
||||
@@ -43,18 +43,6 @@ export const KICK_PARTICIPANT = 'KICK_PARTICIPANT';
|
||||
*/
|
||||
export const MUTE_REMOTE_PARTICIPANT = 'MUTE_REMOTE_PARTICIPANT';
|
||||
|
||||
/**
|
||||
* Create an action for when the local participant's display name is updated.
|
||||
*
|
||||
* {
|
||||
* type: PARTICIPANT_DISPLAY_NAME_CHANGED,
|
||||
* id: string,
|
||||
* name: string
|
||||
* }
|
||||
*/
|
||||
export const PARTICIPANT_DISPLAY_NAME_CHANGED
|
||||
= 'PARTICIPANT_DISPLAY_NAME_CHANGED';
|
||||
|
||||
/**
|
||||
* Action to signal that ID of participant has changed. This happens when
|
||||
* local participant joins a new conference or quits one.
|
||||
|
||||
@@ -12,9 +12,9 @@ import { getCurrentConference } from '../conference/functions';
|
||||
import { ADD_PEOPLE_ENABLED } from '../flags/constants';
|
||||
import { getFeatureFlag } from '../flags/functions';
|
||||
import i18next from '../i18n/i18next';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
import { MEDIA_TYPE, MediaType, VIDEO_TYPE } from '../media/constants';
|
||||
import { toState } from '../redux/functions';
|
||||
import { getScreenShareTrack } from '../tracks/functions.any';
|
||||
import { getScreenShareTrack, isLocalTrackMuted } from '../tracks/functions.any';
|
||||
import { createDeferred } from '../util/helpers';
|
||||
|
||||
import {
|
||||
@@ -362,6 +362,42 @@ export function getRemoteParticipantCountWithFake(stateful: IStateful) {
|
||||
return participantsState.remote.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the muted state of the given media source for a given participant.
|
||||
*
|
||||
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's.
|
||||
* @param {IParticipant} participant - The participant entity.
|
||||
* @param {MediaType} mediaType - The media type.
|
||||
* @returns {boolean} - True its muted, false otherwise.
|
||||
*/
|
||||
export function getMutedStateByParticipantAndMediaType(
|
||||
stateful: IStateful,
|
||||
participant: IParticipant,
|
||||
mediaType: MediaType): boolean {
|
||||
const type = mediaType === MEDIA_TYPE.SCREENSHARE ? 'video' : mediaType;
|
||||
|
||||
if (participant.local) {
|
||||
const state = toState(stateful);
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
return isLocalTrackMuted(tracks, mediaType);
|
||||
}
|
||||
|
||||
const sources = participant.sources?.get(type);
|
||||
|
||||
if (!sources) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mediaType === MEDIA_TYPE.AUDIO) {
|
||||
return Array.from(sources.values())[0].muted;
|
||||
}
|
||||
const videoType = mediaType === MEDIA_TYPE.VIDEO ? VIDEO_TYPE.CAMERA : VIDEO_TYPE.SCREENSHARE;
|
||||
const source = Array.from(sources.values()).find(src => src.videoType === videoType);
|
||||
|
||||
return source?.muted ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a count of the known participants in the passed in redux state,
|
||||
* including fake participants.
|
||||
@@ -468,28 +504,28 @@ export function getScreenshareParticipantIds(stateful: IStateful): Array<string>
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list source name associated with a given remote participant and for the given media type.
|
||||
* Returns a list of source names associated with a given remote participant and for the given media type.
|
||||
*
|
||||
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
|
||||
* retrieve the state.
|
||||
* @param {string} id - The id of the participant whose source names are to be retrieved.
|
||||
* @param {string} mediaType - The type of source, audio or video.
|
||||
* @returns {Array<string>|undefined}
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
export function getSourceNamesByMediaType(
|
||||
export function getSourceNamesByMediaTypeAndParticipant(
|
||||
stateful: IStateful,
|
||||
id: string,
|
||||
mediaType: string): Array<string> | undefined {
|
||||
mediaType: string): Array<string> {
|
||||
const participant: IParticipant | undefined = getParticipantById(stateful, id);
|
||||
|
||||
if (!participant) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
const sources = participant.sources;
|
||||
|
||||
if (!sources) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.from(sources.get(mediaType) ?? new Map())
|
||||
@@ -497,6 +533,37 @@ export function getSourceNamesByMediaType(
|
||||
.map(s => s[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of source names associated with a given remote participant and for the given video type (only for
|
||||
* video sources).
|
||||
*
|
||||
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
|
||||
* retrieve the state.
|
||||
* @param {string} id - The id of the participant whose source names are to be retrieved.
|
||||
* @param {string} videoType - The type of video, camera or desktop.
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
export function getSourceNamesByVideoTypeAndParticipant(
|
||||
stateful: IStateful,
|
||||
id: string,
|
||||
videoType: string): Array<string> {
|
||||
const participant: IParticipant | undefined = getParticipantById(stateful, id);
|
||||
|
||||
if (!participant) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const sources = participant.sources;
|
||||
|
||||
if (!sources) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.from(sources.get(MEDIA_TYPE.VIDEO) ?? new Map())
|
||||
.filter(source => source[1].videoType === videoType && (videoType === VIDEO_TYPE.CAMERA || !source[1].muted))
|
||||
.map(s => s[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the presence status of a participant associated with the passed id.
|
||||
*
|
||||
|
||||
@@ -2,8 +2,6 @@ import i18n from 'i18next';
|
||||
import { batch } from 'react-redux';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
// @ts-expect-error
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { IStore } from '../../app/types';
|
||||
import { approveParticipant } from '../../av-moderation/actions';
|
||||
import { UPDATE_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
|
||||
@@ -41,7 +39,6 @@ import {
|
||||
MUTE_REMOTE_PARTICIPANT,
|
||||
OVERWRITE_PARTICIPANTS_NAMES,
|
||||
OVERWRITE_PARTICIPANT_NAME,
|
||||
PARTICIPANT_DISPLAY_NAME_CHANGED,
|
||||
PARTICIPANT_JOINED,
|
||||
PARTICIPANT_LEFT,
|
||||
PARTICIPANT_UPDATED,
|
||||
@@ -234,20 +231,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO Remove this middleware when the local display name update flow is
|
||||
// fully brought into redux.
|
||||
case PARTICIPANT_DISPLAY_NAME_CHANGED: {
|
||||
if (typeof APP !== 'undefined') {
|
||||
const participant = getLocalParticipant(store.getState());
|
||||
|
||||
if (participant && participant.id === action.id) {
|
||||
APP.UI.emitEvent(UIEvents.NICKNAME_CHANGED, action.name);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case RAISE_HAND_UPDATED: {
|
||||
const { participant } = action;
|
||||
let queue = getRaiseHandsQueue(store.getState());
|
||||
|
||||
@@ -19,9 +19,7 @@ export interface IParticipant {
|
||||
e2eeVerified?: boolean;
|
||||
email?: string;
|
||||
fakeParticipant?: FakeParticipant;
|
||||
features?: {
|
||||
'screen-sharing'?: boolean | string;
|
||||
};
|
||||
features?: IParticipantFeatures;
|
||||
getId?: Function;
|
||||
id: string;
|
||||
isJigasi?: boolean;
|
||||
@@ -54,6 +52,23 @@ export interface ILocalParticipant extends IParticipant {
|
||||
userSelectedMicDeviceLabel?: string;
|
||||
}
|
||||
|
||||
export interface IParticipantFeatures {
|
||||
'branding'?: boolean | string;
|
||||
'calendar'?: boolean | string;
|
||||
'flip'?: boolean | string;
|
||||
'inbound-call'?: boolean | string;
|
||||
'livestreaming'?: boolean | string;
|
||||
'lobby'?: boolean | string;
|
||||
'moderation'?: boolean | string;
|
||||
'outbound-call'?: boolean | string;
|
||||
'recording'?: boolean | string;
|
||||
'room'?: boolean | string;
|
||||
'screen-sharing'?: boolean | string;
|
||||
'sip-inbound-call'?: boolean | string;
|
||||
'sip-outbound-call'?: boolean | string;
|
||||
'transcription'?: boolean | string;
|
||||
}
|
||||
|
||||
export interface ISourceInfo {
|
||||
muted: boolean;
|
||||
videoType: string;
|
||||
|
||||
@@ -66,16 +66,6 @@ export const TRACK_MUTE_UNMUTE_FAILED = 'TRACK_MUTE_UNMUTE_FAILED';
|
||||
*/
|
||||
export const TRACK_NO_DATA_FROM_SOURCE = 'TRACK_NO_DATA_FROM_SOURCE';
|
||||
|
||||
/**
|
||||
* The type of redux action dispatched when the owner of a track changes due to ssrc remapping.
|
||||
*
|
||||
* {
|
||||
* type: TRACK_OWNER_CHANGED,
|
||||
* track: Track
|
||||
* }
|
||||
*/
|
||||
export const TRACK_OWNER_CHANGED = 'TRACK_OWNER_CHANGED';
|
||||
|
||||
/**
|
||||
* The type of redux action dispatched when a track has been (locally or
|
||||
* remotely) removed from the conference.
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
TRACK_CREATE_ERROR,
|
||||
TRACK_MUTE_UNMUTE_FAILED,
|
||||
TRACK_NO_DATA_FROM_SOURCE,
|
||||
TRACK_OWNER_CHANGED,
|
||||
TRACK_REMOVED,
|
||||
TRACK_STOPPED,
|
||||
TRACK_UPDATED,
|
||||
@@ -381,9 +380,6 @@ export function trackAdded(track: any) {
|
||||
track.on(
|
||||
JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,
|
||||
(type: VideoType) => dispatch(trackVideoTypeChanged(track, type)));
|
||||
track.on(
|
||||
JitsiTrackEvents.TRACK_OWNER_CHANGED,
|
||||
(owner: string) => dispatch(trackOwnerChanged(track, owner)));
|
||||
const local = track.isLocal();
|
||||
const mediaType = track.getVideoType() === VIDEO_TYPE.DESKTOP
|
||||
? MEDIA_TYPE.SCREENSHARE
|
||||
@@ -625,32 +621,6 @@ export function trackStreamingStatusChanged(track: any, streamingStatus: string)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the owner of the track changes due to ssrc remapping.
|
||||
*
|
||||
* @param {(JitsiRemoteTrack)} track - JitsiTrack instance.
|
||||
* @param {string} participantId - New owner's participant ID.
|
||||
* @returns {{
|
||||
* type: TRACK_OWNER_CHANGED,
|
||||
* track: Track
|
||||
* }}
|
||||
*/
|
||||
export function trackOwnerChanged(track: any, participantId: string): {
|
||||
track: {
|
||||
jitsiTrack: any;
|
||||
participantId: string;
|
||||
};
|
||||
type: 'TRACK_OWNER_CHANGED';
|
||||
} {
|
||||
return {
|
||||
type: TRACK_OWNER_CHANGED,
|
||||
track: {
|
||||
jitsiTrack: track,
|
||||
participantId
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals passed tracks to be added.
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@ import { IReduxState, IStore } from '../../app/types';
|
||||
import { showModeratedNotification } from '../../av-moderation/actions';
|
||||
import { shouldShowModeratedNotification } from '../../av-moderation/functions';
|
||||
import { setNoiseSuppressionEnabled } from '../../noise-suppression/actions';
|
||||
import { showNotification } from '../../notifications/actions';
|
||||
import { showErrorNotification, showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { stopReceiver } from '../../remote-control/actions';
|
||||
import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen-share/actions';
|
||||
@@ -13,10 +13,12 @@ import { toggleScreenshotCaptureSummary } from '../../screenshot-capture/actions
|
||||
import { isScreenshotCaptureEnabled } from '../../screenshot-capture/functions';
|
||||
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { notifyCameraError, notifyMicError } from '../devices/actions.web';
|
||||
import { openDialog } from '../dialog/actions';
|
||||
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||
import { setScreenshareMuted } from '../media/actions';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
import { JitsiTrackErrors, JitsiTrackEvents, browser } from '../lib-jitsi-meet';
|
||||
import { gumPending, setScreenshareMuted } from '../media/actions';
|
||||
import { MEDIA_TYPE, MediaType, VIDEO_TYPE } from '../media/constants';
|
||||
import { IGUMPendingState } from '../media/types';
|
||||
|
||||
import {
|
||||
addLocalTrack,
|
||||
@@ -31,7 +33,8 @@ import {
|
||||
getLocalVideoTrack,
|
||||
isToggleCameraEnabled
|
||||
} from './functions';
|
||||
import { IShareOptions, IToggleScreenSharingOptions } from './types';
|
||||
import logger from './logger';
|
||||
import { ICreateInitialTracksOptions, IInitialTracksErrors, IShareOptions, IToggleScreenSharingOptions } from './types';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
@@ -74,33 +77,6 @@ export function toggleScreensharing(
|
||||
* @param {Object} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleScreensharingError(
|
||||
error: Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK,
|
||||
{ dispatch }: IStore): void {
|
||||
if (error.name === JitsiTrackErrors.SCREENSHARING_USER_CANCELED) {
|
||||
return;
|
||||
}
|
||||
let descriptionKey, 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';
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
titleKey,
|
||||
descriptionKey
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -128,7 +104,6 @@ async function _maybeApplyAudioMixerEffect(desktopAudioTrack: any, state: IRedux
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggles screen sharing.
|
||||
*
|
||||
@@ -182,7 +157,7 @@ async function _toggleScreenSharing(
|
||||
try {
|
||||
tracks = await createLocalTracksF(options) as any[];
|
||||
} catch (error) {
|
||||
_handleScreensharingError(error as any, store);
|
||||
dispatch(handleScreenSharingError(error, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
|
||||
throw error;
|
||||
}
|
||||
@@ -196,7 +171,7 @@ async function _toggleScreenSharing(
|
||||
desktopVideoTrack.dispose();
|
||||
|
||||
if (!desktopAudioTrack) {
|
||||
_handleScreensharingError(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK, store);
|
||||
dispatch(handleScreenSharingError(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
|
||||
throw new Error(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK);
|
||||
}
|
||||
@@ -317,3 +292,224 @@ export function openAllowToggleCameraDialog(onAllow: Function, initiatorId: stri
|
||||
initiatorId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param {Function} dispatch - The redux dispatch function.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function setGUMPendingStateOnFailedTracks(tracks: Array<any>, dispatch: IStore['dispatch']) {
|
||||
const tracksTypes = tracks.map(track => {
|
||||
if (track.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
||||
return MEDIA_TYPE.SCREENSHARE;
|
||||
}
|
||||
|
||||
return track.getType();
|
||||
});
|
||||
const nonPendingTracks = [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ].filter(type => !tracksTypes.includes(type));
|
||||
|
||||
dispatch(gumPending(nonPendingTracks, IGUMPendingState.NONE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and adds to the conference the initial audio/video tracks.
|
||||
*
|
||||
* @param {Array<MediaType>} devices - Array with devices (audio/video) that will be used.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function createAndAddInitialAVTracks(devices: Array<MediaType>) {
|
||||
return async (dispatch: IStore['dispatch']) => {
|
||||
dispatch(gumPending(devices, IGUMPendingState.PENDING_UNMUTE));
|
||||
|
||||
const { tracks, errors } = await dispatch(createInitialAVTracks({ devices }));
|
||||
|
||||
setGUMPendingStateOnFailedTracks(tracks, dispatch);
|
||||
dispatch(displayErrorsForCreateInitialLocalTracks(errors));
|
||||
|
||||
await Promise.allSettled(tracks.map((track: any) => {
|
||||
const legacyConferenceObject = APP.conference;
|
||||
|
||||
if (track.isAudioTrack()) {
|
||||
return legacyConferenceObject.useAudioStream(track);
|
||||
}
|
||||
if (track.isVideoTrack()) {
|
||||
return legacyConferenceObject.useVideoStream(track);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}));
|
||||
|
||||
dispatch(gumPending(devices, IGUMPendingState.NONE));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the initial audio/video tracks.
|
||||
*
|
||||
* @param {ICreateInitialTracksOptions} options - Options for creating the audio/video tracks.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function createInitialAVTracks(options: ICreateInitialTracksOptions) {
|
||||
return (dispatch: IStore['dispatch'], _getState: IStore['getState']) => {
|
||||
const {
|
||||
devices,
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent
|
||||
} = options;
|
||||
|
||||
dispatch(gumPending(devices, IGUMPendingState.PENDING_UNMUTE));
|
||||
|
||||
return createLocalTracksF(options).then(tracks => {
|
||||
return {
|
||||
errors: {} as IInitialTracksErrors,
|
||||
tracks
|
||||
};
|
||||
})
|
||||
.catch(async error => {
|
||||
const errors = {} as IInitialTracksErrors;
|
||||
|
||||
if (error.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
|
||||
if (devices.includes(MEDIA_TYPE.AUDIO)) {
|
||||
errors.audioOnlyError = error;
|
||||
}
|
||||
|
||||
if (devices.includes(MEDIA_TYPE.VIDEO)) {
|
||||
errors.videoOnlyError = error;
|
||||
}
|
||||
|
||||
if (errors.audioOnlyError && errors.videoOnlyError) {
|
||||
errors.audioAndVideoError = error;
|
||||
}
|
||||
|
||||
return {
|
||||
errors,
|
||||
tracks: []
|
||||
};
|
||||
}
|
||||
|
||||
// Retry with separate gUM calls.
|
||||
const gUMPromises = [];
|
||||
const tracks: any[] | PromiseLike<any[]> = [];
|
||||
|
||||
if (devices.includes(MEDIA_TYPE.AUDIO)) {
|
||||
gUMPromises.push(createLocalTracksF({
|
||||
devices: [ MEDIA_TYPE.AUDIO ],
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent
|
||||
}));
|
||||
}
|
||||
|
||||
if (devices.includes(MEDIA_TYPE.VIDEO)) {
|
||||
gUMPromises.push(createLocalTracksF({
|
||||
devices: [ MEDIA_TYPE.VIDEO ],
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent
|
||||
}));
|
||||
}
|
||||
|
||||
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,
|
||||
errors
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays error notifications according to the state carried by the passed {@code errors} object.
|
||||
*
|
||||
* @param {InitialTracksErrors} errors - The errors (if any).
|
||||
* @returns {Function}
|
||||
* @private
|
||||
*/
|
||||
export function displayErrorsForCreateInitialLocalTracks(errors: IInitialTracksErrors) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
const {
|
||||
audioOnlyError,
|
||||
screenSharingError,
|
||||
videoOnlyError
|
||||
} = errors;
|
||||
|
||||
if (screenSharingError) {
|
||||
dispatch(handleScreenSharingError(screenSharingError, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}
|
||||
if (audioOnlyError || videoOnlyError) {
|
||||
if (audioOnlyError) {
|
||||
dispatch(notifyMicError(audioOnlyError));
|
||||
}
|
||||
|
||||
if (videoOnlyError) {
|
||||
dispatch(notifyCameraError(videoOnlyError));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a UI notification for screensharing failure based on the error passed.
|
||||
*
|
||||
* @private
|
||||
* @param {Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK} error - The error.
|
||||
* @param {NOTIFICATION_TIMEOUT_TYPE} timeout - The time for showing the notification.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function handleScreenSharingError(
|
||||
error: Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK,
|
||||
timeout: NOTIFICATION_TIMEOUT_TYPE) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
logger.error('failed to share local desktop', error);
|
||||
|
||||
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';
|
||||
} else { // safeguard for not showing notification with empty text. This will also include
|
||||
// error.name === JitsiTrackErrors.SCREENSHARING_USER_CANCELED
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(showErrorNotification({
|
||||
descriptionKey,
|
||||
titleKey
|
||||
}, timeout));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
TRACK_ADDED,
|
||||
TRACK_MUTE_UNMUTE_FAILED,
|
||||
TRACK_NO_DATA_FROM_SOURCE,
|
||||
TRACK_OWNER_CHANGED,
|
||||
TRACK_REMOVED,
|
||||
TRACK_STOPPED,
|
||||
TRACK_UPDATED
|
||||
@@ -82,23 +81,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
return result;
|
||||
}
|
||||
|
||||
case TRACK_OWNER_CHANGED: {
|
||||
const oldTrack = getTrackByJitsiTrack(store.getState()['features/base/tracks'], action.track?.jitsiTrack);
|
||||
const oldOwner = oldTrack?.participantId;
|
||||
const result = next(action);
|
||||
const newOwner = action.track?.participantId;
|
||||
|
||||
if (oldOwner) {
|
||||
logTracksForParticipant(store.getState()['features/base/tracks'], oldOwner, 'Owner changed');
|
||||
}
|
||||
|
||||
if (newOwner) {
|
||||
logTracksForParticipant(store.getState()['features/base/tracks'], newOwner, 'Owner changed');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case TRACK_MUTE_UNMUTE_FAILED: {
|
||||
const { jitsiTrack } = action.track;
|
||||
const muted = action.wasMuted;
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
TRACK_CREATE_CANCELED,
|
||||
TRACK_CREATE_ERROR,
|
||||
TRACK_NO_DATA_FROM_SOURCE,
|
||||
TRACK_OWNER_CHANGED,
|
||||
TRACK_REMOVED,
|
||||
TRACK_UPDATED,
|
||||
TRACK_WILL_CREATE
|
||||
@@ -43,18 +42,6 @@ function track(state: ITrack, action: AnyAction) {
|
||||
}
|
||||
break;
|
||||
|
||||
case TRACK_OWNER_CHANGED: {
|
||||
const t = action.track;
|
||||
|
||||
if (state.jitsiTrack === t.jitsiTrack) {
|
||||
return {
|
||||
...state,
|
||||
participantId: t.participantId
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TRACK_UPDATED: {
|
||||
const t = action.track;
|
||||
|
||||
@@ -104,7 +91,6 @@ ReducerRegistry.register<ITracksState>('features/base/tracks', (state = [], acti
|
||||
switch (action.type) {
|
||||
case PARTICIPANT_ID_CHANGED:
|
||||
case TRACK_NO_DATA_FROM_SOURCE:
|
||||
case TRACK_OWNER_CHANGED:
|
||||
case TRACK_UPDATED:
|
||||
return state.map((t: ITrack) => track(t, action));
|
||||
case TRACK_ADDED: {
|
||||
|
||||
@@ -72,3 +72,16 @@ export interface IShareOptions {
|
||||
desktopSharingSources?: string[];
|
||||
desktopStream?: any;
|
||||
}
|
||||
|
||||
export interface ICreateInitialTracksOptions {
|
||||
devices: Array<MediaType>;
|
||||
firePermissionPromptIsShownEvent?: boolean;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface IInitialTracksErrors {
|
||||
audioAndVideoError?: Error;
|
||||
audioOnlyError: Error;
|
||||
screenSharingError: Error;
|
||||
videoOnlyError: Error;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import React, { forwardRef, useCallback, useState } from 'react';
|
||||
import {
|
||||
KeyboardTypeOptions,
|
||||
NativeSyntheticEvent, ReturnKeyTypeOptions,
|
||||
NativeSyntheticEvent,
|
||||
ReturnKeyTypeOptions,
|
||||
StyleProp,
|
||||
Text,
|
||||
TextInput,
|
||||
TextInputChangeEventData,
|
||||
TextInputFocusEventData, TextInputKeyPressEventData,
|
||||
TextInputFocusEventData,
|
||||
TextInputKeyPressEventData,
|
||||
TextInputSubmitEditingEventData,
|
||||
TextStyle,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewStyle
|
||||
@@ -25,8 +28,16 @@ interface IProps extends IInputProps {
|
||||
autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters' | undefined;
|
||||
autoFocus?: boolean;
|
||||
blurOnSubmit?: boolean | undefined;
|
||||
bottomLabel?: string;
|
||||
customStyles?: ICustomStyles;
|
||||
editable?: boolean | undefined;
|
||||
|
||||
/**
|
||||
* The id to set on the input element.
|
||||
* This is required because we need it internally to tie the input to its
|
||||
* info (label, error) so that screen reader users don't get lost.
|
||||
*/
|
||||
id?: string;
|
||||
keyboardType?: KeyboardTypeOptions;
|
||||
maxLength?: number | undefined;
|
||||
minHeight?: number | string | undefined;
|
||||
@@ -52,11 +63,13 @@ const Input = forwardRef<TextInput, IProps>(({
|
||||
autoCapitalize,
|
||||
autoFocus,
|
||||
blurOnSubmit,
|
||||
bottomLabel,
|
||||
clearable,
|
||||
customStyles,
|
||||
disabled,
|
||||
error,
|
||||
icon,
|
||||
id,
|
||||
keyboardType,
|
||||
label,
|
||||
maxLength,
|
||||
@@ -106,7 +119,7 @@ const Input = forwardRef<TextInput, IProps>(({
|
||||
onSubmitEditing?.(text);
|
||||
}, [ onSubmitEditing ]);
|
||||
|
||||
return (<View style = { [ styles.inputContainer, customStyles?.container ] }>
|
||||
return (<View style = { [ styles.inputContainer, customStyles?.container ] as StyleProp<ViewStyle> }>
|
||||
{label && <Text style = { styles.label }>{ label }</Text>}
|
||||
<View style = { styles.fieldContainer as StyleProp<ViewStyle> }>
|
||||
{icon && <Icon
|
||||
@@ -121,6 +134,7 @@ const Input = forwardRef<TextInput, IProps>(({
|
||||
autoFocus = { autoFocus }
|
||||
blurOnSubmit = { blurOnSubmit }
|
||||
editable = { !disabled }
|
||||
id = { id }
|
||||
keyboardType = { keyboardType }
|
||||
maxLength = { maxLength }
|
||||
|
||||
@@ -145,11 +159,11 @@ const Input = forwardRef<TextInput, IProps>(({
|
||||
clearable && styles.clearableInput,
|
||||
customStyles?.input,
|
||||
disabled && styles.inputDisabled,
|
||||
error && styles.inputError,
|
||||
focused && styles.inputFocused,
|
||||
icon && styles.iconInput,
|
||||
multiline && styles.inputMultiline
|
||||
] }
|
||||
multiline && styles.inputMultiline,
|
||||
focused && styles.inputFocused,
|
||||
error && styles.inputError
|
||||
] as StyleProp<TextStyle> }
|
||||
textContentType = { textContentType }
|
||||
value = { typeof value === 'number' ? `${value}` : value } />
|
||||
{ clearable && !disabled && value !== '' && (
|
||||
@@ -163,6 +177,20 @@ const Input = forwardRef<TextInput, IProps>(({
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
{
|
||||
bottomLabel && (
|
||||
<View>
|
||||
<Text
|
||||
id = { `${id}-description` }
|
||||
style = { [
|
||||
styles.bottomLabel,
|
||||
error && styles.bottomLabelError
|
||||
] }>
|
||||
{ bottomLabel }
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
</View>);
|
||||
});
|
||||
|
||||
|
||||
@@ -73,5 +73,15 @@ export default {
|
||||
|
||||
clearIcon: {
|
||||
color: BaseTheme.palette.icon01
|
||||
},
|
||||
|
||||
bottomLabel: {
|
||||
...BaseTheme.typography.labelRegular,
|
||||
color: BaseTheme.palette.text02,
|
||||
marginTop: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
bottomLabelError: {
|
||||
color: BaseTheme.palette.textError
|
||||
}
|
||||
};
|
||||
|
||||
@@ -537,6 +537,7 @@ export function urlObjectToString(o: { [key: string]: any; }): string | undefine
|
||||
|
||||
const search = new URLSearchParams(url.search);
|
||||
|
||||
// TODO: once all available versions are updated to support the jwt in the hash, remove this
|
||||
if (jwt) {
|
||||
search.set('jwt', jwt);
|
||||
}
|
||||
@@ -561,6 +562,14 @@ export function urlObjectToString(o: { [key: string]: any; }): string | undefine
|
||||
|
||||
let { hash } = url;
|
||||
|
||||
if (jwt) {
|
||||
if (hash.length) {
|
||||
hash = `${hash}&jwt=${JSON.stringify(jwt)}`;
|
||||
} else {
|
||||
hash = `#jwt=${JSON.stringify(jwt)}`;
|
||||
}
|
||||
}
|
||||
|
||||
for (const urlPrefix of [ 'config', 'iceServers', 'interfaceConfig', 'devices', 'userInfo', 'appData' ]) {
|
||||
const urlParamsArray
|
||||
= _objectToURLParamsArray(
|
||||
|
||||
@@ -116,7 +116,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const state = store.getState();
|
||||
|
||||
if (!isReactionsEnabled(state)) {
|
||||
return;
|
||||
return next(action);
|
||||
}
|
||||
|
||||
const { participant, data } = action;
|
||||
|
||||
@@ -368,8 +368,6 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
*/
|
||||
_start() {
|
||||
APP.UI.start();
|
||||
|
||||
APP.UI.registerListeners();
|
||||
APP.UI.bindEvents();
|
||||
|
||||
FULL_SCREEN_EVENTS.forEach(name =>
|
||||
|
||||
@@ -314,6 +314,7 @@ function _calendarNotification({ dispatch, getState }: IStore, eventToShow: any)
|
||||
customActionType,
|
||||
description,
|
||||
icon,
|
||||
maxLines: 1,
|
||||
title,
|
||||
uid
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @ts-expect-error
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
|
||||
import { getCurrentConference } from '../base/conference/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
@@ -41,7 +39,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
}
|
||||
case TOGGLE_DOCUMENT_EDITING: {
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
|
||||
APP.UI.onEtherpadClicked();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -427,17 +427,20 @@ export function invitePeopleAndChatRooms(
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const headers = {
|
||||
...jwt ? { 'Authorization': `Bearer ${jwt}` } : {},
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
return fetch(
|
||||
`${inviteServiceUrl}?token=${jwt}`,
|
||||
`${inviteServiceUrl}`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
'invited': inviteItems,
|
||||
'url': inviteUrl
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
headers
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -544,8 +547,16 @@ export function searchDirectory( // eslint-disable-line max-params
|
||||
const query = encodeURIComponent(text);
|
||||
const queryTypesString = encodeURIComponent(JSON.stringify(queryTypes));
|
||||
|
||||
const headers = {
|
||||
...jwt ? { 'Authorization': `Bearer ${jwt}` } : {}
|
||||
};
|
||||
|
||||
return fetch(`${serviceUrl}?query=${query}&queryTypes=${
|
||||
queryTypesString}&jwt=${jwt}`)
|
||||
queryTypesString}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers
|
||||
})
|
||||
.then(response => {
|
||||
const jsonify = response.json();
|
||||
|
||||
|
||||
@@ -152,15 +152,16 @@ const Notification = ({
|
||||
if (descriptionArray?.length) {
|
||||
return (
|
||||
<>
|
||||
<Text style = { styles.contentTextTitle as TextStyle }>
|
||||
{titleText}
|
||||
<Text
|
||||
style = { styles.contentTextTitle as TextStyle }>
|
||||
{ titleText }
|
||||
</Text>
|
||||
{
|
||||
descriptionArray.map((line, index) => (
|
||||
<Text
|
||||
key = { index }
|
||||
style = { styles.contentText }>
|
||||
{replaceNonUnicodeEmojis(line)}
|
||||
{ replaceNonUnicodeEmojis(line) }
|
||||
</Text>
|
||||
))
|
||||
}
|
||||
@@ -170,7 +171,7 @@ const Notification = ({
|
||||
|
||||
return (
|
||||
<Text style = { styles.contentTextTitle as TextStyle }>
|
||||
{titleText}
|
||||
{ titleText }
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
@@ -199,10 +200,10 @@ const Notification = ({
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.contentContainer }>
|
||||
{_renderContent()}
|
||||
{ _renderContent() }
|
||||
</View>
|
||||
<View style = { styles.btnContainer as ViewStyle }>
|
||||
{mapAppearanceToButtons()}
|
||||
{ mapAppearanceToButtons() }
|
||||
</View>
|
||||
</View>
|
||||
<IconButton
|
||||
|
||||
@@ -7,13 +7,12 @@ const contentColumn = {
|
||||
};
|
||||
|
||||
const notification = {
|
||||
display: 'flex',
|
||||
backgroundColor: BaseTheme.palette.ui10,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
borderLeftColor: BaseTheme.palette.link01Active,
|
||||
borderLeftWidth: 4,
|
||||
borderLeftWidth: BaseTheme.spacing[1],
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
maxHeight: 120,
|
||||
height: 'auto',
|
||||
marginBottom: BaseTheme.spacing[2],
|
||||
marginHorizontal: BaseTheme.spacing[2],
|
||||
@@ -43,20 +42,20 @@ export default {
|
||||
*/
|
||||
|
||||
contentContainer: {
|
||||
marginTop: BaseTheme.spacing[2]
|
||||
marginHorizontal: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
contentText: {
|
||||
color: BaseTheme.palette.text04,
|
||||
marginLeft: BaseTheme.spacing[6],
|
||||
marginLeft: BaseTheme.spacing[4],
|
||||
marginTop: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
contentTextTitle: {
|
||||
color: BaseTheme.palette.text04,
|
||||
marginLeft: BaseTheme.spacing[6],
|
||||
fontWeight: 'bold',
|
||||
marginTop: BaseTheme.spacing[1]
|
||||
marginLeft: BaseTheme.spacing[4],
|
||||
marginTop: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -90,9 +89,8 @@ export default {
|
||||
},
|
||||
|
||||
iconContainer: {
|
||||
left: BaseTheme.spacing[1],
|
||||
position: 'absolute',
|
||||
top: BaseTheme.spacing[2]
|
||||
top: 12
|
||||
},
|
||||
|
||||
btn: {
|
||||
@@ -102,7 +100,7 @@ export default {
|
||||
btnContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginLeft: BaseTheme.spacing[1]
|
||||
marginLeft: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
withToolbox: {
|
||||
|
||||
@@ -11,12 +11,12 @@ export const NOTIFICATION_TIMEOUT = {
|
||||
/**
|
||||
* Notification timeout type.
|
||||
*/
|
||||
export const NOTIFICATION_TIMEOUT_TYPE = {
|
||||
SHORT: 'short',
|
||||
MEDIUM: 'medium',
|
||||
LONG: 'long',
|
||||
STICKY: 'sticky'
|
||||
};
|
||||
export enum NOTIFICATION_TIMEOUT_TYPE {
|
||||
LONG = 'long',
|
||||
MEDIUM = 'medium',
|
||||
SHORT = 'short',
|
||||
STICKY = 'sticky'
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of possible notification types.
|
||||
|
||||
@@ -2,9 +2,12 @@ import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { getSsrcRewritingFeatureFlag } from '../../../base/config/functions.any';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getMutedStateByParticipantAndMediaType,
|
||||
getParticipantById,
|
||||
getParticipantDisplayName,
|
||||
hasRaisedHand,
|
||||
@@ -166,8 +169,12 @@ function mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { participant } = ownProps;
|
||||
const { ownerId } = state['features/shared-video'];
|
||||
const localParticipantId = getLocalParticipant(state)?.id;
|
||||
const _isAudioMuted = Boolean(participant && isParticipantAudioMuted(participant, state));
|
||||
const _isVideoMuted = isParticipantVideoMuted(participant, state);
|
||||
const _isAudioMuted = getSsrcRewritingFeatureFlag(state)
|
||||
? Boolean(participant && getMutedStateByParticipantAndMediaType(state, participant, MEDIA_TYPE.AUDIO))
|
||||
: Boolean(participant && isParticipantAudioMuted(participant, state));
|
||||
const _isVideoMuted = getSsrcRewritingFeatureFlag(state)
|
||||
? Boolean(participant && getMutedStateByParticipantAndMediaType(state, participant, MEDIA_TYPE.VIDEO))
|
||||
: isParticipantVideoMuted(participant, state);
|
||||
const audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state);
|
||||
const videoMediaState = getParticipantVideoMediaState(participant, _isVideoMuted, state);
|
||||
const { disableModeratorIndicator } = state['features/base/config'];
|
||||
|
||||
@@ -2,10 +2,12 @@ import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { getSsrcRewritingFeatureFlag } from '../../../base/config/functions.any';
|
||||
import { JitsiTrackEvents } from '../../../base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getMutedStateByParticipantAndMediaType,
|
||||
getParticipantByIdOrUndefined,
|
||||
getParticipantDisplayName,
|
||||
hasRaisedHand,
|
||||
@@ -299,15 +301,15 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { participantID, searchString } = ownProps;
|
||||
const { ownerId } = state['features/shared-video'];
|
||||
const localParticipantId = getLocalParticipant(state)?.id;
|
||||
|
||||
const participant = getParticipantByIdOrUndefined(state, participantID);
|
||||
|
||||
const _displayName = getParticipantDisplayName(state, participant?.id ?? '');
|
||||
|
||||
const _matchesSearch = participantMatchesSearch(participant, searchString);
|
||||
|
||||
const _isAudioMuted = Boolean(participant && isParticipantAudioMuted(participant, state));
|
||||
const _isVideoMuted = isParticipantVideoMuted(participant, state);
|
||||
const _isAudioMuted = getSsrcRewritingFeatureFlag(state)
|
||||
? Boolean(participant && getMutedStateByParticipantAndMediaType(state, participant, MEDIA_TYPE.AUDIO))
|
||||
: Boolean(participant && isParticipantAudioMuted(participant, state));
|
||||
const _isVideoMuted = getSsrcRewritingFeatureFlag(state)
|
||||
? Boolean(participant && getMutedStateByParticipantAndMediaType(state, participant, MEDIA_TYPE.VIDEO))
|
||||
: isParticipantVideoMuted(participant, state);
|
||||
const _audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state);
|
||||
const _videoMediaState = getParticipantVideoMediaState(participant, _isVideoMuted, state);
|
||||
const _quickActionButtonType = getQuickActionButtonType(participant, _isAudioMuted, _isVideoMuted, state);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
close as closeParticipantsPane,
|
||||
open as openParticipantsPane
|
||||
} from '../../../participants-pane/actions.web';
|
||||
import { closeOverflowMenuIfOpen } from '../../../toolbox/actions.web';
|
||||
import { isParticipantsPaneEnabled } from '../../functions';
|
||||
|
||||
import ParticipantsCounter from './ParticipantsCounter';
|
||||
@@ -61,6 +62,7 @@ class ParticipantsPaneButton extends AbstractButton<IProps> {
|
||||
_handleClick() {
|
||||
const { dispatch, _isOpen } = this.props;
|
||||
|
||||
dispatch(closeOverflowMenuIfOpen());
|
||||
if (_isOpen) {
|
||||
dispatch(closeParticipantsPane());
|
||||
} else {
|
||||
|
||||
@@ -18,6 +18,17 @@ export const CHANGE_VOTE = 'CHANGE_VOTE';
|
||||
*/
|
||||
export const CLEAR_POLLS = 'CLEAR_POLLS';
|
||||
|
||||
/**
|
||||
* The type of the action triggered when the poll is editing.
|
||||
*
|
||||
* {
|
||||
* type: EDIT_POLL,
|
||||
* pollId: string,
|
||||
* editing: boolean
|
||||
* }
|
||||
*/
|
||||
export const EDIT_POLL = 'EDIT_POLL';
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a new Poll was received.
|
||||
*
|
||||
@@ -71,3 +82,15 @@ export const RETRACT_VOTE = 'RETRACT_VOTE';
|
||||
* }
|
||||
*/
|
||||
export const RESET_NB_UNREAD_POLLS = 'RESET_NB_UNREAD_POLLS';
|
||||
|
||||
/**
|
||||
* The type of the action triggered when the poll is saved.
|
||||
*
|
||||
* {
|
||||
* type: SAVE_POLL,
|
||||
* poll: Poll,
|
||||
* pollId: string,
|
||||
* saved: boolean
|
||||
* }
|
||||
*/
|
||||
export const SAVE_POLL = 'SAVE_POLL';
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {
|
||||
CHANGE_VOTE,
|
||||
CLEAR_POLLS,
|
||||
EDIT_POLL,
|
||||
RECEIVE_ANSWER,
|
||||
RECEIVE_POLL,
|
||||
REGISTER_VOTE,
|
||||
RESET_NB_UNREAD_POLLS,
|
||||
RETRACT_VOTE
|
||||
RETRACT_VOTE,
|
||||
SAVE_POLL
|
||||
} from './actionTypes';
|
||||
import { IAnswer, IPoll } from './types';
|
||||
|
||||
@@ -128,3 +130,46 @@ export function resetNbUnreadPollsMessages() {
|
||||
type: RESET_NB_UNREAD_POLLS
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to signal saving a poll.
|
||||
*
|
||||
* @param {string} pollId - The id of the poll that gets to be saved.
|
||||
* @param {IPoll} poll - The Poll object that gets to be saved.
|
||||
* @param {boolean} saved - Whether the poll is saved or not.
|
||||
* @returns {{
|
||||
* type: RECEIVE_POLL,
|
||||
* poll: IPoll,
|
||||
* pollId: string,
|
||||
* saved: boolean
|
||||
* }}
|
||||
*/
|
||||
export function savePoll(pollId: string, poll: IPoll, saved: boolean) {
|
||||
return {
|
||||
type: SAVE_POLL,
|
||||
pollId,
|
||||
poll: {
|
||||
...poll,
|
||||
saved
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to signal editing a poll.
|
||||
*
|
||||
* @param {string} pollId - The id of the poll that gets to be edited.
|
||||
* @param {boolean} editing - Whether the poll is in edit mode or not.
|
||||
* @returns {{
|
||||
* type: RECEIVE_POLL,
|
||||
* pollId: string,
|
||||
* editing: boolean
|
||||
* }}
|
||||
*/
|
||||
export function editPoll(pollId: string, editing: boolean) {
|
||||
return {
|
||||
type: EDIT_POLL,
|
||||
pollId,
|
||||
editing
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { getParticipantDisplayName } from '../../base/participants/functions';
|
||||
import { useBoundSelector } from '../../base/util/hooks';
|
||||
import { registerVote, setVoteChanging } from '../actions';
|
||||
import { COMMAND_ANSWER_POLL } from '../constants';
|
||||
import { editPoll, registerVote, setVoteChanging } from '../actions';
|
||||
import { COMMAND_ANSWER_POLL, COMMAND_NEW_POLL } from '../constants';
|
||||
import { IPoll } from '../types';
|
||||
|
||||
/**
|
||||
@@ -16,6 +16,7 @@ import { IPoll } from '../types';
|
||||
*/
|
||||
type InputProps = {
|
||||
pollId: string;
|
||||
setCreateMode: (mode: boolean) => void;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -26,7 +27,10 @@ export type AbstractProps = {
|
||||
checkBoxStates: boolean[];
|
||||
creatorName: string;
|
||||
poll: IPoll;
|
||||
pollId: string;
|
||||
sendPoll: () => void;
|
||||
setCheckbox: Function;
|
||||
setCreateMode: (mode: boolean) => void;
|
||||
skipAnswer: () => void;
|
||||
skipChangeVote: () => void;
|
||||
submitAnswer: () => void;
|
||||
@@ -42,21 +46,23 @@ export type AbstractProps = {
|
||||
*/
|
||||
const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props: InputProps) => {
|
||||
|
||||
const { pollId } = props;
|
||||
const { pollId, setCreateMode } = props;
|
||||
|
||||
const conference: any = useSelector((state: IReduxState) => state['features/base/conference'].conference);
|
||||
|
||||
const poll: IPoll = useSelector((state: IReduxState) => state['features/polls'].polls[pollId]);
|
||||
|
||||
const { answers, lastVote, question, senderId } = poll;
|
||||
|
||||
const [ checkBoxStates, setCheckBoxState ] = useState(() => {
|
||||
if (poll.lastVote !== null) {
|
||||
return [ ...poll.lastVote ];
|
||||
if (lastVote !== null) {
|
||||
return [ ...lastVote ];
|
||||
}
|
||||
|
||||
return new Array(poll.answers.length).fill(false);
|
||||
return new Array(answers.length).fill(false);
|
||||
});
|
||||
|
||||
const participantName = useBoundSelector(getParticipantDisplayName, poll.senderId);
|
||||
const participantName = useBoundSelector(getParticipantDisplayName, senderId);
|
||||
|
||||
const setCheckbox = useCallback((index, state) => {
|
||||
const newCheckBoxStates = [ ...checkBoxStates ];
|
||||
@@ -81,10 +87,21 @@ const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props:
|
||||
return false;
|
||||
}, [ pollId, checkBoxStates, conference ]);
|
||||
|
||||
const sendPoll = useCallback(() => {
|
||||
conference?.sendMessage({
|
||||
type: COMMAND_NEW_POLL,
|
||||
pollId,
|
||||
question,
|
||||
answers: answers.map(answer => answer.name)
|
||||
});
|
||||
|
||||
dispatch(editPoll(pollId, false));
|
||||
|
||||
}, [ conference, question, answers ]);
|
||||
|
||||
const skipAnswer = useCallback(() => {
|
||||
dispatch(registerVote(pollId, null));
|
||||
sendAnalytics(createPollEvent('vote.skipped'));
|
||||
|
||||
}, [ pollId ]);
|
||||
|
||||
const skipChangeVote = useCallback(() => {
|
||||
@@ -97,7 +114,10 @@ const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props:
|
||||
checkBoxStates = { checkBoxStates }
|
||||
creatorName = { participantName }
|
||||
poll = { poll }
|
||||
pollId = { pollId }
|
||||
sendPoll = { sendPoll }
|
||||
setCheckbox = { setCheckbox }
|
||||
setCreateMode = { setCreateMode }
|
||||
skipAnswer = { skipAnswer }
|
||||
skipChangeVote = { skipChangeVote }
|
||||
submitAnswer = { submitAnswer }
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import React, { ComponentType, FormEvent, useCallback, useState } from 'react';
|
||||
/* eslint-disable arrow-body-style */
|
||||
|
||||
import React, { ComponentType, FormEvent, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { createPollEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { COMMAND_NEW_POLL } from '../constants';
|
||||
import { getLocalParticipant } from '../../base/participants/functions';
|
||||
import { savePoll } from '../actions';
|
||||
import { hasIdenticalAnswers } from '../functions';
|
||||
import { IAnswerData, IPoll } from '../types';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of inheriting component.
|
||||
@@ -21,12 +25,14 @@ type InputProps = {
|
||||
**/
|
||||
export type AbstractProps = InputProps & {
|
||||
addAnswer: (index?: number) => void;
|
||||
answers: Array<string>;
|
||||
answers: Array<IAnswerData>;
|
||||
editingPoll: IPoll | undefined;
|
||||
editingPollId: string | undefined;
|
||||
isSubmitDisabled: boolean;
|
||||
onSubmit: (event?: FormEvent<HTMLFormElement>) => void;
|
||||
question: string;
|
||||
removeAnswer: (index: number) => void;
|
||||
setAnswer: (index: number, value: string) => void;
|
||||
setAnswer: (index: number, value: IAnswerData) => void;
|
||||
setQuestion: (question: string) => void;
|
||||
t: Function;
|
||||
};
|
||||
@@ -44,11 +50,45 @@ const AbstractPollCreate = (Component: ComponentType<AbstractProps>) => (props:
|
||||
|
||||
const { setCreateMode } = props;
|
||||
|
||||
const [ question, setQuestion ] = useState('');
|
||||
const pollState = useSelector((state: IReduxState) => state['features/polls'].polls);
|
||||
|
||||
const [ answers, setAnswers ] = useState([ '', '' ]);
|
||||
const editingPoll: [ string, IPoll ] | null = useMemo(() => {
|
||||
if (!pollState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const setAnswer = useCallback((i, answer) => {
|
||||
for (const key in pollState) {
|
||||
if (pollState.hasOwnProperty(key) && pollState[key].editing) {
|
||||
return [ key, pollState[key] ];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [ pollState ]);
|
||||
|
||||
const answerResults = useMemo(() => {
|
||||
return editingPoll
|
||||
? editingPoll[1].answers
|
||||
: [
|
||||
{
|
||||
name: '',
|
||||
voters: []
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
voters: []
|
||||
} ];
|
||||
}, [ editingPoll ]);
|
||||
|
||||
const questionResult = useMemo(() => {
|
||||
return editingPoll ? editingPoll[1].question : '';
|
||||
}, [ editingPoll ]);
|
||||
|
||||
const [ question, setQuestion ] = useState(questionResult);
|
||||
|
||||
const [ answers, setAnswers ] = useState(answerResults);
|
||||
|
||||
const setAnswer = useCallback((i: number, answer: IAnswerData) => {
|
||||
setAnswers(currentAnswers => {
|
||||
const newAnswers = [ ...currentAnswers ];
|
||||
|
||||
@@ -59,10 +99,14 @@ const AbstractPollCreate = (Component: ComponentType<AbstractProps>) => (props:
|
||||
}, [ answers ]);
|
||||
|
||||
const addAnswer = useCallback((i?: number) => {
|
||||
const newAnswers = [ ...answers ];
|
||||
const newAnswers: Array<IAnswerData> = [ ...answers ];
|
||||
|
||||
sendAnalytics(createPollEvent('option.added'));
|
||||
newAnswers.splice(typeof i === 'number' ? i : answers.length, 0, '');
|
||||
newAnswers.splice(typeof i === 'number'
|
||||
? i : answers.length, 0, {
|
||||
name: '',
|
||||
voters: []
|
||||
});
|
||||
setAnswers(newAnswers);
|
||||
}, [ answers ]);
|
||||
|
||||
@@ -79,23 +123,40 @@ const AbstractPollCreate = (Component: ComponentType<AbstractProps>) => (props:
|
||||
|
||||
const conference = useSelector((state: IReduxState) => state['features/base/conference'].conference);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const pollId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36);
|
||||
|
||||
const localParticipant = useSelector(getLocalParticipant);
|
||||
|
||||
const onSubmit = useCallback(ev => {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
const filteredAnswers = answers.filter(answer => answer.trim().length > 0);
|
||||
const filteredAnswers = answers.filter(answer => answer.name.trim().length > 0);
|
||||
|
||||
if (filteredAnswers.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
conference?.sendMessage({
|
||||
type: COMMAND_NEW_POLL,
|
||||
pollId: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36),
|
||||
const poll = {
|
||||
changingVote: false,
|
||||
senderId: localParticipant?.id,
|
||||
showResults: false,
|
||||
lastVote: null,
|
||||
question,
|
||||
answers: filteredAnswers
|
||||
});
|
||||
answers: filteredAnswers,
|
||||
saved: false,
|
||||
editing: false
|
||||
};
|
||||
|
||||
if (editingPoll) {
|
||||
dispatch(savePoll(editingPoll[0], poll, true));
|
||||
} else {
|
||||
dispatch(savePoll(pollId, poll, true));
|
||||
}
|
||||
|
||||
sendAnalytics(createPollEvent('created'));
|
||||
|
||||
setCreateMode(false);
|
||||
@@ -105,7 +166,7 @@ const AbstractPollCreate = (Component: ComponentType<AbstractProps>) => (props:
|
||||
// Check if the poll create form can be submitted i.e. if the send button should be disabled.
|
||||
const isSubmitDisabled
|
||||
= question.trim().length <= 0 // If no question is provided
|
||||
|| answers.filter(answer => answer.trim().length > 0).length < 2 // If not enough options are provided
|
||||
|| answers.filter(answer => answer.name.trim().length > 0).length < 2 // If not enough options are provided
|
||||
|| hasIdenticalAnswers(answers); // If duplicate options are provided
|
||||
|
||||
const { t } = useTranslation();
|
||||
@@ -113,6 +174,8 @@ const AbstractPollCreate = (Component: ComponentType<AbstractProps>) => (props:
|
||||
return (<Component
|
||||
addAnswer = { addAnswer }
|
||||
answers = { answers }
|
||||
editingPoll = { editingPoll?.[1] }
|
||||
editingPollId = { editingPoll?.[0] }
|
||||
isSubmitDisabled = { isSubmitDisabled }
|
||||
onSubmit = { onSubmit }
|
||||
question = { question }
|
||||
|
||||
@@ -69,9 +69,9 @@ const AbstractPollResults = (Component: ComponentType<AbstractProps>) => (props:
|
||||
// Getting every voters ID that participates to the poll
|
||||
for (const answer of pollDetails.answers) {
|
||||
// checking if the voters is an array for supporting old structure model
|
||||
const voters = answer.voters?.length ? answer.voters : Object.keys(answer.voters);
|
||||
const voters: string[] = answer.voters.length ? answer.voters : Object.keys(answer.voters);
|
||||
|
||||
voters.forEach(voter => allVoters.add(voter));
|
||||
voters.forEach((voter: string) => allVoters.add(voter));
|
||||
}
|
||||
|
||||
return pollDetails.answers.map(answer => {
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
|
||||
import React from 'react';
|
||||
import { Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import Switch from '../../../base/ui/components/native/Switch';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { editPoll } from '../../actions';
|
||||
import { isSubmitAnswerDisabled } from '../../functions';
|
||||
import AbstractPollAnswer, { AbstractProps } from '../AbstractPollAnswer';
|
||||
|
||||
import { chatStyles, dialogStyles } from './styles';
|
||||
|
||||
|
||||
const PollAnswer = (props: AbstractProps) => {
|
||||
const {
|
||||
checkBoxStates,
|
||||
poll,
|
||||
pollId,
|
||||
sendPoll,
|
||||
setCheckbox,
|
||||
setCreateMode,
|
||||
skipAnswer,
|
||||
skipChangeVote,
|
||||
submitAnswer,
|
||||
t
|
||||
} = props;
|
||||
const { changingVote } = poll;
|
||||
const { changingVote, saved: pollSaved } = poll;
|
||||
const dispatch = useDispatch();
|
||||
const localParticipant = useSelector(getLocalParticipant);
|
||||
const { PRIMARY, SECONDARY } = BUTTON_TYPES;
|
||||
|
||||
@@ -34,33 +40,57 @@ const PollAnswer = (props: AbstractProps) => {
|
||||
}
|
||||
</Text>
|
||||
<View style = { chatStyles.answerContent as ViewStyle }>
|
||||
{poll.answers.map((answer, index) => (
|
||||
<View
|
||||
key = { index }
|
||||
style = { chatStyles.switchRow as ViewStyle } >
|
||||
<Switch
|
||||
checked = { checkBoxStates[index] }
|
||||
/* eslint-disable-next-line react/jsx-no-bind */
|
||||
onChange = { state => setCheckbox(index, state) } />
|
||||
<Text style = { chatStyles.switchLabel as TextStyle }>{answer.name}</Text>
|
||||
{
|
||||
poll.answers.map((answer, index: number) => (
|
||||
<View
|
||||
key = { index }
|
||||
style = { chatStyles.switchRow as ViewStyle } >
|
||||
<Switch
|
||||
checked = { checkBoxStates[index] }
|
||||
disabled = { poll.saved }
|
||||
onChange = { state => setCheckbox(index, state) } />
|
||||
<Text style = { chatStyles.switchLabel as TextStyle }>
|
||||
{ answer.name }
|
||||
</Text>
|
||||
</View>
|
||||
))
|
||||
}
|
||||
</View>
|
||||
{
|
||||
pollSaved
|
||||
? <View style = { chatStyles.buttonRow as ViewStyle }>
|
||||
<Button
|
||||
accessibilityLabel = 'polls.answer.edit'
|
||||
labelKey = 'polls.answer.edit'
|
||||
onClick = { () => {
|
||||
setCreateMode(true);
|
||||
dispatch(editPoll(pollId, true));
|
||||
} }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
type = { SECONDARY } />
|
||||
<Button
|
||||
accessibilityLabel = 'polls.answer.send'
|
||||
labelKey = 'polls.answer.send'
|
||||
onClick = { sendPoll }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
type = { PRIMARY } />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<View style = { chatStyles.buttonRow as ViewStyle }>
|
||||
<Button
|
||||
accessibilityLabel = 'polls.answer.skip'
|
||||
labelKey = 'polls.answer.skip'
|
||||
onClick = { changingVote ? skipChangeVote : skipAnswer }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
type = { SECONDARY } />
|
||||
<Button
|
||||
accessibilityLabel = 'polls.answer.submit'
|
||||
disabled = { isSubmitAnswerDisabled(checkBoxStates) }
|
||||
labelKey = 'polls.answer.submit'
|
||||
onClick = { submitAnswer }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
type = { PRIMARY } />
|
||||
</View>
|
||||
: <View style = { chatStyles.buttonRow as ViewStyle }>
|
||||
<Button
|
||||
accessibilityLabel = 'polls.answer.skip'
|
||||
labelKey = 'polls.answer.skip'
|
||||
onClick = { changingVote ? skipChangeVote : skipAnswer }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
type = { SECONDARY } />
|
||||
<Button
|
||||
accessibilityLabel = 'polls.answer.submit'
|
||||
disabled = { isSubmitAnswerDisabled(checkBoxStates) }
|
||||
labelKey = 'polls.answer.submit'
|
||||
onClick = { submitAnswer }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
type = { PRIMARY } />
|
||||
</View>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,12 +2,14 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { FlatList, Platform, View, ViewStyle } from 'react-native';
|
||||
import { TextInput } from 'react-native-gesture-handler';
|
||||
import { Divider } from 'react-native-paper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import Input from '../../../base/ui/components/native/Input';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import styles
|
||||
from '../../../settings/components/native/styles';
|
||||
import { editPoll } from '../../actions';
|
||||
import { ANSWERS_LIMIT, CHAR_LIMIT } from '../../constants';
|
||||
import AbstractPollCreate, { AbstractProps } from '../AbstractPollCreate';
|
||||
|
||||
@@ -17,6 +19,8 @@ const PollCreate = (props: AbstractProps) => {
|
||||
const {
|
||||
addAnswer,
|
||||
answers,
|
||||
editingPoll,
|
||||
editingPollId,
|
||||
isSubmitDisabled,
|
||||
onSubmit,
|
||||
question,
|
||||
@@ -28,6 +32,7 @@ const PollCreate = (props: AbstractProps) => {
|
||||
} = props;
|
||||
|
||||
const answerListRef = useRef<FlatList>(null);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
/*
|
||||
* This ref stores the Array of answer input fields, allowing us to focus on them.
|
||||
@@ -74,7 +79,7 @@ const PollCreate = (props: AbstractProps) => {
|
||||
// Called on keypress in answer fields
|
||||
const onAnswerKeyDown = useCallback((index: number, ev) => {
|
||||
const { key } = ev.nativeEvent;
|
||||
const currentText = answers[index];
|
||||
const currentText = answers[index].name;
|
||||
|
||||
if (key === 'Backspace' && currentText === '' && answers.length > 1) {
|
||||
removeAnswer(index);
|
||||
@@ -92,35 +97,48 @@ const PollCreate = (props: AbstractProps) => {
|
||||
type = { TERTIARY } />
|
||||
);
|
||||
|
||||
const pollCreateButtonsContainerStyles = Platform.OS === 'android'
|
||||
? chatStyles.pollCreateButtonsContainerAndroid : chatStyles.pollCreateButtonsContainerIos;
|
||||
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
const renderListItem = ({ index }: { index: number; }) =>
|
||||
const renderListItem = ({ index }: { index: number; }) => {
|
||||
|
||||
// padding to take into account the two default options
|
||||
(
|
||||
const isIdenticalAnswer
|
||||
= answers.slice(0, index).length === 0 ? false : answers.slice(0, index).some(prevAnswer =>
|
||||
prevAnswer.name === answers[index].name
|
||||
&& prevAnswer.name !== '' && answers[index].name !== '');
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { dialogStyles.optionContainer as ViewStyle }>
|
||||
<Input
|
||||
blurOnSubmit = { false }
|
||||
bottomLabel = { (
|
||||
isIdenticalAnswer ? t('polls.errors.notUniqueOption', { index: index + 1 }) : '') }
|
||||
error = { isIdenticalAnswer }
|
||||
id = { `polls-answer-input-${index}` }
|
||||
label = { t('polls.create.pollOption', { index: index + 1 }) }
|
||||
maxLength = { CHAR_LIMIT }
|
||||
multiline = { true }
|
||||
onChange = { text => setAnswer(index, text) }
|
||||
onChange = { name => setAnswer(index,
|
||||
{
|
||||
name,
|
||||
voters: []
|
||||
}) }
|
||||
onKeyPress = { ev => onAnswerKeyDown(index, ev) }
|
||||
placeholder = { t('polls.create.answerPlaceholder', { index: index + 1 }) }
|
||||
|
||||
// This is set to help the touch event not be propagated to any subviews.
|
||||
pointerEvents = { 'auto' }
|
||||
ref = { input => registerFieldRef(index, input) }
|
||||
value = { answers[index] } />
|
||||
value = { answers[index].name } />
|
||||
{
|
||||
answers.length > 2
|
||||
&& createRemoveOptionButton(() => removeAnswer(index))
|
||||
}
|
||||
</View>
|
||||
);
|
||||
const pollCreateButtonsContainerStyles = Platform.OS === 'android'
|
||||
? chatStyles.pollCreateButtonsContainerAndroid : chatStyles.pollCreateButtonsContainerIos;
|
||||
};
|
||||
|
||||
return (
|
||||
<View style = { chatStyles.pollCreateContainer as ViewStyle }>
|
||||
@@ -164,13 +182,18 @@ const PollCreate = (props: AbstractProps) => {
|
||||
<Button
|
||||
accessibilityLabel = 'polls.create.cancel'
|
||||
labelKey = 'polls.create.cancel'
|
||||
onClick = { () => setCreateMode(false) }
|
||||
onClick = { () => {
|
||||
setCreateMode(false);
|
||||
editingPollId
|
||||
&& editingPoll?.editing
|
||||
&& dispatch(editPoll(editingPollId, false));
|
||||
} }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
type = { SECONDARY } />
|
||||
<Button
|
||||
accessibilityLabel = 'polls.create.send'
|
||||
accessibilityLabel = 'polls.create.save'
|
||||
disabled = { isSubmitDisabled }
|
||||
labelKey = 'polls.create.send'
|
||||
labelKey = 'polls.create.save'
|
||||
onClick = { onSubmit }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
type = { PRIMARY } />
|
||||
|
||||
@@ -15,9 +15,14 @@ interface IProps {
|
||||
*/
|
||||
pollId: string;
|
||||
|
||||
/**
|
||||
* Create mode control.
|
||||
*/
|
||||
setCreateMode: (mode: boolean) => void;
|
||||
|
||||
}
|
||||
|
||||
const PollItem = ({ pollId }: IProps) => {
|
||||
const PollItem = ({ pollId, setCreateMode }: IProps) => {
|
||||
const showResults = useSelector(shouldShowResults(pollId));
|
||||
|
||||
return (
|
||||
@@ -28,7 +33,8 @@ const PollItem = ({ pollId }: IProps) => {
|
||||
key = { pollId }
|
||||
pollId = { pollId } />
|
||||
: <PollAnswer
|
||||
pollId = { pollId } />
|
||||
pollId = { pollId }
|
||||
setCreateMode = { setCreateMode } />
|
||||
}
|
||||
|
||||
</View>
|
||||
|
||||
@@ -12,7 +12,11 @@ import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
import PollItem from './PollItem';
|
||||
import { chatStyles } from './styles';
|
||||
|
||||
const PollsList = () => {
|
||||
interface IPollListProps {
|
||||
setCreateMode: (mode: boolean) => void;
|
||||
}
|
||||
|
||||
const PollsList = ({ setCreateMode }: IPollListProps) => {
|
||||
const polls = useSelector((state: IReduxState) => state['features/polls'].polls);
|
||||
const { t } = useTranslation();
|
||||
const listPolls = Object.keys(polls);
|
||||
@@ -20,7 +24,8 @@ const PollsList = () => {
|
||||
const renderItem = useCallback(({ item }) => (
|
||||
<PollItem
|
||||
key = { item }
|
||||
pollId = { item } />)
|
||||
pollId = { item }
|
||||
setCreateMode = { setCreateMode } />)
|
||||
, []);
|
||||
|
||||
const flatlistRef = useRef<FlatList>(null);
|
||||
|
||||
@@ -53,21 +53,20 @@ const PollsPane = (props: AbstractProps) => {
|
||||
{
|
||||
createMode
|
||||
? <PollCreate setCreateMode = { setCreateMode } />
|
||||
: <PollsList />
|
||||
}
|
||||
{
|
||||
!createMode && <Button
|
||||
accessibilityLabel = 'polls.create.create'
|
||||
labelKey = 'polls.create.create'
|
||||
onClick = { onCreate }
|
||||
style = { createPollButtonStyles }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
: <>
|
||||
<PollsList setCreateMode = { setCreateMode } />
|
||||
<Button
|
||||
accessibilityLabel = 'polls.create.create'
|
||||
labelKey = 'polls.create.create'
|
||||
onClick = { onCreate }
|
||||
style = { createPollButtonStyles }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
</>
|
||||
}
|
||||
</JitsiScreen>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* We apply AbstractPollsPane to fill in the AbstractProps common
|
||||
* to both the web and native implementations.
|
||||
|
||||
@@ -209,7 +209,8 @@ export const chatStyles = createStyleSheet({
|
||||
},
|
||||
|
||||
createPollButtonAndroid: {
|
||||
marginHorizontal: 20
|
||||
marginHorizontal: 20,
|
||||
marginVertical: BaseTheme.spacing[5]
|
||||
},
|
||||
|
||||
pollPane: {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import Checkbox from '../../../base/ui/components/web/Checkbox';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.web';
|
||||
import { editPoll } from '../../actions';
|
||||
import { isSubmitAnswerDisabled } from '../../functions';
|
||||
import AbstractPollAnswer, { AbstractProps } from '../AbstractPollAnswer';
|
||||
|
||||
@@ -53,13 +57,18 @@ const PollAnswer = ({
|
||||
creatorName,
|
||||
checkBoxStates,
|
||||
poll,
|
||||
pollId,
|
||||
setCheckbox,
|
||||
setCreateMode,
|
||||
skipAnswer,
|
||||
skipChangeVote,
|
||||
sendPoll,
|
||||
submitAnswer,
|
||||
t
|
||||
}: AbstractProps) => {
|
||||
const { changingVote } = poll;
|
||||
const { changingVote, saved: pollSaved } = poll;
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { classes } = useStyles();
|
||||
|
||||
return (
|
||||
@@ -74,32 +83,50 @@ const PollAnswer = ({
|
||||
</div>
|
||||
<ul className = { classes.answerList }>
|
||||
{
|
||||
poll.answers.map((answer: any, index: number) => (
|
||||
poll.answers.map((answer, index: number) => (
|
||||
<li
|
||||
className = { classes.answer }
|
||||
key = { index }>
|
||||
<Checkbox
|
||||
checked = { checkBoxStates[index] }
|
||||
disabled = { poll.saved }
|
||||
key = { index }
|
||||
label = { answer.name }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onChange = { ev => setCheckbox(index, ev.target.checked) } />
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
<div className = { classes.footer } >
|
||||
<Button
|
||||
accessibilityLabel = { t('polls.answer.skip') }
|
||||
className = { classes.buttonMargin }
|
||||
labelKey = { 'polls.answer.skip' }
|
||||
onClick = { changingVote ? skipChangeVote : skipAnswer }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
<Button
|
||||
accessibilityLabel = { t('polls.answer.submit') }
|
||||
disabled = { isSubmitAnswerDisabled(checkBoxStates) }
|
||||
labelKey = { 'polls.answer.submit' }
|
||||
onClick = { submitAnswer } />
|
||||
{
|
||||
pollSaved ? <>
|
||||
<Button
|
||||
accessibilityLabel = { t('polls.answer.edit') }
|
||||
className = { classes.buttonMargin }
|
||||
labelKey = { 'polls.answer.edit' }
|
||||
onClick = { () => {
|
||||
setCreateMode(true);
|
||||
dispatch(editPoll(pollId, true));
|
||||
} }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
<Button
|
||||
accessibilityLabel = { t('polls.answer.send') }
|
||||
labelKey = { 'polls.answer.send' }
|
||||
onClick = { sendPoll } />
|
||||
</> : <>
|
||||
<Button
|
||||
accessibilityLabel = { t('polls.answer.skip') }
|
||||
className = { classes.buttonMargin }
|
||||
labelKey = { 'polls.answer.skip' }
|
||||
onClick = { changingVote ? skipChangeVote : skipAnswer }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
<Button
|
||||
accessibilityLabel = { t('polls.answer.submit') }
|
||||
disabled = { isSubmitAnswerDisabled(checkBoxStates) }
|
||||
labelKey = { 'polls.answer.submit' }
|
||||
onClick = { submitAnswer } />
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user