mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-09 08:10:18 +00:00
Compare commits
94 Commits
8017
...
testing1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e049cfc85a | ||
|
|
7bc9913b29 | ||
|
|
2483d901d6 | ||
|
|
6ff7995cee | ||
|
|
5d563402d0 | ||
|
|
6727004930 | ||
|
|
c04000ea20 | ||
|
|
23be14697c | ||
|
|
ca07eed85f | ||
|
|
72779e5ba5 | ||
|
|
1b3b949218 | ||
|
|
d510390edc | ||
|
|
1de1381847 | ||
|
|
639114f2e1 | ||
|
|
411e9a2372 | ||
|
|
b4e4dd1aa9 | ||
|
|
81ba2331b0 | ||
|
|
50d84bfd2c | ||
|
|
60b4581cb5 | ||
|
|
2514617417 | ||
|
|
b242900619 | ||
|
|
3a40b52832 | ||
|
|
4a25b9722c | ||
|
|
dbbc7b2e89 | ||
|
|
a3c3b38993 | ||
|
|
94b6808ec6 | ||
|
|
1376f5909c | ||
|
|
74b02af318 | ||
|
|
de1e470c68 | ||
|
|
4ee613ed1f | ||
|
|
fb6a44a39b | ||
|
|
bde28105f4 | ||
|
|
782d46b4a6 | ||
|
|
160d6a4c52 | ||
|
|
767101497c | ||
|
|
889b37cedc | ||
|
|
4e727e9093 | ||
|
|
7fbf47c6f3 | ||
|
|
2d61c68615 | ||
|
|
d2ad3473a1 | ||
|
|
67f49815c4 | ||
|
|
2697eb1273 | ||
|
|
491f793530 | ||
|
|
eb0317fb8d | ||
|
|
59da1537be | ||
|
|
9e1e6237ce | ||
|
|
5c0b8467d5 | ||
|
|
b4a5e63d1d | ||
|
|
3ae50b6c4c | ||
|
|
bc9525a908 | ||
|
|
c6dcac47a8 | ||
|
|
f9f5cf87b9 | ||
|
|
b969fba433 | ||
|
|
f0fc63f573 | ||
|
|
d618175074 | ||
|
|
9ebe2c4395 | ||
|
|
e5189a5c1c | ||
|
|
f96592b4dc | ||
|
|
a11a281bf7 | ||
|
|
8ddab7464c | ||
|
|
9076fb3e4a | ||
|
|
d8079a4232 | ||
|
|
c992a8274c | ||
|
|
b5e059a0a9 | ||
|
|
0be3e2b103 | ||
|
|
ae138c1a15 | ||
|
|
0f8de50d26 | ||
|
|
ff7b6010bd | ||
|
|
270cdd017d | ||
|
|
f4cae6350b | ||
|
|
f0126a43f3 | ||
|
|
b5deb65815 | ||
|
|
6d9bbe0376 | ||
|
|
da634c211e | ||
|
|
61d96a5fd5 | ||
|
|
07e46b9399 | ||
|
|
3139111d36 | ||
|
|
205f88ec5a | ||
|
|
c8d6cdf6c8 | ||
|
|
7022243a95 | ||
|
|
b2ae72249d | ||
|
|
8ee6d179d5 | ||
|
|
bebcfa3fd7 | ||
|
|
d0130f9975 | ||
|
|
efd2db70ca | ||
|
|
befffa7e85 | ||
|
|
0368b4d671 | ||
|
|
acccd760d1 | ||
|
|
7696dbc347 | ||
|
|
e25ceebeec | ||
|
|
c7ee6280d2 | ||
|
|
796a2217aa | ||
|
|
e9bf1ada51 | ||
|
|
667b9fba67 |
173
CONTRIBUTING.md
173
CONTRIBUTING.md
@@ -1,171 +1,14 @@
|
||||
# How to contribute
|
||||
We would love to have your help. Before you start working however, please read
|
||||
and follow this short guide.
|
||||
# Follow Our Updated Guide to See How You Can Contribute
|
||||
|
||||
# Reporting Issues
|
||||
Provide as much information as possible. Mention the version of Jitsi Meet,
|
||||
Jicofo and JVB you are using, and explain (as detailed as you can) how the
|
||||
problem can be reproduced.
|
||||
Hello there! 👋
|
||||
|
||||
# Code contributions
|
||||
Found a bug and know how to fix it? Great! Please read on.
|
||||
We're thrilled that you're eager to contribute to Jitsi Meet! ❤️
|
||||
|
||||
## Contributor License Agreement
|
||||
While the Jitsi projects are released under the
|
||||
[Apache License 2.0](https://github.com/jitsi/jitsi-meet/blob/master/LICENSE), the copyright
|
||||
holder and principal creator is [8x8](https://www.8x8.com/). To
|
||||
ensure that we can continue making these projects available under an Open Source license,
|
||||
we need you to sign our Apache-based contributor
|
||||
license agreement as either a [corporation](https://jitsi.org/ccla) or an
|
||||
[individual](https://jitsi.org/icla). If you cannot accept the terms laid out
|
||||
in the agreement, unfortunately, we cannot accept your contribution.
|
||||
Your interest in improving our platform means a lot to us. To ensure your contributions align seamlessly with our goals and processes, we've recently updated our guide. This guide will provide you with clear instructions on how to get involved effectively.
|
||||
|
||||
## Creating Pull Requests
|
||||
- Make sure your code passes the linter rules beforehand. The linter is executed
|
||||
automatically when committing code.
|
||||
- Perform **one** logical change per pull request.
|
||||
- Maintain a clean list of commits, squash them if necessary.
|
||||
- Rebase your topic branch on top of the master branch before creating the pull
|
||||
request.
|
||||
Ready to get started? Head over to our [Jitsi Meet Handbook](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-contributing/) and let's make Jitsi Meet even better together!
|
||||
|
||||
## Coding style
|
||||
### ❗️Additional Note
|
||||
Before sending us your code, double-check that it meets our coding standards. You can do this by running a command: `npm run lint`. If there are any issues, don't worry! You can fix them by running: `npm run lint-fix`. Once your code passes these checks, feel free to submit your pull request.
|
||||
|
||||
### Comments
|
||||
|
||||
* Comments documenting the source code are required.
|
||||
|
||||
* Comments from which documentation is automatically generated are **not**
|
||||
subject to case-by-case decisions. Such comments are used, for example, on
|
||||
types and their members. Examples of tools which automatically generate
|
||||
documentation from such comments include JSDoc, Javadoc, Doxygen.
|
||||
|
||||
* Comments which are not automatically processed are strongly encouraged. They
|
||||
are subject to case-by-case decisions. Such comments are often observed in
|
||||
function bodies.
|
||||
|
||||
* Comments should be formatted as proper English sentences. Such formatting pays
|
||||
attention to, for example, capitalization and punctuation.
|
||||
|
||||
### Duplication
|
||||
|
||||
* Don't copy-paste source code. Reuse it.
|
||||
|
||||
### Formatting
|
||||
|
||||
* Line length is limited to 120 characters.
|
||||
|
||||
* Sort by alphabetical order in order to make the addition of new entities as
|
||||
easy as looking a word up in a dictionary. Otherwise, one risks duplicate
|
||||
entries (with conflicting values in the cases of key-value pairs). For
|
||||
example:
|
||||
|
||||
* Within an `import` of multiple names from a module, sort the names in
|
||||
alphabetical order. (Of course, the default name stays first as required by
|
||||
the `import` syntax.)
|
||||
|
||||
````javascript
|
||||
import {
|
||||
DOMINANT_SPEAKER_CHANGED,
|
||||
JITSI_CLIENT_CONNECTED,
|
||||
JITSI_CLIENT_CREATED,
|
||||
JITSI_CLIENT_DISCONNECTED,
|
||||
JITSI_CLIENT_ERROR,
|
||||
JITSI_CONFERENCE_JOINED,
|
||||
MODERATOR_CHANGED,
|
||||
PEER_JOINED,
|
||||
PEER_LEFT,
|
||||
RTC_ERROR
|
||||
} from './actionTypes';
|
||||
````
|
||||
|
||||
* Within a group of imports (e.g. groups of imports delimited by an empty line
|
||||
may be: third-party modules, then project modules, and eventually the
|
||||
private files of a module), sort the module names in alphabetical order.
|
||||
|
||||
````javascript
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
````
|
||||
|
||||
### Indentation
|
||||
|
||||
* Align `switch` and `case`/`default`. Don't indent the `case`/`default` more
|
||||
than its `switch`.
|
||||
|
||||
````javascript
|
||||
switch (i) {
|
||||
case 0:
|
||||
...
|
||||
break;
|
||||
default:
|
||||
...
|
||||
}
|
||||
````
|
||||
|
||||
### Naming
|
||||
|
||||
* An abstraction should have one name within the project and across multiple
|
||||
projects. For example:
|
||||
|
||||
* The instance of lib-jitsi-meet's `JitsiConnection` type should be named
|
||||
`connection` or `jitsiConnection` in jitsi-meet, not `client`.
|
||||
|
||||
* The class `ReducerRegistry` should be defined in ReducerRegistry.js and its
|
||||
imports in other files should use the same name. Don't define the class
|
||||
`Registry` in ReducerRegistry.js and then import it as `Reducers` in other
|
||||
files.
|
||||
|
||||
* The names of global constants (including ES6 module-global constants) should
|
||||
be written in uppercase with underscores to separate words. For example,
|
||||
`BACKGROUND_COLOR`.
|
||||
|
||||
* The underscore character at the beginning of a name signals that the
|
||||
respective variable, function, property is non-public i.e. private, protected,
|
||||
or internal. In contrast, the lack of an underscore at the beginning of a name
|
||||
signals public API.
|
||||
|
||||
### Feature layout
|
||||
|
||||
When adding a new feature, this would be the usual layout.
|
||||
|
||||
```
|
||||
react/features/sample/
|
||||
├── actionTypes.ts
|
||||
├── actions.js
|
||||
├── components
|
||||
│ ├── AnotherComponent.js
|
||||
│ ├── OneComponent.js
|
||||
│ └── index.js
|
||||
├── middleware.js
|
||||
└── reducer.js
|
||||
```
|
||||
|
||||
The middleware must be imported in `react/features/app/` specifically
|
||||
in `middlewares.any.ts`, `middlewares.native.ts` or `middlewares.web.ts` where appropriate.
|
||||
Likewise for the reducer.
|
||||
|
||||
An `index.js` file must not be provided for exporting actions, action types and
|
||||
component. Features / files requiring those must import them explicitly.
|
||||
|
||||
This has not always been the case and the entire codebase hasn't been migrated to
|
||||
this model but new features should follow this new layout.
|
||||
|
||||
When working on an old feature, adding the necessary changes to migrate to the new
|
||||
model is encouraged.
|
||||
|
||||
|
||||
### Avoiding bundle bloat
|
||||
|
||||
When adding a new feature it's possible that it triggers a build failure due to the increased bundle size. We have safeguards inplace to avoid bundles growing disproportionatelly. While there are legit reasons for increasing the limits, please analyze the bundle first to make sure no unintended dependencies have been included, causing the increase in size.
|
||||
|
||||
First, make a production build with bundle-analysis enabled:
|
||||
|
||||
```
|
||||
npx webpack -p --analyze-bundle
|
||||
```
|
||||
|
||||
Then open the interactive bundle analyzer tool:
|
||||
|
||||
```
|
||||
npx webpack-bundle-analyzer build/app-stats.json
|
||||
```
|
||||
Happy coding!
|
||||
|
||||
2
Makefile
2
Makefile
@@ -122,7 +122,7 @@ deploy-local:
|
||||
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-olm deploy-tf-wasm deploy-excalidraw-dev deploy-face-landmarks
|
||||
$(WEBPACK_DEV_SERVER)
|
||||
|
||||
source-package:
|
||||
source-package: compile deploy
|
||||
mkdir -p source_package/jitsi-meet/css && \
|
||||
cp -r *.js *.html resources/*.txt fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
|
||||
cp css/all.css source_package/jitsi-meet/css && \
|
||||
|
||||
@@ -150,7 +150,6 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
= new JitsiMeetConferenceOptions.Builder()
|
||||
.setServerURL(buildURL(defaultURL))
|
||||
.setFeatureFlag("welcomepage.enabled", true)
|
||||
.setFeatureFlag("resolution", 360)
|
||||
.setFeatureFlag("server-url-change.enabled", !configurationByRestrictions)
|
||||
.build();
|
||||
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
|
||||
|
||||
@@ -44,7 +44,7 @@ ext {
|
||||
googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild
|
||||
|
||||
//React Native Version
|
||||
rnVersion = "0.72.9"
|
||||
rnVersion = "0.73.8"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
||||
@@ -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
|
||||
@@ -26,5 +26,5 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.bundle.enableUncompressedNativeLibs=false
|
||||
|
||||
appVersion=99.0.0
|
||||
sdkVersion=99.0.0
|
||||
appVersion=24.3.0
|
||||
sdkVersion=9.3.0
|
||||
|
||||
@@ -47,7 +47,7 @@ dependencies {
|
||||
|
||||
implementation 'com.facebook.fresco:animated-gif:2.5.0'
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.jakewharton.timber:timber:5.0.1'
|
||||
implementation 'com.squareup.duktape:duktape-android:1.3.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'androidx.startup:startup-runtime:1.1.0'
|
||||
|
||||
@@ -294,8 +294,8 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
|
||||
@Override
|
||||
protected void onUserLeaveHint() {
|
||||
if (this.jitsiView != null) {
|
||||
this.jitsiView .enterPictureInPicture();
|
||||
if (this.jitsiView != null) {
|
||||
this.jitsiView.enterPictureInPicture();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import static android.Manifest.permission.POST_NOTIFICATIONS;
|
||||
import static android.Manifest.permission.RECORD_AUDIO;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
@@ -38,7 +39,9 @@ import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
@@ -57,7 +60,7 @@ 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 static final int PERMISSIONS_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE);
|
||||
|
||||
private boolean isAudioMuted;
|
||||
|
||||
@@ -95,26 +98,50 @@ 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);
|
||||
List<String> permissionsList = new ArrayList<>();
|
||||
|
||||
PermissionListener listener = new PermissionListener() {
|
||||
@Override
|
||||
public boolean onRequestPermissionsResult(int i, String[] strings, int[] results) {
|
||||
int counter = 0;
|
||||
|
||||
if (results.length > 0) {
|
||||
for (int result : results) {
|
||||
if (result == PackageManager.PERMISSION_GRANTED) {
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
if (counter == results.length){
|
||||
doLaunch(context, extraData);
|
||||
JitsiMeetLogger.w(TAG + " Service launched, permissions were granted");
|
||||
} else {
|
||||
JitsiMeetLogger.w(TAG + " Couldn't launch service, permissions were not granted");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
permissionsList.add(POST_NOTIFICATIONS);
|
||||
permissionsList.add(RECORD_AUDIO);
|
||||
}
|
||||
|
||||
String[] permissionsArray = new String[ permissionsList.size() ];
|
||||
permissionsArray = permissionsList.toArray( permissionsArray );
|
||||
|
||||
if (permissionsArray.length > 0) {
|
||||
JitsiMeetActivityDelegate.requestPermissions(
|
||||
(Activity) context,
|
||||
new String[]{POST_NOTIFICATIONS},
|
||||
POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE,
|
||||
permissionsArray,
|
||||
PERMISSIONS_REQUEST_CODE,
|
||||
listener
|
||||
);
|
||||
} else {
|
||||
doLaunch(context, extraData);
|
||||
JitsiMeetLogger.w(TAG + " Service launched");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,8 +159,10 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
|
||||
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ project(':react-native-background-timer').projectDir = new File(rootProject.proj
|
||||
include ':react-native-calendar-events'
|
||||
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')
|
||||
include ':react-native-community_clipboard'
|
||||
project(':react-native-community_clipboard').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/clipboard/android')
|
||||
project(':react-native-community_clipboard').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-clipboard/clipboard/android')
|
||||
include ':react-native-community_netinfo'
|
||||
project(':react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android')
|
||||
include ':react-native-default-preference'
|
||||
|
||||
@@ -83,6 +83,7 @@ import {
|
||||
setAudioAvailable,
|
||||
setAudioMuted,
|
||||
setAudioUnmutePermissions,
|
||||
setInitialGUMPromise,
|
||||
setVideoAvailable,
|
||||
setVideoMuted,
|
||||
setVideoUnmutePermissions
|
||||
@@ -154,8 +155,7 @@ import {
|
||||
import { isModerationNotificationDisplayed } from './react/features/notifications/functions';
|
||||
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
|
||||
import { suspendDetected } from './react/features/power-monitor/actions';
|
||||
import { initPrejoin } from './react/features/prejoin/actions';
|
||||
import { isPrejoinPageVisible } from './react/features/prejoin/functions';
|
||||
import { initPrejoin, isPrejoinPageVisible } from './react/features/prejoin/functions';
|
||||
import { disableReceiver, stopReceiver } from './react/features/remote-control/actions';
|
||||
import { setScreenAudioShareState } from './react/features/screen-share/actions.web';
|
||||
import { isScreenAudioShared } from './react/features/screen-share/functions';
|
||||
@@ -591,7 +591,7 @@ export default {
|
||||
const handleInitialTracks = (options, tracks) => {
|
||||
let localTracks = tracks;
|
||||
|
||||
if (options.startWithAudioMuted || room?.isStartAudioMuted()) {
|
||||
if (options.startWithAudioMuted) {
|
||||
// Always add the track on Safari because of a known issue where audio playout doesn't happen
|
||||
// if the user joins audio and video muted, i.e., if there is no local media capture.
|
||||
if (browser.isWebKitBased()) {
|
||||
@@ -600,58 +600,38 @@ export default {
|
||||
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
|
||||
}
|
||||
}
|
||||
if (room?.isStartVideoMuted()) {
|
||||
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
|
||||
}
|
||||
|
||||
return localTracks;
|
||||
};
|
||||
|
||||
if (isPrejoinPageVisible(state)) {
|
||||
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
|
||||
const localTracks = await tryCreateLocalTracks;
|
||||
|
||||
// Initialize device list a second time to ensure device labels get populated in case of an initial gUM
|
||||
// acceptance; otherwise they may remain as empty strings.
|
||||
this._initDeviceList(true);
|
||||
|
||||
if (isPrejoinPageVisible(state)) {
|
||||
APP.store.dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
|
||||
|
||||
return APP.store.dispatch(initPrejoin(localTracks, errors));
|
||||
}
|
||||
|
||||
logger.debug('Prejoin screen no longer displayed at the time when tracks were created');
|
||||
|
||||
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
|
||||
|
||||
const tracks = handleInitialTracks(initialOptions, localTracks);
|
||||
|
||||
setGUMPendingStateOnFailedTracks(tracks, APP.store.dispatch);
|
||||
|
||||
return this._setLocalAudioVideoStreams(tracks);
|
||||
}
|
||||
|
||||
const { dispatch, getState } = APP.store;
|
||||
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
|
||||
|
||||
return Promise.all([
|
||||
tryCreateLocalTracks.then(tr => {
|
||||
dispatch(setInitialGUMPromise(tryCreateLocalTracks.then(async tr => {
|
||||
const tracks = handleInitialTracks(initialOptions, tr);
|
||||
|
||||
this._initDeviceList(true);
|
||||
|
||||
if (isPrejoinPageVisible(getState())) {
|
||||
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
|
||||
dispatch(setInitialGUMPromise());
|
||||
|
||||
// Note: Not sure if initPrejoin needs to be async. But let's wait for it just to be sure the
|
||||
// tracks are added.
|
||||
initPrejoin(tracks, errors, dispatch);
|
||||
} else {
|
||||
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
|
||||
setGUMPendingStateOnFailedTracks(tracks, APP.store.dispatch);
|
||||
}
|
||||
|
||||
return tr;
|
||||
}).then(tr => {
|
||||
this._initDeviceList(true);
|
||||
return {
|
||||
tracks,
|
||||
errors
|
||||
};
|
||||
})));
|
||||
|
||||
const filteredTracks = handleInitialTracks(initialOptions, tr);
|
||||
|
||||
setGUMPendingStateOnFailedTracks(filteredTracks, APP.store.dispatch);
|
||||
|
||||
return filteredTracks;
|
||||
}),
|
||||
APP.store.dispatch(connect())
|
||||
]).then(([ tracks, _ ]) => {
|
||||
this.startConference(tracks).catch(logger.error);
|
||||
});
|
||||
if (!isPrejoinPageVisible(getState())) {
|
||||
dispatch(connect());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -705,11 +685,13 @@ export default {
|
||||
|
||||
/**
|
||||
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
|
||||
*
|
||||
* @param {boolean} mute true for mute and false for unmute.
|
||||
* @param {boolean} [showUI] when set to false will not display any error
|
||||
* dialogs in case of media permissions error.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
muteAudio(mute, showUI = true) {
|
||||
async muteAudio(mute, showUI = true) {
|
||||
const state = APP.store.getState();
|
||||
|
||||
if (!mute
|
||||
@@ -749,7 +731,8 @@ export default {
|
||||
};
|
||||
|
||||
APP.store.dispatch(gumPending([ MEDIA_TYPE.AUDIO ], IGUMPendingState.PENDING_UNMUTE));
|
||||
createLocalTracksF({ devices: [ 'audio' ] })
|
||||
|
||||
await createLocalTracksF({ devices: [ 'audio' ] })
|
||||
.then(([ audioTrack ]) => audioTrack)
|
||||
.catch(error => {
|
||||
maybeShowErrorDialog(error);
|
||||
@@ -1162,11 +1145,12 @@ export default {
|
||||
APP.store.dispatch(gumPending(mutedTrackTypes, IGUMPendingState.NONE));
|
||||
}
|
||||
|
||||
this._setLocalAudioVideoStreams(tracks);
|
||||
this._room = room; // FIXME do not use this
|
||||
|
||||
APP.store.dispatch(_conferenceWillJoin(room));
|
||||
|
||||
this._setLocalAudioVideoStreams(tracks);
|
||||
|
||||
sendLocalParticipant(APP.store, room);
|
||||
|
||||
this._setupListeners();
|
||||
@@ -1277,8 +1261,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(
|
||||
replaceLocalTrack(oldTrack, newTrack, room))
|
||||
APP.store.dispatch(replaceLocalTrack(oldTrack, newTrack, room))
|
||||
.then(() => {
|
||||
this.updateAudioIconEnabled();
|
||||
})
|
||||
@@ -1676,6 +1659,18 @@ export default {
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.SILENT_STATUS_CHANGED,
|
||||
(id, isSilent) => {
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id,
|
||||
isSilent
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.BOT_TYPE_CHANGED,
|
||||
(id, botType) => {
|
||||
|
||||
22
config.js
22
config.js
@@ -88,11 +88,8 @@ var config = {
|
||||
// issues related to insertable streams.
|
||||
// disableE2EE: false,
|
||||
|
||||
// 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
|
||||
// Enables the use of the codec selection API supported by the browsers .
|
||||
// enableCodecSelectionAPI: false,
|
||||
|
||||
// P2P test mode disables automatic switching to P2P when there are 2
|
||||
// participants in the conference.
|
||||
@@ -124,6 +121,9 @@ var config = {
|
||||
// Disables polls feature.
|
||||
// disablePolls: false,
|
||||
|
||||
// Disables demote button from self-view
|
||||
// disableSelfDemote: false,
|
||||
|
||||
// Disables self-view tile. (hides it from tile view and from filmstrip)
|
||||
// disableSelfView: false,
|
||||
|
||||
@@ -461,6 +461,10 @@ var config = {
|
||||
// // Provides a way to set the codec preference on desktop based endpoints.
|
||||
// codecPreferenceOrder: [ 'VP9', 'VP8', 'H264' ],
|
||||
//
|
||||
// // Provides a way to set the codec for screenshare.
|
||||
// screenshareCodec: 'AV1',
|
||||
// mobileScreenshareCodec: 'VP8',
|
||||
//
|
||||
// // Codec specific settings for scalability modes and max bitrates.
|
||||
// av1: {
|
||||
// maxBitratesVideo: {
|
||||
@@ -509,7 +513,7 @@ var config = {
|
||||
// scalabilityModeEnabled: true,
|
||||
// useSimulcast: false,
|
||||
// useKSVC: true
|
||||
// }
|
||||
// },
|
||||
//
|
||||
// DEPRECATED! Use `codec specific settings` instead.
|
||||
// // Provides a way to configure the maximum bitrates that will be enforced on the simulcast streams for
|
||||
@@ -1050,6 +1054,10 @@ var config = {
|
||||
// Provides a way to set the codec preference on desktop based endpoints.
|
||||
// codecPreferenceOrder: [ 'VP9', 'VP8', 'H264 ],
|
||||
|
||||
// Provides a way to set the codec for screenshare.
|
||||
// screenshareCodec: 'AV1',
|
||||
// mobileScreenshareCodec: 'VP8',
|
||||
|
||||
// How long we're going to wait, before going back to P2P after the 3rd
|
||||
// participant has left the conference (to filter out page reload).
|
||||
// backToP2PDelay: 5,
|
||||
@@ -1770,7 +1778,7 @@ var config = {
|
||||
// // to control the performance.
|
||||
// userLimit: 25,
|
||||
// // The url for more info about the whiteboard and its usage limitations.
|
||||
// limitUrl: 'https://example.com/blog/whiteboard-limits,
|
||||
// limitUrl: 'https://example.com/blog/whiteboard-limits',
|
||||
// },
|
||||
|
||||
// The watchRTC initialize config params as described :
|
||||
|
||||
@@ -10,6 +10,10 @@ workspace 'jitsi-meet'
|
||||
|
||||
install! 'cocoapods', :deterministic_uuids => false
|
||||
|
||||
def cocoa_utilities
|
||||
pod 'CocoaLumberjack', '3.7.4'
|
||||
end
|
||||
|
||||
target 'JitsiMeet' do
|
||||
project 'app/app.xcodeproj'
|
||||
|
||||
@@ -45,7 +49,7 @@ target 'JitsiMeetSDK' do
|
||||
# Native pod dependencies
|
||||
#
|
||||
|
||||
pod 'CocoaLumberjack', '3.7.2'
|
||||
cocoa_utilities
|
||||
pod 'ObjectiveDropboxOfficial', '6.2.3'
|
||||
end
|
||||
|
||||
@@ -70,7 +74,7 @@ target 'JitsiMeetSDKLite' do
|
||||
# Native pod dependencies
|
||||
#
|
||||
|
||||
pod 'CocoaLumberjack', '3.7.2'
|
||||
cocoa_utilities
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
@@ -79,7 +83,6 @@ post_install do |installer|
|
||||
use_native_modules![:reactNativePath],
|
||||
:mac_catalyst_enabled => false
|
||||
)
|
||||
__apply_Xcode_12_5_M1_post_install_workaround(installer)
|
||||
installer.pods_project.targets.each do |target|
|
||||
# https://github.com/CocoaPods/CocoaPods/issues/11402
|
||||
if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"
|
||||
|
||||
1275
ios/Podfile.lock
1275
ios/Podfile.lock
File diff suppressed because it is too large
Load Diff
@@ -375,6 +375,7 @@
|
||||
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
|
||||
0BB7DA181EC9E695007AAE98 /* Adjust ATS */,
|
||||
DE4F6D6E22005C0400DE699E /* Setup Dropbox */,
|
||||
E9D850368D253EFA8AB3B8D1 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -611,6 +612,23 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "INFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nDROPBOX_KEY_FILE=\"$PROJECT_DIR/dropbox.key\"\n\nif [[ -f $DROPBOX_KEY_FILE ]]; then\n /usr/libexec/PlistBuddy -c \"Delete :LSApplicationQueriesSchemes\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes array\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes:0 string 'dbapi-2'\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes:1 string 'dbapi-8-emm'\" $INFO_PLIST\n\n DROPBOX_KEY=$(head -n 1 $DROPBOX_KEY_FILE)\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLName string dropbox\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLSchemes array\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLSchemes:0 string $DROPBOX_KEY\" $INFO_PLIST\nfi\n";
|
||||
};
|
||||
E9D850368D253EFA8AB3B8D1 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -968,7 +986,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
@@ -1025,6 +1043,7 @@
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
USE_HERMES = false;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1033,7 +1052,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
@@ -1086,6 +1105,7 @@
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
USE_HERMES = false;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<string>24.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSExtension</key>
|
||||
|
||||
@@ -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];
|
||||
}];
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<string>24.3.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<string>24.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<string>24.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -730,7 +730,7 @@
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
@@ -785,6 +785,7 @@
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
USE_HERMES = false;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
@@ -797,7 +798,7 @@
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
@@ -849,6 +850,7 @@
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
USE_HERMES = false;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<string>9.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<string>9.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"hsb": "Hornjoserbšćina",
|
||||
"hu": "Magyar",
|
||||
"hy": "Հայերեն",
|
||||
"id": "Bahasa",
|
||||
"is": "Íslenska",
|
||||
"it": "Italiano",
|
||||
"ja": "日本語",
|
||||
|
||||
@@ -925,7 +925,7 @@
|
||||
"iWantToDialIn": "Mi volas alvoki",
|
||||
"initiated": "Voko komencita",
|
||||
"joinAudioByPhone": "Aliĝu kun telefona mikrofono",
|
||||
"joinMeeting": "Aliĝu al la kunvenon",
|
||||
"joinMeeting": "Aliĝu al la kunveno",
|
||||
"joinMeetingInLowBandwidthMode": "Aliĝu en malaltkapacita modo",
|
||||
"joinWithoutAudio": "Aliĝu sen mikrofono",
|
||||
"keyboardShortcuts": "Ŝaltu fulmoklavojn",
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
"privateNotice": "Message privé à {{recipient}}",
|
||||
"sendButton": "Envoyer",
|
||||
"smileysPanel": "Panneaux des Émojis",
|
||||
"systemDisplayName": "Système",
|
||||
"tabs": {
|
||||
"chat": "Chat",
|
||||
"polls": "Sondages"
|
||||
@@ -219,7 +220,9 @@
|
||||
"joinInBrowser": "Rejoindre depuis le navigateur",
|
||||
"launchMeetingLabel": "Comment voulez-vous rejoindre la réunion ?",
|
||||
"launchWebButton": "Lancer dans le navigateur",
|
||||
"noDesktopApp": "Vous n'avez pas l'application ?",
|
||||
"noMobileApp": "Vous n’avez pas l’application ?",
|
||||
"or": "OU",
|
||||
"termsAndConditions": "En continuant, vous acceptez nos <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>conditions générales d’utilisation.</a>",
|
||||
"title": "Lancement de votre réunion dans {{app}} en cours ...",
|
||||
"titleNew": "Lancement de votre réunion ...",
|
||||
@@ -261,6 +264,7 @@
|
||||
"Share": "Partager",
|
||||
"Submit": "Soumettre",
|
||||
"WaitForHostMsg": "La conférence n'a pas encore commencé. Si vous en êtes l'hôte, veuillez vous authentifier. Sinon, veuillez attendre son arrivée.",
|
||||
"WaitForHostNoAuthMsg": "La conférence n'a pas encore commencé car aucun modérateur n'est encore arrivé. Veuillez patienter.",
|
||||
"WaitingForHostButton": "Attendre l'hôte",
|
||||
"WaitingForHostTitle": "En attente de l'hôte ...",
|
||||
"Yes": "Oui",
|
||||
@@ -303,6 +307,8 @@
|
||||
"contactSupport": "Contacter le support",
|
||||
"copied": "Copié",
|
||||
"copy": "Copier",
|
||||
"demoteParticipantDialog": "Êtes-vous sûr de vouloir déplacer ce participant en visiteur ?",
|
||||
"demoteParticipantTitle": "Déplacer en visiteur",
|
||||
"dismiss": "Rejeter",
|
||||
"displayNameRequired": "Bonjour ! Quel est votre nom ?",
|
||||
"done": "Terminé",
|
||||
@@ -314,6 +320,7 @@
|
||||
"embedMeeting": "Intégrer la réunion",
|
||||
"enterDisplayName": "Merci de saisir votre nom ici",
|
||||
"error": "Erreur",
|
||||
"errorRoomCreationRestriction": "Vous avez essayé de rejoindre trop rapidement, veuillez revenir dans un moment.",
|
||||
"gracefulShutdown": "Notre service est actuellement en maintenance. Veuillez réessayer plus tard.",
|
||||
"grantModeratorDialog": "Êtes-vous sûr de vouloir rendre ce participant modérateur ?",
|
||||
"grantModeratorTitle": "Nommer modérateur",
|
||||
@@ -558,6 +565,7 @@
|
||||
"noNumbers": "Numéros non trouvés",
|
||||
"noPassword": "Aucun",
|
||||
"noRoom": "Aucune réunion n'a été spécifiée pour l'appel entrant.",
|
||||
"noWhiteboard": "Impossible de charger le tableau blanc.",
|
||||
"numbers": "Numéros d'appel",
|
||||
"password": "$t(lockRoomPasswordUppercase) :",
|
||||
"reachedLimit": "Vous avez atteint la limite de votre abonnement.",
|
||||
@@ -565,7 +573,8 @@
|
||||
"sipAudioOnly": "Adresse SIP en audio uniquement",
|
||||
"title": "Partager",
|
||||
"tooltip": "Partager le lien et les informations de connexion pour cette conférence",
|
||||
"upgradeOptions": "Veuillez vérifier les options de mise à niveau"
|
||||
"upgradeOptions": "Veuillez vérifier les options de mise à niveau",
|
||||
"whiteboardError": "Erreur de chargement du tableau blanc. Veuillez réessayer plus tard."
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "Il y a eu un petit problème.",
|
||||
@@ -729,6 +738,8 @@
|
||||
"connectedTwoMembers": "{{first}} et {{second}} ont rejoint la réunion",
|
||||
"dataChannelClosed": "Qualité vidéo dégradée",
|
||||
"dataChannelClosedDescription": "Le canal de communication avec le Bridge a été interrompu, la qualité vidéo se trouve limitée à sa valeur la plus faible.",
|
||||
"dataChannelClosedDescriptionWithAudio": "Le canal de pont est fermé, ce qui peut entraîner des perturbations de l'audio et de la vidéo.",
|
||||
"dataChannelClosedWithAudio": "La qualité de l'audio et de la vidéo peut être altérée",
|
||||
"disabledIframe": "L'intégration Iframe est uniquement destinée à des démos, cet appel se terminera dans {{timeout}} minutes.",
|
||||
"disabledIframeSecondary": "L'intégration Iframe de {{domaine}} est uniquement destinée à des démos, cet appel se terminera dans {{timeout}} minutes.",
|
||||
"disconnected": "déconnecté",
|
||||
@@ -800,13 +811,19 @@
|
||||
"startSilentTitle": "Vous avez rejoint sans sortie audio !",
|
||||
"suboptimalBrowserWarning": "Nous craignons que votre expérience de réunion en ligne ne soit pas idéale ici. Nous cherchons des moyens d'améliorer cela, mais d'ici-là, essayez d'utiliser l'un des <a href='{{recommendedBrowserPageLink}}' target='_blank'>navigateurs supportés</a>.",
|
||||
"suboptimalExperienceTitle": "Avertissement du navigateur",
|
||||
"suggestRecordingAction": "Démarrer",
|
||||
"suggestRecordingDescription": "Souhaitez-vous démarrer un enregistrement ?",
|
||||
"suggestRecordingTitle": "Enregistrer cette réunion",
|
||||
"unmute": "Rétablir le son",
|
||||
"videoMutedRemotelyDescription": "Vous pouvez toujours la réactiver.",
|
||||
"videoMutedRemotelyTitle": "Votre caméra a été coupée par {{participantDisplayName}}!",
|
||||
"videoUnmuteBlockedDescription": "Le rétablissement de la vidéo a été bloqué temporairement en raison de limites système.",
|
||||
"videoUnmuteBlockedTitle": "Rétablissement de la caméra bloqué !",
|
||||
"viewLobby": "Voir la salle d'attente",
|
||||
"viewVisitors": "Voir les visiteurs",
|
||||
"waitingParticipants": "{{waitingParticipants}} personnes",
|
||||
"waitingVisitors": "Visiteurs en attente dans la file : {{waitingVisitors}}",
|
||||
"waitingVisitorsTitle": "La réunion n'est pas encore en direct !",
|
||||
"whiteboardLimitDescription": "Veuillez sauvegarder votre progression, car la limite d’utilisation du tableau blanc sera bientôt atteinte et celui-ci sera fermé.",
|
||||
"whiteboardLimitTitle": "Utiilisation du tableau blanc"
|
||||
},
|
||||
@@ -820,6 +837,7 @@
|
||||
"audioModeration": "Rouvrir leur micro",
|
||||
"blockEveryoneMicCamera": "Bloquer tous les micros et caméras",
|
||||
"breakoutRooms": "Salles annexes",
|
||||
"goLive": "Passer en direct",
|
||||
"invite": "Inviter quelqu'un",
|
||||
"moreModerationActions": "Options de modération supplémentaires",
|
||||
"moreModerationControls": "Options de modération supplémentaires",
|
||||
@@ -837,6 +855,7 @@
|
||||
"headings": {
|
||||
"lobby": "Salle d'attente ({{count}})",
|
||||
"participantsList": "Participants de la réunion ({{count}})",
|
||||
"visitorInQueue": " (en attente {{count}})",
|
||||
"visitorRequests": "(Demande {{count}} )",
|
||||
"visitors": "Visiteurs {{count}}",
|
||||
"waitingLobby": "Dans la salle d'attente ({{count}})"
|
||||
@@ -850,6 +869,8 @@
|
||||
"pinnedParticipant": "Participant toujours affiché",
|
||||
"polls": {
|
||||
"answer": {
|
||||
"edit": "Modifier",
|
||||
"send": "Envoyer",
|
||||
"skip": "Passer",
|
||||
"submit": "Envoyer"
|
||||
},
|
||||
@@ -863,6 +884,7 @@
|
||||
"pollQuestion": "Question du sondage",
|
||||
"questionPlaceholder": "Poser une question",
|
||||
"removeOption": "Supprimer l'option",
|
||||
"save": "Enregistrer",
|
||||
"send": "Envoyer"
|
||||
},
|
||||
"errors": {
|
||||
@@ -935,6 +957,7 @@
|
||||
"or": "ou",
|
||||
"premeeting": "Pré-séance",
|
||||
"proceedAnyway": "Continuer quand même",
|
||||
"recordingWarning": "D'autres participants peuvent enregistrer cet appel",
|
||||
"screenSharingError": "Erreur de partage d'écran:",
|
||||
"showScreen": "Activer l'écran de pré-séance",
|
||||
"startWithPhone": "Commencez avec l'audio du téléphone",
|
||||
@@ -1356,13 +1379,9 @@
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Activer / Désactiver les sous-titres",
|
||||
"error": "Échec de la transcription. Veuillez réessayer.",
|
||||
"expandedLabel": "La transcription est actuellement activée",
|
||||
"failedToStart": "Échec de démarrage de la transcription",
|
||||
"labelToolTip": "La transcription de la réunion est en cours",
|
||||
"off": "La transcription est désactivée",
|
||||
"on": "La transcription est activée",
|
||||
"pending": "Préparation de la transcription de la réunion ...",
|
||||
"sourceLanguageDesc": "Actuellement, la langue de la réunion est sélectionnée à <b>{{sourceLanguage}}</b>. <br/> Vous pouvez la changer à partir de ",
|
||||
"sourceLanguageHere": "ici",
|
||||
"start": "Activer les sous-titres",
|
||||
@@ -1418,6 +1437,7 @@
|
||||
},
|
||||
"videothumbnail": {
|
||||
"connectionInfo": "Informations de la connexion",
|
||||
"demote": "Déplacer en visiteur",
|
||||
"domute": "Couper le micro",
|
||||
"domuteOthers": "Couper le micro de tous les autres",
|
||||
"domuteVideo": "Couper la caméra",
|
||||
@@ -1472,9 +1492,15 @@
|
||||
"chatIndicator": "(visiteur)",
|
||||
"labelTooltip": "Nombre de Visiteurs",
|
||||
"notification": {
|
||||
"demoteDescription": "Envoyé ici par {{actor}}, levez la main pour participer",
|
||||
"description": "Pour participer lever la main.",
|
||||
"noMainParticipantsDescription": "Un participant doit démarrer la réunion. Veuillez réessayer dans un moment.",
|
||||
"noMainParticipantsTitle": "Cette réunion n'a pas encore commencé.",
|
||||
"noVisitorLobby": "Vous ne pouvez pas rejoindre tant qu'une salle d'attente est activée pour la réunion.",
|
||||
"notAllowedPromotion": "Un participant doit d'abord autoriser votre demande.",
|
||||
"title": "Vous êtes visiteur dans cette réunion"
|
||||
}
|
||||
},
|
||||
"waitingMessage": "Vous rejoindrez la réunion dès qu'elle sera en direct !"
|
||||
},
|
||||
"volumeSlider": "Curseur de volume",
|
||||
"welcomepage": {
|
||||
@@ -1532,6 +1558,7 @@
|
||||
"whiteboard": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Tableau blanc"
|
||||
}
|
||||
},
|
||||
"screenTitle": "Tableau blanc"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"loading": "מחפש אנשים ומספרי טלפון",
|
||||
"loadingNumber": "מאמת מספר טלפון",
|
||||
"loadingPeople": "מחפש אנשים להזמין",
|
||||
"noResults": "לא נמצאו תואצות מתאימות",
|
||||
"noResults": "לא נמצאו תוצאות מתאימות",
|
||||
"noValidNumbers": "אנא הזן מסםר טלפון",
|
||||
"searchNumbers": "הוסף מספר טלפון",
|
||||
"searchPeople": "חפש אנשים",
|
||||
@@ -47,7 +47,7 @@
|
||||
},
|
||||
"chat": {
|
||||
"error": "שגיאה: ההודעה שלך \"{{originalText}}\" לא נשלחה. סיבה: {{error}}",
|
||||
"fieldPlaceHolder": "הקלד הודעתך כאו",
|
||||
"fieldPlaceHolder": "הקלד הודעתך כאן",
|
||||
"messageTo": "הודעה פרטית אל {{recipient}}",
|
||||
"messagebox": "הקלד הודעה",
|
||||
"nickname": {
|
||||
@@ -442,7 +442,7 @@
|
||||
"me": "אני",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "פגיעות אבטחה!",
|
||||
"connectedOneMember": "{{name}} הצטרף למפדש",
|
||||
"connectedOneMember": "{{name}} הצטרף למפגש",
|
||||
"connectedThreePlusMembers": "{{name}} ו-{{count}} אחרים הצטרפו למפגש",
|
||||
"connectedTwoMembers": "{{first}} ו-{{second}} הצטרפו למפגש",
|
||||
"disconnected": "מנותק",
|
||||
|
||||
1559
lang/main-id.json
Normal file
1559
lang/main-id.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -263,7 +263,8 @@
|
||||
"Remove": "Noņemt",
|
||||
"Share": "Kopīgot",
|
||||
"Submit": "Iesniegt",
|
||||
"WaitForHostMsg": "Sapulce vēl nav sākusies. Ja esat sapulces rīkotājs, lūdzu autorizējaties. Pretējā gadījumā, lūdzu, uzgaidiet.",
|
||||
"WaitForHostMsg": "Sapulce vēl nav sākusies, jo vēl nav ieradies neviens moderators. Lūdzu, autorizējieties, lai kļūtu par moderatoru. Pretējā gadījumā, lūdzu, uzgaidiet.",
|
||||
"WaitForHostNoAuthMsg": "Sapulce vēl nav sākusies, jo vēl nav ieradies neviens moderators. Lūdzu, uzgaidiet.",
|
||||
"WaitingForHostButton": "Gaidīt rīkotāju",
|
||||
"WaitingForHostTitle": "Gaida rīkotāju...",
|
||||
"Yes": "Jā",
|
||||
@@ -864,6 +865,8 @@
|
||||
"pinnedParticipant": "Dalībnieks ir piesprausts",
|
||||
"polls": {
|
||||
"answer": {
|
||||
"edit": "Labot",
|
||||
"send": "Nosūtīt",
|
||||
"skip": "Izlaist",
|
||||
"submit": "Iesniegt"
|
||||
},
|
||||
@@ -877,6 +880,7 @@
|
||||
"pollQuestion": "Aptaujas Jautājums",
|
||||
"questionPlaceholder": "Uzdod jautājumu",
|
||||
"removeOption": "Noņemt opciju",
|
||||
"save": "Saglabāt",
|
||||
"send": "Nosūtīt"
|
||||
},
|
||||
"errors": {
|
||||
|
||||
@@ -263,7 +263,8 @@
|
||||
"Remove": "Kaldır",
|
||||
"Share": "Paylaş",
|
||||
"Submit": "Gönder",
|
||||
"WaitForHostMsg": "Toplantısı henüz başlamadı. Toplantı sahibi sizseniz, lütfen kimlik doğrulaması yapın. Değilseniz lütfen toplantı sahibinin gelmesini bekleyin.",
|
||||
"WaitForHostMsg": "Toplantı sahibi gelmediğinden toplantı henüz başlamadı. Toplantı sahibi sizseniz, lütfen kimlik doğrulaması yapın. Değilseniz lütfen toplantı sahibinin gelmesini bekleyin.",
|
||||
"WaitForHostNoAuthMsg": "Toplantı sahibi gelmediğinden toplantı henüz başlamadı. Lütfen bekleyin.",
|
||||
"WaitingForHostButton": "Toplantı sahibini bekle",
|
||||
"WaitingForHostTitle": "Toplantı sahibi bekleniyor ...",
|
||||
"Yes": "Evet",
|
||||
@@ -319,6 +320,7 @@
|
||||
"embedMeeting": "Toplantıyı yerleştir",
|
||||
"enterDisplayName": "Lütfen adınızı buraya girin...",
|
||||
"error": "Hata",
|
||||
"errorRoomCreationRestriction": "Çok hızlı katılmaya çalıştınız, lütfen biraz sonra tekrar gelin.",
|
||||
"gracefulShutdown": "Hizmetimiz şu anda bakım için devre dışı. Lütfen daha sonra tekrar deneyiniz.",
|
||||
"grantModeratorDialog": "{{participantName}} için moderatör hakları vermek istediğinize emin misiniz?",
|
||||
"grantModeratorTitle": "Moderatör hakları ver",
|
||||
@@ -820,6 +822,8 @@
|
||||
"viewLobby": "Lobiyi göster",
|
||||
"viewVisitors": "Ziyaretçileri görüntüle",
|
||||
"waitingParticipants": "{{waitingParticipants}} kişi",
|
||||
"waitingVisitors": "Sırada bekleyen ziyaretçiler: {{waitingVisitors}}",
|
||||
"waitingVisitorsTitle": "Toplantı henüz canlı değil!",
|
||||
"whiteboardLimitDescription": "Kullanıcı sınırına yakında ulaşılacağından ve beyaz tahta kapanacağından lütfen ilerlemenizi kaydedin.",
|
||||
"whiteboardLimitTitle": "Beyaz tahta kullanımı"
|
||||
},
|
||||
@@ -833,6 +837,7 @@
|
||||
"audioModeration": "Seslerini aç",
|
||||
"blockEveryoneMicCamera": "Herkesin mikrofonunu ve kamerasını blokla",
|
||||
"breakoutRooms": "Alt odalar",
|
||||
"goLive": "Canlı yayına geç",
|
||||
"invite": "Birini davet et",
|
||||
"moreModerationActions": "Daha fazla denetleme seçeneği",
|
||||
"moreModerationControls": "Daha fazla denetleme kontrolü",
|
||||
@@ -850,6 +855,7 @@
|
||||
"headings": {
|
||||
"lobby": "Lobi ({{count}})",
|
||||
"participantsList": "Toplantı Katılımcıları ({{count}})",
|
||||
"visitorInQueue": "(waiting {{count}})",
|
||||
"visitorRequests": "(requests {{count}})",
|
||||
"visitors": "Ziyaretçiler {{count}}",
|
||||
"waitingLobby": "Lobide bekleyen ({{count}})"
|
||||
@@ -863,6 +869,8 @@
|
||||
"pinnedParticipant": "Katılımcı sabitlendi",
|
||||
"polls": {
|
||||
"answer": {
|
||||
"edit": "Düzenle",
|
||||
"send": "Gönder",
|
||||
"skip": "Geç",
|
||||
"submit": "Gönder"
|
||||
},
|
||||
@@ -876,6 +884,7 @@
|
||||
"pollQuestion": "Anket Sorusu",
|
||||
"questionPlaceholder": "Soru sor",
|
||||
"removeOption": "Seçeneği sil",
|
||||
"save": "Kaydet",
|
||||
"send": "Gönder"
|
||||
},
|
||||
"errors": {
|
||||
@@ -1485,8 +1494,13 @@
|
||||
"notification": {
|
||||
"demoteDescription": "Buraya {{actor}} tarafından gönderildi, katılmak için elinizi kaldırın",
|
||||
"description": "Katılmak için elinizi kaldırın",
|
||||
"noMainParticipantsDescription": "Bir katılımcının toplantıyı başlatması gerekiyor. Lütfen biraz sonra tekrar deneyin.",
|
||||
"noMainParticipantsTitle": "Bu toplantı henüz başlamadı.",
|
||||
"noVisitorLobby": "Toplantı için etkinleştirilmiş bir lobi varken katılamazsınız.",
|
||||
"notAllowedPromotion": "Bir katılımcının öncelikle isteğinize izin vermesi gerekiyor.",
|
||||
"title": "Toplantıda ziyaretçisiniz"
|
||||
}
|
||||
},
|
||||
"waitingMessage": "Toplantı canlı yayınlanır yayınlanmaz katılacaksınız!"
|
||||
},
|
||||
"volumeSlider": "Ses kaydırıcısı",
|
||||
"welcomepage": {
|
||||
|
||||
@@ -786,6 +786,7 @@
|
||||
"newDeviceAction": "Use",
|
||||
"newDeviceAudioTitle": "New audio device detected",
|
||||
"newDeviceCameraTitle": "New camera detected",
|
||||
"nextToSpeak": "You are the next in line to speak",
|
||||
"noiseSuppressionDesktopAudioDescription": "Noise suppression can't be enabled while sharing desktop audio, please disable it and try again.",
|
||||
"noiseSuppressionFailedTitle": "Failed to start noise suppression",
|
||||
"noiseSuppressionStereoDescription": "Stereo audio noise suppression is not currently supported.",
|
||||
@@ -822,6 +823,8 @@
|
||||
"viewLobby": "View lobby",
|
||||
"viewVisitors": "View visitors",
|
||||
"waitingParticipants": "{{waitingParticipants}} people",
|
||||
"waitingVisitors": "Visitors waiting in queue: {{waitingVisitors}}",
|
||||
"waitingVisitorsTitle": "The meeting is not live yet!",
|
||||
"whiteboardLimitDescription": "Please save your progress, as the user limit will soon be reached and the whiteboard will close.",
|
||||
"whiteboardLimitTitle": "Whiteboard usage"
|
||||
},
|
||||
@@ -835,7 +838,10 @@
|
||||
"audioModeration": "Unmute themselves",
|
||||
"blockEveryoneMicCamera": "Block everyone's mic and camera",
|
||||
"breakoutRooms": "Breakout rooms",
|
||||
"goLive": "Go live",
|
||||
"invite": "Invite Someone",
|
||||
"lowerAllHands": "Lower all hands",
|
||||
"lowerHand": "Lower the hand",
|
||||
"moreModerationActions": "More moderation options",
|
||||
"moreModerationControls": "More moderation controls",
|
||||
"moreParticipantOptions": "More participant options",
|
||||
@@ -852,6 +858,7 @@
|
||||
"headings": {
|
||||
"lobby": "Lobby ({{count}})",
|
||||
"participantsList": "Meeting participants ({{count}})",
|
||||
"visitorInQueue": " (waiting {{count}})",
|
||||
"visitorRequests": " (requests {{count}})",
|
||||
"visitors": "Visitors {{count}}",
|
||||
"waitingLobby": "Waiting in lobby ({{count}})"
|
||||
@@ -871,6 +878,7 @@
|
||||
"submit": "Submit"
|
||||
},
|
||||
"by": "By {{ name }}",
|
||||
"closeButton": "Close poll",
|
||||
"create": {
|
||||
"addOption": "Add option",
|
||||
"answerPlaceholder": "Option {{index}}",
|
||||
@@ -1486,6 +1494,12 @@
|
||||
},
|
||||
"visitors": {
|
||||
"chatIndicator": "(visitor)",
|
||||
"joinMeeting": {
|
||||
"description": "You're currently an observer in this conference.",
|
||||
"raiseHand": "Raise your hand",
|
||||
"title": "Joining meeting",
|
||||
"wishToSpeak": "If you wish to speak, please raise your hand below and wait for the moderator's approval."
|
||||
},
|
||||
"labelTooltip": "Number of visitors: {{count}}",
|
||||
"notification": {
|
||||
"demoteDescription": "Sent here by {{actor}}, raise your hand to participate",
|
||||
@@ -1495,7 +1509,8 @@
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"waitingMessage": "You'll join the meeting as soon as it is live!"
|
||||
},
|
||||
"volumeSlider": "Volume slider",
|
||||
"welcomepage": {
|
||||
|
||||
@@ -100,7 +100,7 @@ import {
|
||||
} from '../../react/features/participants-pane/actions';
|
||||
import { getParticipantsPaneOpen, isForceMuted } from '../../react/features/participants-pane/functions';
|
||||
import { startLocalVideoRecording, stopLocalVideoRecording } from '../../react/features/recording/actions.any';
|
||||
import { RECORDING_TYPES } from '../../react/features/recording/constants';
|
||||
import { RECORDING_METADATA_ID, RECORDING_TYPES } from '../../react/features/recording/constants';
|
||||
import { getActiveSession, supportsLocalRecording } from '../../react/features/recording/functions';
|
||||
import { startAudioScreenShareFlow, startScreenShareFlow } from '../../react/features/screen-share/actions';
|
||||
import { isScreenAudioSupported } from '../../react/features/screen-share/functions';
|
||||
@@ -629,6 +629,7 @@ function initCommands() {
|
||||
* @param { string } arg.youtubeStreamKey - The youtube stream key.
|
||||
* @param { string } arg.youtubeBroadcastID - The youtube broadcast ID.
|
||||
* @param { Object } arg.extraMetadata - Any extra metadata params for file recording.
|
||||
* @param { boolean } arg.transcription - Whether a transcription should be started or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
'start-recording': ({
|
||||
@@ -640,7 +641,8 @@ function initCommands() {
|
||||
rtmpBroadcastID,
|
||||
youtubeStreamKey,
|
||||
youtubeBroadcastID,
|
||||
extraMetadata = {}
|
||||
extraMetadata = {},
|
||||
transcription
|
||||
}) => {
|
||||
const state = APP.store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
@@ -715,25 +717,33 @@ function initCommands() {
|
||||
mode: JitsiRecordingConstants.mode.STREAM,
|
||||
streamId: youtubeStreamKey || rtmpStreamKey
|
||||
};
|
||||
} else {
|
||||
logger.error('Invalid recording mode provided');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isScreenshotCaptureEnabled(state, true, false)) {
|
||||
APP.store.dispatch(toggleScreenshotCaptureSummary(true));
|
||||
}
|
||||
conference.startRecording(recordingConfig);
|
||||
|
||||
// Start audio / video recording, if requested.
|
||||
if (typeof recordingConfig !== 'undefined') {
|
||||
conference.startRecording(recordingConfig);
|
||||
}
|
||||
|
||||
if (transcription) {
|
||||
APP.store.dispatch(setRequestingSubtitles(true, false, null));
|
||||
conference.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
|
||||
isTranscribingEnabled: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops a recording or streaming in progress.
|
||||
*
|
||||
* @param {string} mode - `local`, `file` or `stream`.
|
||||
* @param {boolean} transcription - Whether the transcription needs to be stopped.
|
||||
* @returns {void}
|
||||
*/
|
||||
'stop-recording': mode => {
|
||||
'stop-recording': (mode, transcription) => {
|
||||
const state = APP.store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
@@ -743,6 +753,13 @@ function initCommands() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (transcription) {
|
||||
APP.store.dispatch(setRequestingSubtitles(false, false, null));
|
||||
conference.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
|
||||
isTranscribingEnabled: false
|
||||
});
|
||||
}
|
||||
|
||||
if (mode === 'local') {
|
||||
APP.store.dispatch(stopLocalVideoRecording());
|
||||
|
||||
@@ -1109,7 +1126,12 @@ class API {
|
||||
this.notifyBrowserSupport(isSupportedBrowser());
|
||||
|
||||
// Let the embedder know we are ready.
|
||||
this._sendEvent({ name: 'ready' });
|
||||
this._sendEvent({
|
||||
name: 'ready',
|
||||
|
||||
// XXX: Here we are using window.config since this is fired really early.
|
||||
info: window.config.deploymentInfo
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1797,9 +1819,9 @@ class API {
|
||||
* Notify external application of a participant, remote or local, being
|
||||
* removed from the conference by another participant.
|
||||
*
|
||||
* @param {string} kicked - The ID of the participant removed from the
|
||||
* @param {Object} kicked - The participant removed from the
|
||||
* conference.
|
||||
* @param {string} kicker - The ID of the participant that removed the
|
||||
* @param {Object} kicker - The participant that removed the
|
||||
* other participant.
|
||||
* @returns {void}
|
||||
*/
|
||||
@@ -1917,14 +1939,16 @@ class API {
|
||||
* @param {boolean} on - True if recording is on, false otherwise.
|
||||
* @param {string} mode - Stream or file or local.
|
||||
* @param {string} error - Error type or null if success.
|
||||
* @param {boolean} transcription - True if a transcription is being recorded, false otherwise.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyRecordingStatusChanged(on, mode, error) {
|
||||
notifyRecordingStatusChanged(on, mode, error, transcription) {
|
||||
this._sendEvent({
|
||||
name: 'recording-status-changed',
|
||||
on,
|
||||
mode,
|
||||
error
|
||||
error,
|
||||
transcription
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
6
modules/API/external/external_api.js
vendored
6
modules/API/external/external_api.js
vendored
@@ -1446,6 +1446,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* @param { string } options.youtubeStreamKey - The youtube stream key.
|
||||
* @param { string } options.youtubeBroadcastID - The youtube broadcast ID.
|
||||
* @param {Object } options.extraMetadata - Any extra metadata params for file recording.
|
||||
* @param { boolean } arg.transcription - Whether a transcription should be started or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
startRecording(options) {
|
||||
@@ -1456,10 +1457,11 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* Stops a recording or streaming session that is in progress.
|
||||
*
|
||||
* @param {string} mode - `file` or `stream`.
|
||||
* @param {boolean} transcription - Whether the transcription needs to be stopped.
|
||||
* @returns {void}
|
||||
*/
|
||||
stopRecording(mode) {
|
||||
this.executeCommand('stopRecording', mode);
|
||||
stopRecording(mode, transcription) {
|
||||
this.executeCommand('stopRecording', mode, transcription);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
7658
package-lock.json
generated
7658
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
50
package.json
50
package.json
@@ -16,7 +16,7 @@
|
||||
"author": "",
|
||||
"readmeFilename": "README.md",
|
||||
"dependencies": {
|
||||
"@amplitude/react-native": "2.7.0",
|
||||
"@amplitude/react-native": "2.17.3",
|
||||
"@braintree/sanitize-url": "7.0.0",
|
||||
"@emotion/react": "11.10.6",
|
||||
"@emotion/styled": "11.10.6",
|
||||
@@ -31,16 +31,17 @@
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"@microsoft/microsoft-graph-client": "3.0.1",
|
||||
"@mui/material": "5.12.1",
|
||||
"@react-native-async-storage/async-storage": "1.19.4",
|
||||
"@react-native-community/clipboard": "1.5.1",
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
"@react-native-clipboard/clipboard": "1.14.1",
|
||||
"@react-native-community/netinfo": "11.1.0",
|
||||
"@react-native-community/slider": "4.4.3",
|
||||
"@react-native-google-signin/google-signin": "10.1.0",
|
||||
"@react-navigation/bottom-tabs": "6.5.8",
|
||||
"@react-navigation/elements": "1.3.18",
|
||||
"@react-navigation/material-top-tabs": "6.6.3",
|
||||
"@react-navigation/native": "6.1.7",
|
||||
"@react-navigation/stack": "6.3.17",
|
||||
"@react-navigation/bottom-tabs": "6.6.0",
|
||||
"@react-navigation/elements": "1.3.30",
|
||||
"@react-navigation/material-top-tabs": "6.6.13",
|
||||
"@react-navigation/native": "6.1.17",
|
||||
"@react-navigation/stack": "6.4.0",
|
||||
"@stomp/stompjs": "7.0.0",
|
||||
"@svgr/webpack": "6.3.1",
|
||||
"@tensorflow/tfjs-backend-wasm": "3.13.0",
|
||||
"@tensorflow/tfjs-core": "3.13.0",
|
||||
@@ -66,7 +67,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1830.0.0+5a14bd43/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1845.0.0+515a927c/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -77,17 +78,17 @@
|
||||
"punycode": "2.3.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-emoji-render": "1.2.4",
|
||||
"react-emoji-render": "2.0.1",
|
||||
"react-focus-on": "3.8.1",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "0.72.14",
|
||||
"react-native": "0.73.8",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "10.9.0",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.9.0",
|
||||
"react-native-gesture-handler": "2.17.1",
|
||||
"react-native-get-random-values": "1.9.0",
|
||||
"react-native-immersive-mode": "2.0.1",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
@@ -96,16 +97,16 @@
|
||||
"react-native-paper": "5.10.3",
|
||||
"react-native-performance": "5.0.0",
|
||||
"react-native-safe-area-context": "4.7.1",
|
||||
"react-native-screens": "3.24.0",
|
||||
"react-native-screens": "3.32.0",
|
||||
"react-native-sound": "0.11.2",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
"react-native-svg": "13.13.0",
|
||||
"react-native-svg-transformer": "1.1.0",
|
||||
"react-native-svg-transformer": "1.2.0",
|
||||
"react-native-tab-view": "3.5.2",
|
||||
"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-webrtc": "124.0.3",
|
||||
"react-native-webview": "13.8.7",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
"react-redux": "7.2.9",
|
||||
@@ -126,13 +127,13 @@
|
||||
"zxcvbn": "4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.21.5",
|
||||
"@babel/eslint-parser": "7.21.8",
|
||||
"@babel/plugin-proposal-export-default-from": "7.22.5",
|
||||
"@babel/preset-env": "7.21.5",
|
||||
"@babel/preset-react": "7.16.0",
|
||||
"@babel/core": "7.24.7",
|
||||
"@babel/eslint-parser": "7.24.7",
|
||||
"@babel/plugin-proposal-export-default-from": "7.24.7",
|
||||
"@babel/preset-env": "7.24.7",
|
||||
"@babel/preset-react": "7.24.7",
|
||||
"@jitsi/eslint-config": "4.1.10",
|
||||
"@react-native/metro-config": "0.72.12",
|
||||
"@react-native/metro-config": "0.73.5",
|
||||
"@types/amplitude-js": "8.16.5",
|
||||
"@types/audioworklet": "0.0.29",
|
||||
"@types/dom-screen-wake-lock": "1.0.1",
|
||||
@@ -145,7 +146,6 @@
|
||||
"@types/react": "17.0.14",
|
||||
"@types/react-dom": "17.0.14",
|
||||
"@types/react-linkify": "1.0.1",
|
||||
"@types/react-native": "0.69.22",
|
||||
"@types/react-native-keep-awake": "2.0.3",
|
||||
"@types/react-native-video": "5.0.14",
|
||||
"@types/react-redux": "7.1.24",
|
||||
@@ -157,7 +157,7 @@
|
||||
"@types/zxcvbn": "4.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.5",
|
||||
"@typescript-eslint/parser": "5.59.5",
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-loader": "9.1.0",
|
||||
"babel-plugin-optional-require": "0.3.1",
|
||||
"circular-dependency-plugin": "5.2.0",
|
||||
"clean-css-cli": "4.3.0",
|
||||
@@ -169,7 +169,7 @@
|
||||
"eslint-plugin-react-native": "4.0.0",
|
||||
"eslint-plugin-typescript-sort-keys": "2.3.0",
|
||||
"jetifier": "1.6.4",
|
||||
"metro-react-native-babel-preset": "0.75.1",
|
||||
"metro-react-native-babel-preset": "0.77.0",
|
||||
"patch-package": "6.4.7",
|
||||
"process": "0.11.10",
|
||||
"sass": "1.26.8",
|
||||
@@ -181,7 +181,7 @@
|
||||
"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"
|
||||
|
||||
@@ -18,7 +18,7 @@ index e4f7e15..6f05fb3 100644
|
||||
+ = [UIApplication sharedApplication].applicationState == UIApplicationStateBackground
|
||||
+ || [UIDevice currentDevice].proximityState;
|
||||
});
|
||||
|
||||
|
||||
+ _inBackground = initialInBackground;
|
||||
+
|
||||
for (NSString *name in @[
|
||||
@@ -34,12 +34,12 @@ index e4f7e15..6f05fb3 100644
|
||||
+ name:UIDeviceProximityStateDidChangeNotification
|
||||
+ object:nil];
|
||||
}
|
||||
|
||||
|
||||
- (void)dealloc
|
||||
@@ -187,6 +195,16 @@ RCT_EXPORT_MODULE()
|
||||
[self startTimers];
|
||||
}
|
||||
|
||||
|
||||
+- (void)proximityChanged
|
||||
+{
|
||||
+ BOOL isClose = [UIDevice currentDevice].proximityState;
|
||||
@@ -17,6 +17,7 @@ import type { IRoomsInfo } from '../react/features/breakout-rooms/types';
|
||||
|
||||
import { appNavigate } from './react/features/app/actions.native';
|
||||
import { App } from './react/features/app/components/App.native';
|
||||
import { setAudioOnly } from './react/features/base/audio-only/actions';
|
||||
import { setAudioMuted, setVideoMuted } from './react/features/base/media/actions';
|
||||
import { getRoomsInfo } from './react/features/breakout-rooms/functions';
|
||||
|
||||
@@ -30,6 +31,7 @@ interface IEventListeners {
|
||||
onConferenceLeft?: Function;
|
||||
onConferenceWillJoin?: Function;
|
||||
onEnterPictureInPicture?: Function;
|
||||
onEndpointMessageReceived?: Function;
|
||||
onParticipantJoined?: Function;
|
||||
onParticipantLeft?: ({ id }: { id: string }) => void;
|
||||
onReadyToClose?: Function;
|
||||
@@ -54,6 +56,7 @@ interface IAppProps {
|
||||
|
||||
export interface JitsiRefProps {
|
||||
close: Function;
|
||||
setAudioOnly?: (value: boolean) => void;
|
||||
setAudioMuted?: (muted: boolean) => void;
|
||||
setVideoMuted?: (muted: boolean) => void;
|
||||
getRoomsInfo?: () => IRoomsInfo;
|
||||
@@ -83,6 +86,11 @@ export const JitsiMeeting = forwardRef<JitsiRefProps, IAppProps>((props, ref) =>
|
||||
|
||||
dispatch(appNavigate(undefined));
|
||||
},
|
||||
setAudioOnly: value => {
|
||||
const dispatch = app.current.state.store.dispatch;
|
||||
|
||||
dispatch(setAudioOnly(value));
|
||||
},
|
||||
setAudioMuted: muted => {
|
||||
const dispatch = app.current.state.store.dispatch;
|
||||
|
||||
@@ -133,6 +141,7 @@ export const JitsiMeeting = forwardRef<JitsiRefProps, IAppProps>((props, ref) =>
|
||||
onConferenceWillJoin: eventListeners?.onConferenceWillJoin,
|
||||
onConferenceLeft: eventListeners?.onConferenceLeft,
|
||||
onEnterPictureInPicture: eventListeners?.onEnterPictureInPicture,
|
||||
onEndpointMessageReceived: eventListeners?.onEndpointMessageReceived,
|
||||
onParticipantJoined: eventListeners?.onParticipantJoined,
|
||||
onParticipantLeft: eventListeners?.onParticipantLeft,
|
||||
onReadyToClose: eventListeners?.onReadyToClose
|
||||
|
||||
@@ -57,10 +57,11 @@
|
||||
"@giphy/react-native-sdk": "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-clipboard/clipboard": "0.0.0",
|
||||
"@react-native-community/netinfo": "0.0.0",
|
||||
"@react-native-community/slider": "0.0.0",
|
||||
"@react-native-google-signin/google-signin": "0.0.0",
|
||||
"@stomp/stompjs": "0.0.0",
|
||||
"react-native": "*",
|
||||
"react": "*",
|
||||
"react-native-background-timer": "0.0.0",
|
||||
|
||||
@@ -151,7 +151,7 @@ export function appNavigate(uri?: string, options: IReloadNowOptions = {}) {
|
||||
}
|
||||
|
||||
dispatch(setLocationURL(locationURL));
|
||||
dispatch(setConfig(config, locationURL));
|
||||
dispatch(setConfig(config));
|
||||
dispatch(setRoom(room));
|
||||
|
||||
if (!room) {
|
||||
|
||||
@@ -74,7 +74,7 @@ export function appNavigate(uri?: string) {
|
||||
const config = await loadConfig();
|
||||
|
||||
dispatch(setLocationURL(locationURL));
|
||||
dispatch(setConfig(config, locationURL));
|
||||
dispatch(setConfig(config));
|
||||
dispatch(setRoom(room));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import '../notifications/middleware';
|
||||
import '../overlay/middleware';
|
||||
import '../participants-pane/middleware';
|
||||
import '../polls/middleware';
|
||||
import '../polls-history/middleware';
|
||||
import '../reactions/middleware';
|
||||
import '../recent-list/middleware';
|
||||
import '../recording/middleware';
|
||||
|
||||
@@ -41,6 +41,7 @@ import '../notifications/reducer';
|
||||
import '../overlay/reducer';
|
||||
import '../participants-pane/reducer';
|
||||
import '../polls/reducer';
|
||||
import '../polls-history/reducer';
|
||||
import '../reactions/reducer';
|
||||
import '../recent-list/reducer';
|
||||
import '../recording/reducer';
|
||||
|
||||
@@ -60,6 +60,7 @@ import { INotificationsState } from '../notifications/reducer';
|
||||
import { IOverlayState } from '../overlay/reducer';
|
||||
import { IParticipantsPaneState } from '../participants-pane/reducer';
|
||||
import { IPollsState } from '../polls/reducer';
|
||||
import { IPollsHistoryState } from '../polls-history/reducer';
|
||||
import { IPowerMonitorState } from '../power-monitor/reducer';
|
||||
import { IPrejoinState } from '../prejoin/reducer';
|
||||
import { IReactionsState } from '../reactions/reducer';
|
||||
@@ -149,6 +150,7 @@ export interface IReduxState {
|
||||
'features/overlay': IOverlayState;
|
||||
'features/participants-pane': IParticipantsPaneState;
|
||||
'features/polls': IPollsState;
|
||||
'features/polls-history': IPollsHistoryState;
|
||||
'features/power-monitor': IPowerMonitorState;
|
||||
'features/prejoin': IPrejoinState;
|
||||
'features/reactions': IReactionsState;
|
||||
|
||||
@@ -5,12 +5,12 @@ import { connect as reduxConnect } from 'react-redux';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { IJitsiConference } from '../../../base/conference/reducer';
|
||||
import { IConfig } from '../../../base/config/configType';
|
||||
import { connect } from '../../../base/connection/actions.web';
|
||||
import { toJid } from '../../../base/connection/functions';
|
||||
import { translate, translateToHTML } from '../../../base/i18n/functions';
|
||||
import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
import Input from '../../../base/ui/components/web/Input';
|
||||
import { joinConference } from '../../../prejoin/actions.web';
|
||||
import {
|
||||
authenticateAndUpgradeRole,
|
||||
cancelLogin
|
||||
@@ -134,9 +134,7 @@ class LoginDialog extends Component<IProps, IState> {
|
||||
if (conference) {
|
||||
dispatch(authenticateAndUpgradeRole(jid, password, conference));
|
||||
} else {
|
||||
// dispatch(connect(jid, password));
|
||||
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
|
||||
dispatch(joinConference(undefined, false, jid, password));
|
||||
dispatch(connect(jid, password));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,8 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
case CONNECTION_FAILED: {
|
||||
const { error } = action;
|
||||
const state = store.getState();
|
||||
const { getState } = store;
|
||||
const state = getState();
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
|
||||
if (error
|
||||
|
||||
@@ -18,7 +18,7 @@ let pressureObserver: typeof window.PressureObserver;
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(() => (next: Function) => async (action: AnyAction) => {
|
||||
MiddlewareRegistry.register(() => (next: Function) => (action: AnyAction) => {
|
||||
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT: {
|
||||
|
||||
@@ -46,6 +46,6 @@ export function toggleAudioOnly() {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { enabled } = getState()['features/base/audio-only'];
|
||||
|
||||
return dispatch(setAudioOnly(!enabled));
|
||||
dispatch(setAudioOnly(!enabled));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -228,6 +228,14 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
|
||||
name: getNormalizedDisplayName(displayName)
|
||||
})));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.SILENT_STATUS_CHANGED,
|
||||
(id: string, isSilent: boolean) => dispatch(participantUpdated({
|
||||
conference,
|
||||
id,
|
||||
isSilent
|
||||
})));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
||||
(dominant: string, previous: string[], silence: boolean | string) => {
|
||||
@@ -867,7 +875,7 @@ export function setPassword(
|
||||
password?: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
if (!conference) {
|
||||
return;
|
||||
return Promise.reject();
|
||||
}
|
||||
switch (method) {
|
||||
case conference.join: {
|
||||
@@ -973,7 +981,7 @@ export function setStartMutedPolicy(
|
||||
video: startVideoMuted
|
||||
});
|
||||
|
||||
return dispatch(
|
||||
dispatch(
|
||||
onStartMutedPolicyChanged(startAudioMuted, startVideoMuted));
|
||||
};
|
||||
}
|
||||
@@ -984,12 +992,12 @@ export function setStartMutedPolicy(
|
||||
* @param {string} subject - The new subject.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function setSubject(subject: string | undefined) {
|
||||
export function setSubject(subject: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { conference } = getState()['features/base/conference'];
|
||||
|
||||
if (conference) {
|
||||
conference.setSubject(subject || '');
|
||||
conference.setSubject(subject);
|
||||
} else {
|
||||
dispatch({
|
||||
type: SET_PENDING_SUBJECT_CHANGE,
|
||||
@@ -1008,7 +1016,7 @@ export function setSubject(subject: string | undefined) {
|
||||
* localSubject: string
|
||||
* }}
|
||||
*/
|
||||
export function setLocalSubject(localSubject: string | undefined) {
|
||||
export function setLocalSubject(localSubject: string) {
|
||||
return {
|
||||
type: CONFERENCE_LOCAL_SUBJECT_CHANGED,
|
||||
localSubject
|
||||
@@ -1050,14 +1058,20 @@ export function redirect(vnode: string, focusJid: string, username: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(overwriteConfig(newConfig)) // @ts-ignore
|
||||
.then(() => dispatch(disconnect(true)))
|
||||
.then(() => dispatch(setIAmVisitor(Boolean(vnode))))
|
||||
dispatch(overwriteConfig(newConfig));
|
||||
|
||||
// we do not clear local tracks on error, so we need to manually clear them
|
||||
.then(() => dispatch(destroyLocalTracks()))
|
||||
.then(() => dispatch(conferenceWillInit()))
|
||||
.then(() => dispatch(connect()))
|
||||
dispatch(disconnect(true))
|
||||
.then(() => {
|
||||
dispatch(setIAmVisitor(Boolean(vnode)));
|
||||
|
||||
// we do not clear local tracks on error, so we need to manually clear them
|
||||
return dispatch(destroyLocalTracks());
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(conferenceWillInit());
|
||||
|
||||
return dispatch(connect());
|
||||
})
|
||||
.then(() => {
|
||||
const media: Array<MediaType> = [];
|
||||
|
||||
|
||||
@@ -21,11 +21,5 @@ export function setupVisitorStartupMedia(media: Array<MediaType>) {
|
||||
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([]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,14 +37,6 @@ import { IJitsiConference } from './reducer';
|
||||
*/
|
||||
export const getConferenceState = (state: IReduxState) => state['features/base/conference'];
|
||||
|
||||
/**
|
||||
* Is the conference joined or not.
|
||||
*
|
||||
* @param {IReduxState} state - Global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const getIsConferenceJoined = (state: IReduxState) => Boolean(getConferenceState(state).conference);
|
||||
|
||||
/**
|
||||
* Attach a set of local tracks to a conference.
|
||||
*
|
||||
@@ -185,18 +177,12 @@ export function forEachConference(
|
||||
export function getConferenceName(stateful: IStateful): string {
|
||||
const state = toState(stateful);
|
||||
const { callee } = state['features/base/jwt'];
|
||||
const {
|
||||
callDisplayName,
|
||||
localSubject: configLocalSubject,
|
||||
subject: configSubject
|
||||
} = state['features/base/config'];
|
||||
const { callDisplayName } = state['features/base/config'];
|
||||
const { localSubject, pendingSubjectChange, room, subject } = getConferenceState(state);
|
||||
|
||||
return (pendingSubjectChange
|
||||
|| configSubject
|
||||
return (localSubject
|
||||
|| pendingSubjectChange
|
||||
|| subject
|
||||
|| configLocalSubject
|
||||
|| localSubject
|
||||
|| callDisplayName
|
||||
|| callee?.name
|
||||
|| (room && safeStartCase(safeDecodeURIComponent(room)))) ?? '';
|
||||
|
||||
@@ -24,7 +24,7 @@ import LocalRecordingManager from '../../recording/components/Recording/LocalRec
|
||||
import { iAmVisitor } from '../../visitors/functions';
|
||||
import { overwriteConfig } from '../config/actions';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../connection/actionTypes';
|
||||
import { connect, connectionDisconnected, disconnect } from '../connection/actions';
|
||||
import { connectionDisconnected, disconnect } from '../connection/actions';
|
||||
import { validateJwt } from '../jwt/functions';
|
||||
import { JitsiConferenceErrors, JitsiConferenceEvents, JitsiConnectionErrors } from '../lib-jitsi-meet';
|
||||
import { PARTICIPANT_UPDATED, PIN_PARTICIPANT } from '../participants/actionTypes';
|
||||
@@ -37,7 +37,6 @@ import {
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||
import { TRACK_ADDED, TRACK_REMOVED } from '../tracks/actionTypes';
|
||||
import { getLocalTracks } from '../tracks/functions.any';
|
||||
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
@@ -205,20 +204,11 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
const newConfig = restoreConferenceOptions(getState);
|
||||
|
||||
if (newConfig) {
|
||||
dispatch(overwriteConfig(newConfig)) // @ts-ignore
|
||||
.then(() => dispatch(conferenceWillLeave(conference)))
|
||||
.then(() => conference.leave())
|
||||
.then(() => dispatch(disconnect()))
|
||||
.then(() => dispatch(connect()))
|
||||
.then(() => {
|
||||
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
|
||||
if (typeof APP !== 'undefined') {
|
||||
const localTracks = getLocalTracks(getState()['features/base/tracks']);
|
||||
const jitsiTracks = localTracks.map((t: any) => t.jitsiTrack);
|
||||
dispatch(overwriteConfig(newConfig));
|
||||
dispatch(conferenceWillLeave(conference));
|
||||
|
||||
APP.conference.startConference(jitsiTracks).catch(logger.error);
|
||||
}
|
||||
});
|
||||
conference.leave()
|
||||
.then(() => dispatch(disconnect()));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -706,6 +696,10 @@ function _updateLocalParticipantInConference({ dispatch, getState }: IStore, nex
|
||||
conference.setDisplayName(participant.name);
|
||||
}
|
||||
|
||||
if ('isSilent' in participant) {
|
||||
conference.setIsSilent(participant.isSilent);
|
||||
}
|
||||
|
||||
if ('role' in participant && participant.role === PARTICIPANT_ROLE.MODERATOR) {
|
||||
const { pendingSubjectChange, subject } = getState()['features/base/conference'];
|
||||
|
||||
|
||||
@@ -4,9 +4,17 @@ import {
|
||||
setPrejoinPageVisibility,
|
||||
setSkipPrejoinOnReload
|
||||
} from '../../prejoin/actions.web';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { iAmVisitor } from '../../visitors/functions';
|
||||
import { CONNECTION_DISCONNECTED, CONNECTION_ESTABLISHED } from '../connection/actionTypes';
|
||||
import { hangup } from '../connection/actions.web';
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
import { JitsiConferenceErrors, browser } from '../lib-jitsi-meet';
|
||||
import { gumPending, setInitialGUMPromise } from '../media/actions';
|
||||
import { MEDIA_TYPE } from '../media/constants';
|
||||
import { IGUMPendingState } from '../media/types';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { replaceLocalTrack } from '../tracks/actions.any';
|
||||
import { getLocalTracks } from '../tracks/functions.any';
|
||||
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
@@ -131,6 +139,75 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
releaseScreenLock();
|
||||
|
||||
break;
|
||||
case CONNECTION_DISCONNECTED: {
|
||||
const { initialGUMPromise } = getState()['features/base/media'];
|
||||
|
||||
if (initialGUMPromise) {
|
||||
store.dispatch(setInitialGUMPromise());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case CONNECTION_ESTABLISHED: {
|
||||
if (isPrejoinPageVisible(getState())) {
|
||||
let { initialGUMPromise } = getState()['features/base/media'];
|
||||
|
||||
initialGUMPromise = initialGUMPromise || Promise.resolve({ tracks: [] });
|
||||
|
||||
initialGUMPromise.then(() => {
|
||||
const state = getState();
|
||||
let localTracks = getLocalTracks(state['features/base/tracks']);
|
||||
const trackReplacePromises = [];
|
||||
|
||||
// Do not signal audio/video tracks if the user joins muted.
|
||||
for (const track of localTracks) {
|
||||
// Always add the audio track on Safari because of a known issue where audio playout doesn't happen
|
||||
// if the user joins audio and video muted.
|
||||
if ((track.muted && !(browser.isWebKitBased() && track.jitsiTrack
|
||||
&& track.jitsiTrack.getType() === MEDIA_TYPE.AUDIO)) || iAmVisitor(state)) {
|
||||
trackReplacePromises.push(dispatch(replaceLocalTrack(track.jitsiTrack, null))
|
||||
.catch((error: any) => {
|
||||
logger.error(`Failed to replace local track (${track.jitsiTrack}) with null: ${error}`);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Promise.allSettled(trackReplacePromises).then(() => {
|
||||
|
||||
// Re-fetch the local tracks after muted tracks have been removed above.
|
||||
// This is needed, because the tracks are effectively disposed by the replaceLocalTrack and should
|
||||
// not be used anymore.
|
||||
localTracks = getLocalTracks(getState()['features/base/tracks']);
|
||||
|
||||
const jitsiTracks = localTracks.map((t: any) => t.jitsiTrack);
|
||||
|
||||
|
||||
return APP.conference.startConference(jitsiTracks);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
let { initialGUMPromise } = getState()['features/base/media'];
|
||||
|
||||
initialGUMPromise = initialGUMPromise || Promise.resolve({ tracks: [] });
|
||||
|
||||
initialGUMPromise.then(({ tracks }) => {
|
||||
let tracksToUse = tracks ?? [];
|
||||
|
||||
if (iAmVisitor(getState())) {
|
||||
tracksToUse = [];
|
||||
tracks.forEach(track => track.dispose().catch(logger.error));
|
||||
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
|
||||
}
|
||||
|
||||
dispatch(setInitialGUMPromise());
|
||||
|
||||
return APP.conference.startConference(tracksToUse);
|
||||
})
|
||||
.catch(logger.error);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
||||
@@ -3,6 +3,8 @@ import { AnyAction } from 'redux';
|
||||
import { FaceLandmarks } from '../../face-landmarks/types';
|
||||
import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../room-lock/constants';
|
||||
import { ISpeakerStats } from '../../speaker-stats/reducer';
|
||||
import { SET_CONFIG } from '../config/actionTypes';
|
||||
import { IConfig } from '../config/configType';
|
||||
import { CONNECTION_WILL_CONNECT, SET_LOCATION_URL } from '../connection/actionTypes';
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
import ReducerRegistry from '../redux/ReducerRegistry';
|
||||
@@ -52,6 +54,9 @@ export interface IConferenceMetadata {
|
||||
recording?: {
|
||||
isTranscribingEnabled: boolean;
|
||||
};
|
||||
visitors?: {
|
||||
live: boolean;
|
||||
};
|
||||
whiteboard?: {
|
||||
collabDetails: {
|
||||
roomId: string;
|
||||
@@ -130,6 +135,7 @@ export interface IJitsiConference {
|
||||
setAssumedBandwidthBps: (value: number) => void;
|
||||
setDesktopSharingFrameRate: Function;
|
||||
setDisplayName: Function;
|
||||
setIsSilent: Function;
|
||||
setLocalParticipantProperty: Function;
|
||||
setMediaEncryptionKey: Function;
|
||||
setReceiverConstraints: Function;
|
||||
@@ -278,11 +284,33 @@ ReducerRegistry.register<IConferenceState>('features/base/conference',
|
||||
...state,
|
||||
metadata: action.metadata
|
||||
};
|
||||
|
||||
case SET_CONFIG:
|
||||
return _setConfig(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
|
||||
/**
|
||||
* Processes subject and local subject of the conference based on the new config.
|
||||
*
|
||||
* @param {Object} state - The Redux state of feature base/conference.
|
||||
* @param {Action} action - The Redux action SET_CONFIG to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state after the reduction of the specified action.
|
||||
*/
|
||||
function _setConfig(state: IConferenceState, { config }: { config: IConfig; }) {
|
||||
const { localSubject, subject } = config;
|
||||
|
||||
return {
|
||||
...state,
|
||||
localSubject,
|
||||
pendingSubjectChange: subject,
|
||||
subject: undefined
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action AUTH_STATUS_CHANGED of the feature
|
||||
* base/conference.
|
||||
@@ -606,10 +634,7 @@ function _setRoom(state: IConferenceState, action: AnyAction) {
|
||||
*/
|
||||
return assign(state, {
|
||||
error: undefined,
|
||||
localSubject: undefined,
|
||||
pendingSubjectChange: undefined,
|
||||
room,
|
||||
subject: undefined
|
||||
room
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -96,51 +96,53 @@ export function overwriteConfig(config: Object) {
|
||||
*
|
||||
* @param {Object} config - The configuration to be represented by the feature
|
||||
* base/config.
|
||||
* @param {URL} locationURL - The URL of the location which necessitated the
|
||||
* loading of a configuration.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setConfig(config: IConfig = {}, locationURL: URL | undefined) {
|
||||
// Now that the loading of the config was successful override the values
|
||||
// with the parameters passed in the hash part of the location URI.
|
||||
// TODO We're still in the middle ground between old Web with config,
|
||||
// and interfaceConfig used via global variables and new
|
||||
// Web and mobile reading the respective values from the redux store.
|
||||
// Only the config will be overridden on React Native, as the other
|
||||
// globals will be undefined here. It's intentional - we do not care to
|
||||
// override those configs yet.
|
||||
locationURL
|
||||
&& setConfigFromURLParams(
|
||||
export function setConfig(config: IConfig = {}) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { locationURL } = getState()['features/base/connection'];
|
||||
|
||||
// On Web the config also comes from the window.config global,
|
||||
// but it is resolved in the loadConfig procedure.
|
||||
config,
|
||||
window.interfaceConfig,
|
||||
locationURL);
|
||||
// Now that the loading of the config was successful override the values
|
||||
// with the parameters passed in the hash part of the location URI.
|
||||
// TODO We're still in the middle ground between old Web with config,
|
||||
// and interfaceConfig used via global variables and new
|
||||
// Web and mobile reading the respective values from the redux store.
|
||||
// Only the config will be overridden on React Native, as the other
|
||||
// globals will be undefined here. It's intentional - we do not care to
|
||||
// override those configs yet.
|
||||
locationURL
|
||||
&& setConfigFromURLParams(
|
||||
|
||||
let { bosh } = config;
|
||||
// On Web the config also comes from the window.config global,
|
||||
// but it is resolved in the loadConfig procedure.
|
||||
config,
|
||||
window.interfaceConfig,
|
||||
locationURL);
|
||||
|
||||
if (bosh) {
|
||||
// Normalize the BOSH URL.
|
||||
if (bosh.startsWith('//')) {
|
||||
// By default our config.js doesn't include the protocol.
|
||||
bosh = `${locationURL?.protocol}${bosh}`;
|
||||
} else if (bosh.startsWith('/')) {
|
||||
// Handle relative URLs, which won't work on mobile.
|
||||
const {
|
||||
protocol,
|
||||
host,
|
||||
contextRoot
|
||||
} = parseURIString(locationURL?.href);
|
||||
let { bosh } = config;
|
||||
|
||||
bosh = `${protocol}//${host}${contextRoot || '/'}${bosh.substr(1)}`;
|
||||
if (bosh) {
|
||||
// Normalize the BOSH URL.
|
||||
if (bosh.startsWith('//')) {
|
||||
// By default our config.js doesn't include the protocol.
|
||||
bosh = `${locationURL?.protocol}${bosh}`;
|
||||
} else if (bosh.startsWith('/')) {
|
||||
// Handle relative URLs, which won't work on mobile.
|
||||
const {
|
||||
protocol,
|
||||
host,
|
||||
contextRoot
|
||||
} = parseURIString(locationURL?.href);
|
||||
|
||||
bosh = `${protocol}//${host}${contextRoot || '/'}${bosh.substr(1)}`;
|
||||
}
|
||||
config.bosh = bosh;
|
||||
}
|
||||
config.bosh = bosh;
|
||||
}
|
||||
|
||||
return {
|
||||
type: SET_CONFIG,
|
||||
config
|
||||
dispatch({
|
||||
type: SET_CONFIG,
|
||||
config
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -287,6 +287,7 @@ export interface IConfig {
|
||||
disableRemoveRaisedHandOnFocus?: boolean;
|
||||
disableResponsiveTiles?: boolean;
|
||||
disableRtx?: boolean;
|
||||
disableSelfDemote?: boolean;
|
||||
disableSelfView?: boolean;
|
||||
disableSelfViewSettings?: boolean;
|
||||
disableShortcuts?: boolean;
|
||||
@@ -478,6 +479,7 @@ export interface IConfig {
|
||||
peopleSearchQueryTypes?: string[];
|
||||
peopleSearchUrl?: string;
|
||||
preferBosh?: boolean;
|
||||
preferVisitor?: boolean;
|
||||
preferredTranscribeLanguage?: string;
|
||||
prejoinConfig?: {
|
||||
enabled?: boolean;
|
||||
@@ -544,7 +546,6 @@ export interface IConfig {
|
||||
assumeBandwidth?: boolean;
|
||||
disableE2EE?: boolean;
|
||||
dumpTranscript?: boolean;
|
||||
mobileXmppWsThreshold?: number;
|
||||
noAutoPlayVideo?: boolean;
|
||||
p2pTestMode?: boolean;
|
||||
skipInterimTranscriptions?: boolean;
|
||||
|
||||
@@ -116,6 +116,7 @@ export default [
|
||||
'disableRemoteMute',
|
||||
'disableResponsiveTiles',
|
||||
'disableRtx',
|
||||
'disableSelfDemote',
|
||||
'disableSelfView',
|
||||
'disableSelfViewSettings',
|
||||
'disableShortcuts',
|
||||
@@ -197,6 +198,7 @@ export default [
|
||||
'participantsPane',
|
||||
'pcStatsInterval',
|
||||
'preferBosh',
|
||||
'preferVisitor',
|
||||
'prejoinConfig',
|
||||
'prejoinPageEnabled',
|
||||
'recordingService',
|
||||
|
||||
@@ -6,6 +6,7 @@ import { safeJsonParse } from '@jitsi/js-utils/json';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { getLocalParticipant } from '../participants/functions';
|
||||
import { parseURLParams } from '../util/parseURLParams';
|
||||
|
||||
import { IConfig } from './configType';
|
||||
@@ -184,6 +185,31 @@ export function isNameReadOnly(state: IReduxState): boolean {
|
||||
|| state['features/base/config'].readOnlyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the participant is the next one in the queue to speak.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isNextToSpeak(state: IReduxState): boolean {
|
||||
const raisedHandsQueue = state['features/base/participants'].raisedHandsQueue || [];
|
||||
const participantId = getLocalParticipant(state)?.id;
|
||||
|
||||
return participantId === raisedHandsQueue[0]?.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the next to speak participant in the queue has been notified.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function hasBeenNotified(state: IReduxState): boolean {
|
||||
const raisedHandsQueue = state['features/base/participants'].raisedHandsQueue;
|
||||
|
||||
return Boolean(raisedHandsQueue[0]?.hasBeenNotified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the display name is visible.
|
||||
*
|
||||
|
||||
@@ -80,6 +80,7 @@ export interface IConfigState extends IConfig {
|
||||
audio?: boolean;
|
||||
video?: boolean;
|
||||
};
|
||||
queueService: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,5 +1,8 @@
|
||||
import { appNavigate } from '../../app/actions.native';
|
||||
import { IStore } from '../../app/types';
|
||||
import { navigateRoot } from '../../mobile/navigation/rootNavigationContainerRef';
|
||||
import { screen } from '../../mobile/navigation/routes';
|
||||
import { JitsiConnectionErrors } from '../lib-jitsi-meet';
|
||||
|
||||
import { _connectInternal } from './actions.any';
|
||||
|
||||
@@ -13,7 +16,12 @@ export * from './actions.any';
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function connect(id?: string, password?: string) {
|
||||
return (dispatch: IStore['dispatch']) => dispatch(_connectInternal(id, password));
|
||||
return (dispatch: IStore['dispatch']) => dispatch(_connectInternal(id, password))
|
||||
.catch(error => {
|
||||
if (error === JitsiConnectionErrors.NOT_LIVE_ERROR) {
|
||||
navigateRoot(screen.visitorsQueue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,8 +34,11 @@ export function connect(id?: string, password?: string) {
|
||||
return getJaasJWT(state);
|
||||
}
|
||||
})
|
||||
.then(j => j && dispatch(setJWT(j)))
|
||||
.then(() => dispatch(_connectInternal(id, password)));
|
||||
.then(j => {
|
||||
j && dispatch(setJWT(j));
|
||||
|
||||
return dispatch(_connectInternal(id, password));
|
||||
});
|
||||
}
|
||||
|
||||
// used by jibri
|
||||
|
||||
@@ -147,6 +147,13 @@ function _connectionFailed(
|
||||
return state;
|
||||
}
|
||||
|
||||
let preferVisitor;
|
||||
|
||||
if (error.name === JitsiConnectionErrors.NOT_LIVE_ERROR) {
|
||||
// we want to keep the state for the moment when the meeting is live
|
||||
preferVisitor = state.preferVisitor;
|
||||
}
|
||||
|
||||
return assign(state, {
|
||||
connecting: undefined,
|
||||
connection: undefined,
|
||||
@@ -154,7 +161,7 @@ function _connectionFailed(
|
||||
passwordRequired:
|
||||
error.name === JitsiConnectionErrors.PASSWORD_REQUIRED
|
||||
? connection : undefined,
|
||||
preferVisitor: undefined
|
||||
preferVisitor
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,3 +19,14 @@ export function isIosMobileBrowser() {
|
||||
return Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the current environment is an ipad device.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isIpadMobileBrowser() {
|
||||
|
||||
// @ts-ignore
|
||||
return isIosMobileBrowser() && Platform.isPad;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import logger from './logger';
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => async action => {
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case I18NEXT_INITIALIZED:
|
||||
case LANGUAGE_CHANGED:
|
||||
@@ -23,11 +23,10 @@ MiddlewareRegistry.register(store => next => async action => {
|
||||
: store.getState()['features/dynamic-branding'];
|
||||
|
||||
if (language && labels && labels[language]) {
|
||||
try {
|
||||
await changeLanguageBundle(language, labels[language]);
|
||||
} catch (err) {
|
||||
changeLanguageBundle(language, labels[language])
|
||||
.catch(err => {
|
||||
logger.log('Error setting dynamic language bundle', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -176,10 +176,12 @@ export function validateJwt(jwt: string) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValidUnixTimestamp(nbf)) {
|
||||
errors.push({ key: JWT_VALIDATION_ERRORS.NBF_INVALID });
|
||||
} else if (currentTimestamp < nbf * 1000) {
|
||||
errors.push({ key: JWT_VALIDATION_ERRORS.NBF_FUTURE });
|
||||
if (nbf) { // nbf value is optional
|
||||
if (!isValidUnixTimestamp(nbf)) {
|
||||
errors.push({ key: JWT_VALIDATION_ERRORS.NBF_INVALID });
|
||||
} else if (currentTimestamp < nbf * 1000) {
|
||||
errors.push({ key: JWT_VALIDATION_ERRORS.NBF_FUTURE });
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValidUnixTimestamp(exp)) {
|
||||
|
||||
@@ -12,8 +12,7 @@ export const JitsiConferenceErrors = JitsiMeetJS.errors.conference;
|
||||
export const JitsiConferenceEvents = JitsiMeetJS.events.conference;
|
||||
export const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
export const JitsiConnectionEvents = JitsiMeetJS.events.connection;
|
||||
export const JitsiConnectionQualityEvents
|
||||
= JitsiMeetJS.events.connectionQuality;
|
||||
export const JitsiConnectionQualityEvents = JitsiMeetJS.events.connectionQuality;
|
||||
export const JitsiDetectionEvents = JitsiMeetJS.events.detection;
|
||||
export const JitsiE2ePingEvents = JitsiMeetJS.events.e2eping;
|
||||
export const JitsiMediaDevicesEvents = JitsiMeetJS.events.mediaDevices;
|
||||
|
||||
@@ -51,6 +51,16 @@ export const SET_AUDIO_UNMUTE_PERMISSIONS = 'SET_AUDIO_UNMUTE_PERMISSIONS';
|
||||
*/
|
||||
export const SET_CAMERA_FACING_MODE = 'SET_CAMERA_FACING_MODE';
|
||||
|
||||
/**
|
||||
* Sets the initial GUM promise.
|
||||
*
|
||||
* {
|
||||
* type: SET_INITIAL_GUM_PROMISE,
|
||||
* promise: Promise
|
||||
* }}
|
||||
*/
|
||||
export const SET_INITIAL_GUM_PROMISE = 'SET_INITIAL_GUM_PROMISE';
|
||||
|
||||
/**
|
||||
* The type of (redux) action to set the muted state of the local screenshare.
|
||||
*
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
SET_AUDIO_MUTED,
|
||||
SET_AUDIO_UNMUTE_PERMISSIONS,
|
||||
SET_CAMERA_FACING_MODE,
|
||||
SET_INITIAL_GUM_PROMISE,
|
||||
SET_SCREENSHARE_MUTED,
|
||||
SET_VIDEO_AVAILABLE,
|
||||
SET_VIDEO_MUTED,
|
||||
@@ -93,6 +94,22 @@ export function setCameraFacingMode(cameraFacingMode: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial GUM promise.
|
||||
*
|
||||
* @param {Promise<Array<Object>> | undefined} promise - The promise.
|
||||
* @returns {{
|
||||
* type: SET_INITIAL_GUM_PROMISE,
|
||||
* promise: Promise
|
||||
* }}
|
||||
*/
|
||||
export function setInitialGUMPromise(promise: Promise<{ errors: any; tracks: Array<any>; }> | null = null) {
|
||||
return {
|
||||
type: SET_INITIAL_GUM_PROMISE,
|
||||
promise
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to set the muted state of the local screenshare.
|
||||
*
|
||||
@@ -122,7 +139,7 @@ export function setScreenshareMuted(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
const newValue = muted ? oldValue | authority : oldValue & ~authority;
|
||||
|
||||
return dispatch({
|
||||
dispatch({
|
||||
type: SET_SCREENSHARE_MUTED,
|
||||
authority,
|
||||
ensureTrack,
|
||||
@@ -180,7 +197,7 @@ export function setVideoMuted(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
const newValue = muted ? oldValue | authority : oldValue & ~authority;
|
||||
|
||||
return dispatch({
|
||||
dispatch({
|
||||
type: SET_VIDEO_MUTED,
|
||||
authority,
|
||||
ensureTrack,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
SET_AUDIO_MUTED,
|
||||
SET_AUDIO_UNMUTE_PERMISSIONS,
|
||||
SET_CAMERA_FACING_MODE,
|
||||
SET_INITIAL_GUM_PROMISE,
|
||||
SET_SCREENSHARE_MUTED,
|
||||
SET_VIDEO_AVAILABLE,
|
||||
SET_VIDEO_MUTED,
|
||||
@@ -87,6 +88,22 @@ function _audio(state: IAudioState = _AUDIO_INITIAL_MEDIA_STATE, action: AnyActi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reducer fot the common properties in media state.
|
||||
*
|
||||
* @param {ICommonState} state - Common media state.
|
||||
* @param {Object} action - Action object.
|
||||
* @param {string} action.type - Type of action.
|
||||
* @returns {ICommonState}
|
||||
*/
|
||||
function _initialGUMPromise(state: initialGUMPromise | null = null, action: AnyAction) {
|
||||
if (action.type === SET_INITIAL_GUM_PROMISE) {
|
||||
return action.promise ?? null;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Media state object for local screenshare.
|
||||
*
|
||||
@@ -247,6 +264,11 @@ interface IAudioState {
|
||||
unmuteBlocked: boolean;
|
||||
}
|
||||
|
||||
type initialGUMPromise = Promise<{
|
||||
errors?: any;
|
||||
tracks: Array<any>;
|
||||
}> | null;
|
||||
|
||||
interface IScreenshareState {
|
||||
available: boolean;
|
||||
muted: number;
|
||||
@@ -264,6 +286,7 @@ interface IVideoState {
|
||||
|
||||
export interface IMediaState {
|
||||
audio: IAudioState;
|
||||
initialGUMPromise: initialGUMPromise;
|
||||
screenshare: IScreenshareState;
|
||||
video: IVideoState;
|
||||
}
|
||||
@@ -280,6 +303,7 @@ export interface IMediaState {
|
||||
*/
|
||||
ReducerRegistry.register<IMediaState>('features/base/media', combineReducers({
|
||||
audio: _audio,
|
||||
initialGUMPromise: _initialGUMPromise,
|
||||
screenshare: _screenshare,
|
||||
video: _video
|
||||
}));
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/**
|
||||
* Create an action to mark the participant as notified to speak next.
|
||||
*
|
||||
* {
|
||||
* type: NOTIFIED_TO_SPEAK
|
||||
* }
|
||||
*/
|
||||
export const NOTIFIED_TO_SPEAK = 'NOTIFIED_TO_SPEAK';
|
||||
|
||||
/**
|
||||
* Create an action for when dominant speaker changes.
|
||||
*
|
||||
|
||||
@@ -544,23 +544,27 @@ export function createVirtualScreenshareParticipant(sourceName: string, local: b
|
||||
*/
|
||||
export function participantKicked(kicker: any, kicked: any) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const kickedId = kicked.getId();
|
||||
const kickerId = kicker.getId();
|
||||
|
||||
dispatch({
|
||||
type: PARTICIPANT_KICKED,
|
||||
kicked: kicked.getId(),
|
||||
kicker: kicker?.getId()
|
||||
kicked: kickedId,
|
||||
kicker: kickerId
|
||||
});
|
||||
|
||||
if (kicked.isReplaced?.()) {
|
||||
if (kicked.isReplaced?.() || kickerId === localParticipant?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
titleArguments: {
|
||||
kicked:
|
||||
getParticipantDisplayName(getState, kicked.getId()),
|
||||
getParticipantDisplayName(state, kickedId),
|
||||
kicker:
|
||||
getParticipantDisplayName(getState, kicker.getId())
|
||||
getParticipantDisplayName(state, kickerId)
|
||||
},
|
||||
titleKey: 'notify.kickParticipant'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
|
||||
@@ -19,7 +19,7 @@ import { CALLING, INVITED } from '../../presence-status/constants';
|
||||
import { RAISE_HAND_SOUND_ID } from '../../reactions/constants';
|
||||
import { RECORDING_OFF_SOUND_ID, RECORDING_ON_SOUND_ID } from '../../recording/constants';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app/actionTypes';
|
||||
import { CONFERENCE_WILL_JOIN } from '../conference/actionTypes';
|
||||
import { CONFERENCE_JOINED, CONFERENCE_WILL_JOIN } from '../conference/actionTypes';
|
||||
import { forEachConference, getCurrentConference } from '../conference/functions';
|
||||
import { IJitsiConference } from '../conference/reducer';
|
||||
import { SET_CONFIG } from '../config/actionTypes';
|
||||
@@ -201,6 +201,28 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
return result;
|
||||
}
|
||||
|
||||
case CONFERENCE_JOINED: {
|
||||
const result = next(action);
|
||||
|
||||
const state = store.getState();
|
||||
const { startSilent } = state['features/base/config'];
|
||||
|
||||
if (startSilent) {
|
||||
const localId = getLocalParticipant(store.getState())?.id;
|
||||
|
||||
if (localId) {
|
||||
store.dispatch(participantUpdated({
|
||||
id: localId,
|
||||
local: true,
|
||||
isSilent: startSilent
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
case SET_LOCAL_PARTICIPANT_RECORDING_STATUS: {
|
||||
const state = store.getState();
|
||||
const { recording, onlySelf } = action;
|
||||
@@ -236,10 +258,8 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
let queue = getRaiseHandsQueue(store.getState());
|
||||
|
||||
if (participant.raisedHandTimestamp) {
|
||||
queue.push({
|
||||
id: participant.id,
|
||||
raisedHandTimestamp: participant.raisedHandTimestamp
|
||||
});
|
||||
queue = [ ...queue, { id: participant.id,
|
||||
raisedHandTimestamp: participant.raisedHandTimestamp } ];
|
||||
|
||||
// sort the queue before adding to store.
|
||||
queue = queue.sort(({ raisedHandTimestamp: a }, { raisedHandTimestamp: b }) => a - b);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { set } from '../redux/functions';
|
||||
|
||||
import {
|
||||
DOMINANT_SPEAKER_CHANGED,
|
||||
NOTIFIED_TO_SPEAK,
|
||||
OVERWRITE_PARTICIPANT_NAME,
|
||||
PARTICIPANT_ID_CHANGED,
|
||||
PARTICIPANT_JOINED,
|
||||
@@ -92,7 +93,7 @@ export interface IParticipantsState {
|
||||
numberOfParticipantsNotSupportingE2EE: number;
|
||||
overwrittenNameList: { [id: string]: string; };
|
||||
pinnedParticipant?: string;
|
||||
raisedHandsQueue: Array<{ id: string; raisedHandTimestamp: number; }>;
|
||||
raisedHandsQueue: Array<{ hasBeenNotified?: boolean; id: string; raisedHandTimestamp: number; }>;
|
||||
remote: Map<string, IParticipant>;
|
||||
remoteVideoSources: Set<string>;
|
||||
sortedRemoteParticipants: Map<string, string>;
|
||||
@@ -114,6 +115,19 @@ export interface IParticipantsState {
|
||||
ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
(state = DEFAULT_STATE, action): IParticipantsState => {
|
||||
switch (action.type) {
|
||||
case NOTIFIED_TO_SPEAK: {
|
||||
return {
|
||||
...state,
|
||||
raisedHandsQueue: [
|
||||
{
|
||||
...state.raisedHandsQueue[0],
|
||||
hasBeenNotified: true
|
||||
},
|
||||
...state.raisedHandsQueue.slice(1)
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
case PARTICIPANT_ID_CHANGED: {
|
||||
const { local } = state;
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
|
||||
import _ from 'lodash';
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { getSsrcRewritingFeatureFlag } from '../config/functions.any';
|
||||
import { getSsrcRewritingFeatureFlag, hasBeenNotified, isNextToSpeak } from '../config/functions.any';
|
||||
import { VIDEO_TYPE } from '../media/constants';
|
||||
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||
|
||||
import { NOTIFIED_TO_SPEAK } from './actionTypes';
|
||||
import { createVirtualScreenshareParticipant, participantLeft } from './actions';
|
||||
import {
|
||||
getParticipantById,
|
||||
@@ -25,6 +30,15 @@ StateListenerRegistry.register(
|
||||
&& _updateScreenshareParticipantsBasedOnPresence(store)
|
||||
);
|
||||
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/participants'].raisedHandsQueue,
|
||||
/* listener */ (raisedHandsQueue, store) => {
|
||||
if (isNextToSpeak(store.getState()) && !hasBeenNotified(store.getState())) {
|
||||
_notifyNextSpeakerInRaisedHandQueue(store);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Compares the old and new screenshare lists provided and creates/removes the virtual screenshare participant
|
||||
* tiles accodingly.
|
||||
@@ -121,3 +135,23 @@ function _updateScreenshareParticipantsBasedOnPresence(store: IStore): void {
|
||||
|
||||
_createOrRemoveVirtualParticipants(previousScreenshareSourceNames, currentScreenshareSourceNames, store);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles notifying the next speaker in the raised hand queue.
|
||||
*
|
||||
* @param {*} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _notifyNextSpeakerInRaisedHandQueue(store: IStore): void {
|
||||
const { dispatch } = store;
|
||||
|
||||
batch(() => {
|
||||
dispatch(showNotification({
|
||||
titleKey: 'notify.nextToSpeak',
|
||||
maxLines: 2
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
dispatch({
|
||||
type: NOTIFIED_TO_SPEAK
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface IParticipant {
|
||||
isJigasi?: boolean;
|
||||
isReplaced?: boolean;
|
||||
isReplacing?: number;
|
||||
isSilent?: boolean;
|
||||
jwtId?: string;
|
||||
loadableAvatarUrl?: string;
|
||||
loadableAvatarUrlUseCORS?: boolean;
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
// @ts-ignore
|
||||
const { userAgent, maxTouchPoints, platform } = navigator;
|
||||
let OS = '';
|
||||
const { maxTouchPoints, platform, userAgent } = navigator;
|
||||
|
||||
let OS = '',
|
||||
isPad = false;
|
||||
|
||||
if (userAgent.match(/Android/i)) {
|
||||
OS = 'android';
|
||||
} else if (userAgent.match(/iP(ad|hone|od)/i) || (maxTouchPoints && maxTouchPoints > 2 && /MacIntel/.test(platform))) {
|
||||
OS = 'ios';
|
||||
} else if (userAgent.match(/iP(ad)/i)) {
|
||||
OS = 'ios';
|
||||
isPad = true;
|
||||
} else if (userAgent.match(/Mac(intosh| OS X)/i)) {
|
||||
OS = 'macos';
|
||||
} else if (userAgent.match(/Windows/i)) {
|
||||
@@ -18,6 +23,13 @@ if (userAgent.match(/Android/i)) {
|
||||
* Provides a minimal equivalent of react-native's Platform abstraction.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Returns a boolean which defines if device is an iPad.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
isPad,
|
||||
|
||||
/**
|
||||
* The operating system on which the application is executing.
|
||||
*
|
||||
|
||||
@@ -35,6 +35,10 @@ const REDUCED_UI_THRESHOLD = 300;
|
||||
*/
|
||||
export function clientResized(clientWidth: number, clientHeight: number) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
if (!clientWidth && !clientHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
let availableWidth = clientWidth;
|
||||
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app/actionTypes';
|
||||
import { CONFERENCE_JOINED } from '../conference/actionTypes';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
import { clientResized } from './actions';
|
||||
@@ -27,6 +28,19 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
_appWillMount(store);
|
||||
break;
|
||||
|
||||
case CONFERENCE_JOINED: {
|
||||
const { clientHeight = 0, clientWidth = 0 } = store.getState()['features/base/responsive-ui'];
|
||||
|
||||
if (!clientHeight && !clientWidth) {
|
||||
const {
|
||||
innerHeight,
|
||||
innerWidth
|
||||
} = window;
|
||||
|
||||
store.dispatch(clientResized(innerWidth, innerHeight));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { isTrackStreamingStatusActive } from '../../connection-indicator/functions';
|
||||
import { VIDEO_CODEC } from '../../video-quality/constants';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
import { getParticipantById, isScreenShareParticipant } from '../participants/functions';
|
||||
import { getTrackByMediaTypeAndParticipant, getVideoTrackByParticipant } from '../tracks/functions';
|
||||
import {
|
||||
getLocalVideoTrack,
|
||||
getTrackByMediaTypeAndParticipant,
|
||||
getVideoTrackByParticipant
|
||||
} from '../tracks/functions';
|
||||
|
||||
/**
|
||||
* Indicates whether the test mode is enabled. When it's enabled
|
||||
@@ -51,6 +56,78 @@ export function isLargeVideoReceived({ getState }: IStore): boolean {
|
||||
return Boolean(videoTrack && !videoTrack.muted && isTrackStreamingStatusActive(videoTrack));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the local video track is encoded in AV1.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalCameraEncodingAv1({ getState }: IStore): boolean {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localtrack = getLocalVideoTrack(tracks);
|
||||
|
||||
if (localtrack?.codec?.toLowerCase() === VIDEO_CODEC.AV1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the local video track is encoded in H.264.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalCameraEncodingH264({ getState }: IStore): boolean {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localtrack = getLocalVideoTrack(tracks);
|
||||
|
||||
if (localtrack?.codec?.toLowerCase() === VIDEO_CODEC.H264) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the local video track is encoded in VP8.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalCameraEncodingVp8({ getState }: IStore): boolean {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localtrack = getLocalVideoTrack(tracks);
|
||||
|
||||
if (localtrack?.codec?.toLowerCase() === VIDEO_CODEC.VP8) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the local video track is encoded in VP9.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalCameraEncodingVp9({ getState }: IStore): boolean {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localtrack = getLocalVideoTrack(tracks);
|
||||
|
||||
if (localtrack?.codec?.toLowerCase() === VIDEO_CODEC.VP9) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the last media event received for a remote video indicates that the video is playing, if not muted.
|
||||
*
|
||||
|
||||
@@ -10,6 +10,10 @@ import { setConnectionState } from './actions';
|
||||
import {
|
||||
getRemoteVideoType,
|
||||
isLargeVideoReceived,
|
||||
isLocalCameraEncodingAv1,
|
||||
isLocalCameraEncodingH264,
|
||||
isLocalCameraEncodingVp8,
|
||||
isLocalCameraEncodingVp9,
|
||||
isRemoteVideoReceived,
|
||||
isTestModeEnabled
|
||||
} from './functions';
|
||||
@@ -86,6 +90,10 @@ function _bindTortureHelpers(store: IStore) {
|
||||
getJitsiMeetGlobalNS().testing = {
|
||||
getRemoteVideoType: getRemoteVideoType.bind(null, store),
|
||||
isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
|
||||
isLocalCameraEncodingAv1: isLocalCameraEncodingAv1.bind(null, store),
|
||||
isLocalCameraEncodingH264: isLocalCameraEncodingH264.bind(null, store),
|
||||
isLocalCameraEncodingVp8: isLocalCameraEncodingVp8.bind(null, store),
|
||||
isLocalCameraEncodingVp9: isLocalCameraEncodingVp9.bind(null, store),
|
||||
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
getTrackByJitsiTrack
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { ITrackOptions } from './types';
|
||||
import { ITrack, ITrackOptions } from './types';
|
||||
|
||||
/**
|
||||
* Add a given local track to the conference.
|
||||
@@ -64,7 +64,7 @@ export function addLocalTrack(newTrack: any) {
|
||||
const isMuted = newTrack.isMuted();
|
||||
|
||||
logger.log(`Adding ${newTrack.getType()} track - ${isMuted ? 'muted' : 'unmuted'}`);
|
||||
await dispatch(setMuted(isMuted));
|
||||
dispatch(setMuted(isMuted));
|
||||
|
||||
return dispatch(_addTracks([ newTrack ]));
|
||||
};
|
||||
@@ -233,12 +233,11 @@ export function createLocalTracksA(options: ITrackOptions = {}) {
|
||||
*/
|
||||
export function destroyLocalTracks(track: any = null) {
|
||||
if (track) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
dispatch(_disposeAndRemoveTracks([ track ]));
|
||||
};
|
||||
return (dispatch: IStore['dispatch']) => dispatch(_disposeAndRemoveTracks([ track ]));
|
||||
}
|
||||
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) =>
|
||||
|
||||
// First wait until any getUserMedia in progress is settled and then get
|
||||
// rid of all local tracks.
|
||||
_cancelGUMProcesses(getState)
|
||||
@@ -248,7 +247,6 @@ export function destroyLocalTracks(track: any = null) {
|
||||
getState()['features/base/tracks']
|
||||
.filter(t => t.local)
|
||||
.map(t => t.jitsiTrack))));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,7 +272,7 @@ export function noDataFromSource(track: any) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showNoDataFromSourceVideoError(jitsiTrack: any) {
|
||||
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
let notificationInfo;
|
||||
|
||||
const track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
|
||||
@@ -286,7 +284,7 @@ export function showNoDataFromSourceVideoError(jitsiTrack: any) {
|
||||
if (track.isReceivingData) {
|
||||
notificationInfo = undefined;
|
||||
} else {
|
||||
const notificationAction = await dispatch(showErrorNotification({
|
||||
const notificationAction = dispatch(showErrorNotification({
|
||||
descriptionKey: 'dialog.cameraNotSendingData',
|
||||
titleKey: 'dialog.cameraNotSendingDataTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
@@ -359,7 +357,7 @@ function replaceStoredTracks(oldTrack: any, newTrack: any) {
|
||||
sendAnalytics(createTrackMutedEvent(newTrack.getType(), 'track.replaced', isMuted));
|
||||
logger.log(`Replace ${newTrack.getType()} track - ${isMuted ? 'muted' : 'unmuted'}`);
|
||||
|
||||
await dispatch(setMuted(isMuted));
|
||||
dispatch(setMuted(isMuted));
|
||||
await dispatch(_addTracks([ newTrack ]));
|
||||
}
|
||||
};
|
||||
@@ -373,7 +371,7 @@ function replaceStoredTracks(oldTrack: any, newTrack: any) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function trackAdded(track: any) {
|
||||
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
track.on(
|
||||
JitsiTrackEvents.TRACK_MUTE_CHANGED,
|
||||
() => dispatch(trackMutedChanged(track)));
|
||||
@@ -400,7 +398,7 @@ export function trackAdded(track: any) {
|
||||
track.on(JitsiTrackEvents.NO_DATA_FROM_SOURCE, () => dispatch(noDataFromSource({ jitsiTrack: track })));
|
||||
if (!isReceivingData) {
|
||||
if (mediaType === MEDIA_TYPE.AUDIO) {
|
||||
const notificationAction = await dispatch(showNotification({
|
||||
const notificationAction = dispatch(showNotification({
|
||||
descriptionKey: 'dialog.micNotSendingData',
|
||||
titleKey: 'dialog.micNotSendingDataTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
@@ -450,6 +448,32 @@ export function trackAdded(track: any) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when a track's codec has been signaled to have been changed.
|
||||
*
|
||||
* @param {JitsiLocalTrack} track - JitsiLocalTrack instance.
|
||||
* @param {string} codec - The video codec.
|
||||
* @returns {{
|
||||
* type: TRACK_UPDATED,
|
||||
* track: Track
|
||||
* }}
|
||||
*/
|
||||
export function trackCodecChanged(track: ITrack, codec: string): {
|
||||
track: {
|
||||
codec: string;
|
||||
jitsiTrack: any;
|
||||
};
|
||||
type: 'TRACK_UPDATED';
|
||||
} {
|
||||
return {
|
||||
type: TRACK_UPDATED,
|
||||
track: {
|
||||
codec,
|
||||
jitsiTrack: track
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when a track's muted state has been signaled to be
|
||||
* changed.
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
* The payload name for remotely setting the camera facing mode message.
|
||||
*/
|
||||
export const CAMERA_FACING_MODE_MESSAGE = 'camera-facing-mode-message';
|
||||
export const LOWER_HAND_MESSAGE = 'lower-hand-message';
|
||||
|
||||
@@ -183,7 +183,7 @@ function _getLocalTrack(
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
async function _setMuted(store: IStore, { ensureTrack, muted }: {
|
||||
function _setMuted(store: IStore, { ensureTrack, muted }: {
|
||||
ensureTrack: boolean; muted: boolean; }, mediaType: MediaType) {
|
||||
const { dispatch, getState } = store;
|
||||
const localTrack = _getLocalTrack(store, mediaType, /* includePending */ true);
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface ITrackOptions {
|
||||
* any.
|
||||
*/
|
||||
export interface ITrack {
|
||||
codec: string;
|
||||
getOriginalStream: Function;
|
||||
isReceivingData: boolean;
|
||||
jitsiTrack: any;
|
||||
|
||||
@@ -111,12 +111,9 @@ const Dialog = ({
|
||||
}, [ onCancel ]);
|
||||
|
||||
const submit = useCallback(() => {
|
||||
if (onSubmit && (
|
||||
(document.activeElement && !operatesWithEnterKey(document.activeElement))
|
||||
|| !document.activeElement
|
||||
)) {
|
||||
if ((document.activeElement && !operatesWithEnterKey(document.activeElement)) || !document.activeElement) {
|
||||
!disableAutoHideOnSubmit && dispatch(hideDialog());
|
||||
onSubmit();
|
||||
onSubmit?.();
|
||||
}
|
||||
}, [ onSubmit ]);
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ const useContextMenu = <T>(): [(force?: boolean | Object) => void,
|
||||
return;
|
||||
}
|
||||
|
||||
if (raiseContext !== initialState) {
|
||||
if (raiseContext !== initialState || force) {
|
||||
setRaiseContext(initialState);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
|
||||
/**
|
||||
* Tries to copy a given text to the clipboard.
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function BreakoutRoomNamePrompt({ breakoutRoomJid, initialRoomNam
|
||||
const formattedRoomName = roomName?.trim();
|
||||
|
||||
if (formattedRoomName) {
|
||||
dispatch(renameBreakoutRoom(formattedRoomName, roomName));
|
||||
dispatch(renameBreakoutRoom(breakoutRoomJid, formattedRoomName));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
@@ -33,6 +33,19 @@ const BreakoutRooms = () => {
|
||||
.sort((p1, p2) => (p1?.name || '').localeCompare(p2?.name || ''));
|
||||
const showAddBreakoutRoom = useSelector(isAddBreakoutRoomButtonVisible);
|
||||
const showAutoAssign = useSelector(isAutoAssignParticipantsVisible);
|
||||
const renderListHeaderComponent = useMemo(() => (
|
||||
<>
|
||||
{ showAutoAssign && <AutoAssignButton /> }
|
||||
{ inBreakoutRoom && <LeaveBreakoutRoomButton /> }
|
||||
{
|
||||
isBreakoutRoomsSupported
|
||||
&& rooms.map(room => (<CollapsibleRoom
|
||||
key = { room.id }
|
||||
room = { room }
|
||||
roomId = { room.id } />))
|
||||
}
|
||||
</>
|
||||
), [ showAutoAssign, inBreakoutRoom, isBreakoutRoomsSupported, rooms ]);
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
@@ -42,21 +55,7 @@ const BreakoutRooms = () => {
|
||||
|
||||
{ /* Fixes warning regarding nested lists */ }
|
||||
<FlatList
|
||||
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
ListHeaderComponent = { () => (
|
||||
<>
|
||||
{ showAutoAssign && <AutoAssignButton /> }
|
||||
{ inBreakoutRoom && <LeaveBreakoutRoomButton /> }
|
||||
{
|
||||
isBreakoutRoomsSupported
|
||||
&& rooms.map(room => (<CollapsibleRoom
|
||||
key = { room.id }
|
||||
room = { room }
|
||||
roomId = { room.id } />))
|
||||
}
|
||||
</>
|
||||
) }
|
||||
ListHeaderComponent = { renderListHeaderComponent }
|
||||
data = { [] as ReadonlyArray<undefined> }
|
||||
keyExtractor = { keyExtractor }
|
||||
renderItem = { null }
|
||||
|
||||
@@ -2,12 +2,11 @@ import React, { useCallback } from 'react';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { openHighlightDialog } from '../../../recording/actions.native';
|
||||
import HighlightButton from '../../../recording/components/Recording/native/HighlightButton';
|
||||
import RecordingLabel from '../../../recording/components/native/RecordingLabel';
|
||||
import { getActiveSession } from '../../../recording/functions';
|
||||
import { isLiveStreamingRunning } from '../../../recording/functions';
|
||||
import VisitorsCountLabel from '../../../visitors/components/native/VisitorsCountLabel';
|
||||
|
||||
import RaisedHandsCountLabel from './RaisedHandsCountLabel';
|
||||
@@ -30,8 +29,7 @@ interface IProps {
|
||||
|
||||
const AlwaysOnLabels = ({ createOnPress }: IProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const isStreaming = useSelector((state: IReduxState) =>
|
||||
Boolean(getActiveSession(state, JitsiRecordingConstants.mode.STREAM)));
|
||||
const isStreaming = useSelector(isLiveStreamingRunning);
|
||||
const openHighlightDialogCallback = useCallback(() =>
|
||||
dispatch(openHighlightDialog()), [ dispatch ]);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ export default {
|
||||
},
|
||||
|
||||
displayNameContainer: {
|
||||
margin: 10
|
||||
margin: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,8 @@ import JitsiPortal from '../../../toolbox/components/web/JitsiPortal';
|
||||
import Toolbox from '../../../toolbox/components/web/Toolbox';
|
||||
import { LAYOUT_CLASSNAMES } from '../../../video-layout/constants';
|
||||
import { getCurrentLayout } from '../../../video-layout/functions.any';
|
||||
import VisitorsQueue from '../../../visitors/components/web/VisitorsQueue';
|
||||
import { showVisitorsQueue } from '../../../visitors/functions';
|
||||
import { init } from '../../actions.web';
|
||||
import { maybeShowSuboptimalExperienceNotification } from '../../functions.web';
|
||||
import {
|
||||
@@ -100,6 +102,11 @@ interface IProps extends AbstractProps, WithTranslation {
|
||||
*/
|
||||
_showPrejoin: boolean;
|
||||
|
||||
/**
|
||||
* If visitors queue page is visible or not.
|
||||
*/
|
||||
_showVisitorsQueue: boolean;
|
||||
|
||||
dispatch: IStore['dispatch'];
|
||||
}
|
||||
|
||||
@@ -206,6 +213,7 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
_overflowDrawer,
|
||||
_showLobby,
|
||||
_showPrejoin,
|
||||
_showVisitorsQueue,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
@@ -257,8 +265,9 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
|
||||
<CalleeInfoContainer />
|
||||
|
||||
{ _showPrejoin && <Prejoin />}
|
||||
{ _showLobby && <LobbyScreen />}
|
||||
{ (_showPrejoin && !_showVisitorsQueue) && <Prejoin />}
|
||||
{ (_showLobby && !_showVisitorsQueue) && <LobbyScreen />}
|
||||
{ _showVisitorsQueue && <VisitorsQueue />}
|
||||
</div>
|
||||
<ParticipantsPane />
|
||||
<ReactionAnimations />
|
||||
@@ -402,7 +411,8 @@ function _mapStateToProps(state: IReduxState) {
|
||||
_overflowDrawer: overflowDrawer,
|
||||
_roomName: getConferenceNameForTitle(state),
|
||||
_showLobby: getIsLobbyVisible(state),
|
||||
_showPrejoin: isPrejoinPageVisible(state)
|
||||
_showPrejoin: isPrejoinPageVisible(state),
|
||||
_showVisitorsQueue: showVisitorsQueue(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ import { IReduxState, IStore } from '../app/types';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT
|
||||
CONFERENCE_LEFT,
|
||||
ENDPOINT_MESSAGE_RECEIVED
|
||||
} from '../base/conference/actionTypes';
|
||||
import { getCurrentConference } from '../base/conference/functions';
|
||||
import { getURLWithoutParamsNormalized } from '../base/connection/utils';
|
||||
@@ -19,10 +20,11 @@ import { getLocalizedDateFormatter } from '../base/i18n/dateUtil';
|
||||
import { translateToHTML } from '../base/i18n/functions';
|
||||
import i18next from '../base/i18n/i18next';
|
||||
import { browser } from '../base/lib-jitsi-meet';
|
||||
import { pinParticipant, raiseHandClear } from '../base/participants/actions';
|
||||
import { pinParticipant, raiseHand, raiseHandClear } from '../base/participants/actions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { SET_REDUCED_UI } from '../base/responsive-ui/actionTypes';
|
||||
import { LOWER_HAND_MESSAGE } from '../base/tracks/constants';
|
||||
import { BUTTON_TYPES } from '../base/ui/constants.any';
|
||||
import { inIframe } from '../base/util/iframeUtils';
|
||||
import { isCalendarEnabled } from '../calendar-sync/functions';
|
||||
@@ -71,6 +73,15 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
break;
|
||||
}
|
||||
case ENDPOINT_MESSAGE_RECEIVED: {
|
||||
const { participant, data } = action;
|
||||
const { dispatch } = store;
|
||||
|
||||
if (data.name === LOWER_HAND_MESSAGE && participant.isModerator()) {
|
||||
dispatch(raiseHand(false));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -4,6 +4,8 @@ import { IJitsiConference } from '../base/conference/reducer';
|
||||
import {
|
||||
JitsiConnectionQualityEvents
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import { trackCodecChanged } from '../base/tracks/actions.any';
|
||||
import { getLocalTracks } from '../base/tracks/functions.any';
|
||||
|
||||
/**
|
||||
* Contains all the callbacks to be notified when stats are updated.
|
||||
@@ -129,6 +131,10 @@ const statsEmitter = {
|
||||
codec: allUserCodecs[localUserId as keyof typeof allUserCodecs]
|
||||
});
|
||||
|
||||
modifiedLocalStats.codec
|
||||
&& Object.keys(modifiedLocalStats.codec).length
|
||||
&& this._updateLocalCodecs(modifiedLocalStats.codec);
|
||||
|
||||
this._emitStatsUpdate(localUserId, modifiedLocalStats);
|
||||
|
||||
// Get all the unique user ids from the framerate and resolution stats
|
||||
@@ -162,6 +168,33 @@ const statsEmitter = {
|
||||
|
||||
this._emitStatsUpdate(id, remoteUserStats);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the codec associated with the local tracks.
|
||||
* This is currently used for torture tests.
|
||||
*
|
||||
* @param {any} codecs - Codec information per local SSRC.
|
||||
* @returns {void}
|
||||
*/
|
||||
_updateLocalCodecs(codecs: any) {
|
||||
if (typeof APP !== 'undefined') {
|
||||
const tracks = APP.store.getState()['features/base/tracks'];
|
||||
const localTracks = getLocalTracks(tracks);
|
||||
|
||||
for (const track of localTracks) {
|
||||
const ssrc = track.jitsiTrack?.getSsrc();
|
||||
|
||||
if (ssrc && Object.keys(codecs).find(key => Number(key) === ssrc)) {
|
||||
const codecsPerSsrc = codecs[ssrc];
|
||||
const codec = codecsPerSsrc.audio ?? codecsPerSsrc.video;
|
||||
|
||||
if (track.codec !== codec) {
|
||||
APP.store.dispatch(trackCodecChanged(track.jitsiTrack, codec));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
25
react/features/embed-meeting/hooks.ts
Normal file
25
react/features/embed-meeting/hooks.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { isMobileBrowser } from '../base/environment/utils';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
|
||||
import EmbedMeetingButton from './components/EmbedMeetingButton';
|
||||
|
||||
const embed = {
|
||||
key: 'embedmeeting',
|
||||
Content: EmbedMeetingButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the embed button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useEmbedButton() {
|
||||
const _isVpaasMeeting = useSelector(isVpaasMeeting);
|
||||
|
||||
if (!isMobileBrowser() && !_isVpaasMeeting) {
|
||||
return embed;
|
||||
}
|
||||
}
|
||||
24
react/features/etherpad/hooks.ts
Normal file
24
react/features/etherpad/hooks.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
|
||||
import SharedDocumentButtonWeb from './components/SharedDocumentButton';
|
||||
|
||||
const etherpad = {
|
||||
key: 'etherpad',
|
||||
Content: SharedDocumentButtonWeb,
|
||||
group: 3
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the etherpad button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useEtherpadButton() {
|
||||
const visible = useSelector((state: IReduxState) => Boolean(state['features/etherpad'].documentUrl));
|
||||
|
||||
if (visible) {
|
||||
return etherpad;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,18 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const etherpadBaseUrl = sanitizeUrl(etherpadBase);
|
||||
|
||||
if (etherpadBaseUrl) {
|
||||
url = new URL(value, etherpadBaseUrl.toString()).toString();
|
||||
const urlObj = new URL(value, etherpadBaseUrl.toString());
|
||||
|
||||
// Merge query string parameters on top of internal ones
|
||||
if (etherpadBaseUrl.search) {
|
||||
const searchParams = new URLSearchParams(urlObj.search);
|
||||
|
||||
for (const [ key, val ] of new URLSearchParams(etherpadBaseUrl.search)) {
|
||||
searchParams.set(key, val);
|
||||
}
|
||||
urlObj.search = searchParams.toString();
|
||||
}
|
||||
url = urlObj.toString();
|
||||
}
|
||||
|
||||
dispatch(setDocumentUrl(url));
|
||||
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
import {
|
||||
getDominantSpeakerParticipant,
|
||||
getLocalParticipant,
|
||||
getParticipantById
|
||||
getParticipantById,
|
||||
getParticipantDisplayName
|
||||
} from '../base/participants/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { getBaseUrl } from '../base/util/helpers';
|
||||
@@ -133,15 +134,29 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
APP.API.notifyDataChannelOpened();
|
||||
break;
|
||||
|
||||
case KICKED_OUT:
|
||||
case KICKED_OUT: {
|
||||
const state = store.getState();
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
if (!localParticipant) {
|
||||
break;
|
||||
}
|
||||
|
||||
const pId = action.participant.getId();
|
||||
|
||||
APP.API.notifyKickedOut(
|
||||
{
|
||||
id: getLocalParticipant(store.getState())?.id,
|
||||
id: localParticipant.id,
|
||||
name: getParticipantDisplayName(state, localParticipant.id),
|
||||
local: true
|
||||
},
|
||||
{ id: action.participant ? action.participant.getId() : undefined }
|
||||
{
|
||||
id: pId,
|
||||
name: getParticipantDisplayName(state, pId)
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case NOTIFY_CAMERA_ERROR:
|
||||
if (action.error) {
|
||||
@@ -156,14 +171,28 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
break;
|
||||
|
||||
case PARTICIPANT_KICKED:
|
||||
case PARTICIPANT_KICKED: {
|
||||
const state = store.getState();
|
||||
const kickedParticipant = getParticipantById(state, action.kicked);
|
||||
const kickerParticipant = getParticipantById(state, action.kicker);
|
||||
|
||||
if (!kickerParticipant || !kickedParticipant) {
|
||||
break;
|
||||
}
|
||||
|
||||
APP.API.notifyKickedOut(
|
||||
{
|
||||
id: action.kicked,
|
||||
local: false
|
||||
id: kickedParticipant.id,
|
||||
local: kickedParticipant.local,
|
||||
name: getParticipantDisplayName(state, kickedParticipant.id)
|
||||
},
|
||||
{ id: action.kicker });
|
||||
{
|
||||
id: kickerParticipant.id,
|
||||
local: kickerParticipant.local,
|
||||
name: getParticipantDisplayName(state, kickerParticipant.id)
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case PARTICIPANT_LEFT: {
|
||||
const { participant } = action;
|
||||
|
||||
23
react/features/feedback/hooks.web.ts
Normal file
23
react/features/feedback/hooks.web.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import FeedbackButtonWeb from './components/FeedbackButton.web';
|
||||
import { shouldSendJaaSFeedbackMetadata } from './functions.web';
|
||||
|
||||
const feedback = {
|
||||
key: 'feedback',
|
||||
Content: FeedbackButtonWeb,
|
||||
group: 4
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the feedback button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useFeedbackButton() {
|
||||
const visible = useSelector(shouldSendJaaSFeedbackMetadata);
|
||||
|
||||
if (visible) {
|
||||
return feedback;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { getVpaasTenant, isVpaasMeeting } from './functions';
|
||||
* @returns {Function}
|
||||
*/
|
||||
|
||||
MiddlewareRegistry.register(store => next => async action => {
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED: {
|
||||
_maybeTrackVpaasConferenceJoin(store.getState());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user