Compare commits

..

13 Commits

Author SHA1 Message Date
Hristo Terezov
13d3ec9f6d fix(conference): Make sure join waits for confernce.init.
It was possible that join can be executed before conference.init have even started or we haven't reached the point ot create the initialGUMPromise. This was causing the following issues:
 - users stuck on the prejoin screen
 - participants join 2+ times in the call (we have been creating more than 1 local participants from a single page).
2024-10-25 15:33:02 -05:00
Hristo Terezov
f7acc2e15f feat(shared-video): Allow only whitelisted URLs. 2024-08-13 12:45:42 -05:00
Hristo Terezov
a2c09357ca fix(gif): Restrict gif rendering to Giphy only 2024-08-09 10:25:47 -05:00
Hristo Terezov
3ed68fdf1a fix(giphy): Remove proxyUrl config option. 2024-08-09 10:20:49 -05:00
Mihaela Dumitru
df010e6a1d fix(whiteboard) remove limit dialog for jibri (#14947) (#14952) 2024-07-30 16:49:16 +03:00
Hristo Terezov
c901c18e21 fix(push2talk): incorect state on release because a new audio track is beening created. (part 2) 2024-07-23 18:19:36 -05:00
Hristo Terezov
d57b48f8d7 fix(push2talk): incorect state on release because a new audio track is beening created.
We are ending up in incorrect mute state (unmuted) if the initial press event is resulting in a new track creation and the release event happens before the track is created.
2024-07-23 10:13:52 -05:00
Saúl Ibarra Corretgé
c549c4ce5b fix(keyboard-shortcuts) fix PTT on keyboards which send repeated keys
Come over for a fun story, dear reader!

Here is a not-so-fun difference in behavior, observed in macOS:

- The builtin keyboard doesn't seem to send the same key over and over
  again while it's being held.
- On the contrary, a USB keyboard does.

That means that for some keyboards PTT has been broken. We get
keydown/keyup pairs in quick successing.

One would think that KeyboardEvent.repeat would solve that, but it
doesn't seem to, in practice. See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat

So, in order to solve this, delay handling the keyup event by 50ms. This
way, if a new keydown comes before the keyup has been handled we'll
cancel it and act as it never happened, restoring PTT functionality.

While we're at it, use window.addEventListener rather than
onkeyup/onkeydown, since it's 2024 :-)
2024-07-23 10:13:33 -05:00
Mihaela Dumitru
0951a9dd9a fix(visitors) update dialog button (#14936) 2024-07-22 16:31:19 +03:00
Mihaela Dumitru
133e36ead6 feat(visitors) add info dialog (#14926) 2024-07-19 10:17:10 +03:00
Saúl Ibarra Corretgé
914ad85baa feat(external_api) add ability to start transcriptions together with recordings 2024-07-03 20:52:38 +02:00
Saúl Ibarra Corretgé
70c3a2b01c feat(external_api) include transcription state in recordingStatusChanged 2024-07-03 20:52:38 +02:00
Saúl Ibarra Corretgé
0ce193e5c3 feat(recording) add isLiveStreamingRunning helper 2024-07-03 20:52:38 +02:00
183 changed files with 5041 additions and 6976 deletions

View File

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

View File

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

View File

@@ -26,5 +26,5 @@ android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
appVersion=24.3.0
sdkVersion=9.3.0
appVersion=99.0.0
sdkVersion=99.0.0

View File

@@ -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();
}
}

View File

@@ -17,7 +17,6 @@
package org.jitsi.meet.sdk;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.Manifest.permission.RECORD_AUDIO;
import android.app.Activity;
import android.app.Notification;
@@ -39,9 +38,7 @@ 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;
/**
@@ -60,7 +57,7 @@ public class JitsiMeetOngoingConferenceService extends Service
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver();
private static final int PERMISSIONS_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE);
private static final int POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE);
private boolean isAudioMuted;
@@ -98,50 +95,26 @@ public class JitsiMeetOngoingConferenceService extends Service
public static void launch(Context context, HashMap<String, Object> extraData) {
List<String> permissionsList = new ArrayList<>();
PermissionListener listener = new PermissionListener() {
@Override
public boolean onRequestPermissionsResult(int i, String[] strings, int[] results) {
int counter = 0;
if (results.length > 0) {
for (int result : results) {
if (result == PackageManager.PERMISSION_GRANTED) {
counter++;
}
}
if (counter == results.length){
doLaunch(context, extraData);
JitsiMeetLogger.w(TAG + " Service launched, permissions were granted");
} else {
JitsiMeetLogger.w(TAG + " Couldn't launch service, permissions were not granted");
}
}
return true;
}
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissionsList.add(POST_NOTIFICATIONS);
permissionsList.add(RECORD_AUDIO);
}
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);
}
String[] permissionsArray = new String[ permissionsList.size() ];
permissionsArray = permissionsList.toArray( permissionsArray );
return true;
}
};
if (permissionsArray.length > 0) {
JitsiMeetActivityDelegate.requestPermissions(
(Activity) context,
permissionsArray,
PERMISSIONS_REQUEST_CODE,
new String[]{POST_NOTIFICATIONS},
POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE,
listener
);
} else {
doLaunch(context, extraData);
JitsiMeetLogger.w(TAG + " Service launched");
}
}
@@ -159,10 +132,8 @@ 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.R) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
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);
}

View File

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

View File

@@ -6,7 +6,7 @@ import Logger from '@jitsi/logger';
import { ENDPOINT_TEXT_MESSAGE_NAME } from './modules/API/constants';
import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
import Recorder from './modules/recorder/Recorder';
import { createTaskQueue } from './modules/util/helpers';
import { createDeferred, createTaskQueue } from './modules/util/helpers';
import {
createDeviceChangedEvent,
createScreenSharingEvent,
@@ -83,7 +83,6 @@ import {
setAudioAvailable,
setAudioMuted,
setAudioUnmutePermissions,
setInitialGUMPromise,
setVideoAvailable,
setVideoMuted,
setVideoUnmutePermissions
@@ -155,7 +154,8 @@ import {
import { isModerationNotificationDisplayed } from './react/features/notifications/functions';
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
import { suspendDetected } from './react/features/power-monitor/actions';
import { initPrejoin, isPrejoinPageVisible } from './react/features/prejoin/functions';
import { initPrejoin } from './react/features/prejoin/actions';
import { isPrejoinPageVisible } from './react/features/prejoin/functions';
import { disableReceiver, stopReceiver } from './react/features/remote-control/actions';
import { setScreenAudioShareState } from './react/features/screen-share/actions.web';
import { isScreenAudioShared } from './react/features/screen-share/functions';
@@ -168,6 +168,8 @@ import { muteLocal } from './react/features/video-menu/actions.any';
const logger = Logger.getLogger(__filename);
let room;
const initPromise = createDeferred();
/*
* Logic to open a desktop picker put on the window global for
* lib-jitsi-meet to detect and invoke
@@ -509,49 +511,56 @@ export default {
},
startConference(tracks) {
tracks.forEach(track => {
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|| (track.isVideoTrack() && this.isLocalVideoMuted())) {
const mediaType = track.getType();
logger.debug('startConference called!');
sendAnalytics(
createTrackMutedEvent(mediaType, 'initial mute'));
logger.log(`${mediaType} mute: initially muted.`);
track.mute();
const promise = initPromise?.promise || Promise.resolve();
return promise.then(() => {
logger.debug('startConference start execution!');
tracks.forEach(track => {
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|| (track.isVideoTrack() && this.isLocalVideoMuted())) {
const mediaType = track.getType();
sendAnalytics(
createTrackMutedEvent(mediaType, 'initial mute'));
logger.log(`${mediaType} mute: initially muted.`);
track.mute();
}
});
this._createRoom(tracks);
// if user didn't give access to mic or camera or doesn't have
// them at all, we mark corresponding toolbar buttons as muted,
// so that the user can try unmute later on and add audio/video
// to the conference
if (!tracks.find(t => t.isAudioTrack())) {
this.updateAudioIconEnabled();
}
});
this._createRoom(tracks);
if (!tracks.find(t => t.isVideoTrack())) {
this.setVideoMuteStatus();
}
// if user didn't give access to mic or camera or doesn't have
// them at all, we mark corresponding toolbar buttons as muted,
// so that the user can try unmute later on and add audio/video
// to the conference
if (!tracks.find(t => t.isAudioTrack())) {
this.updateAudioIconEnabled();
}
if (config.iAmRecorder) {
this.recorder = new Recorder();
}
if (!tracks.find(t => t.isVideoTrack())) {
this.setVideoMuteStatus();
}
if (config.startSilent) {
sendAnalytics(createStartSilentEvent());
APP.store.dispatch(showNotification({
descriptionKey: 'notify.startSilentDescription',
titleKey: 'notify.startSilentTitle'
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}
if (config.iAmRecorder) {
this.recorder = new Recorder();
}
if (config.startSilent) {
sendAnalytics(createStartSilentEvent());
APP.store.dispatch(showNotification({
descriptionKey: 'notify.startSilentDescription',
titleKey: 'notify.startSilentTitle'
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}
// XXX The API will take care of disconnecting from the XMPP
// server (and, thus, leaving the room) on unload.
return new Promise((resolve, reject) => {
new ConferenceConnector(resolve, reject, this).connect();
});
// XXX The API will take care of disconnecting from the XMPP
// server (and, thus, leaving the room) on unload.
return new Promise((resolve, reject) => {
new ConferenceConnector(resolve, reject, this).connect();
});
}).catch(logger.error);
},
/**
@@ -591,7 +600,7 @@ export default {
const handleInitialTracks = (options, tracks) => {
let localTracks = tracks;
if (options.startWithAudioMuted) {
if (options.startWithAudioMuted || room?.isStartAudioMuted()) {
// Always add the track on Safari because of a known issue where audio playout doesn't happen
// if the user joins audio and video muted, i.e., if there is no local media capture.
if (browser.isWebKitBased()) {
@@ -600,38 +609,79 @@ export default {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
}
}
if (room?.isStartVideoMuted()) {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
}
return localTracks;
};
const { dispatch, getState } = APP.store;
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
dispatch(setInitialGUMPromise(tryCreateLocalTracks.then(async tr => {
const tracks = handleInitialTracks(initialOptions, tr);
if (isPrejoinPageVisible(state)) {
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
const localTracks = await tryCreateLocalTracks;
// Initialize device list a second time to ensure device labels get populated in case of an initial gUM
// acceptance; otherwise they may remain as empty strings.
this._initDeviceList(true);
if (isPrejoinPageVisible(getState())) {
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
dispatch(setInitialGUMPromise());
if (isPrejoinPageVisible(state)) {
APP.store.dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
// Note: Not sure if initPrejoin needs to be async. But let's wait for it just to be sure the
// tracks are added.
initPrejoin(tracks, errors, dispatch);
} else {
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
setGUMPendingStateOnFailedTracks(tracks, APP.store.dispatch);
const promise = APP.store.dispatch(initPrejoin(localTracks, errors));
promise.finally(() => {
logger.debug('Resolving initPromise! prejoinVisible=true');
initPromise.resolve();
});
return promise;
}
return {
tracks,
errors
};
})));
logger.debug('Prejoin screen no longer displayed at the time when tracks were created');
if (!isPrejoinPageVisible(getState())) {
dispatch(connect());
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
const tracks = handleInitialTracks(initialOptions, localTracks);
setGUMPendingStateOnFailedTracks(tracks, APP.store.dispatch);
const promise = this._setLocalAudioVideoStreams(tracks);
promise.finally(() => {
logger.debug('Resolving initPromise! Prejoin was visible initially but now it is hidden.');
initPromise.resolve();
});
return promise;
}
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
const promise = Promise.all([
tryCreateLocalTracks.then(tr => {
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
return tr;
}).then(tr => {
this._initDeviceList(true);
const filteredTracks = handleInitialTracks(initialOptions, tr);
setGUMPendingStateOnFailedTracks(filteredTracks, APP.store.dispatch);
return filteredTracks;
}),
APP.store.dispatch(connect())
]);
promise.finally(() => {
logger.debug('Resolving initPromise! prejoinVisible=false');
initPromise.resolve();
});
return promise.then(([ tracks, _ ]) => {
this.startConference(tracks).catch(logger.error);
});
},
/**
@@ -1145,12 +1195,11 @@ 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();
@@ -1659,18 +1708,6 @@ export default {
});
}
);
room.on(
JitsiConferenceEvents.SILENT_STATUS_CHANGED,
(id, isSilent) => {
APP.store.dispatch(participantUpdated({
conference: room,
id,
isSilent
}));
}
);
room.on(
JitsiConferenceEvents.BOT_TYPE_CHANGED,
(id, botType) => {

View File

@@ -88,8 +88,8 @@ var config = {
// issues related to insertable streams.
// disableE2EE: false,
// Enables the use of the codec selection API supported by the browsers .
// enableCodecSelectionAPI: false,
// Enables supports for AV1 codec.
// enableAv1Support: false,
// P2P test mode disables automatic switching to P2P when there are 2
// participants in the conference.
@@ -121,9 +121,6 @@ var config = {
// Disables polls feature.
// disablePolls: false,
// Disables demote button from self-view
// disableSelfDemote: false,
// Disables self-view tile. (hides it from tile view and from filmstrip)
// disableSelfView: false,
@@ -461,10 +458,6 @@ var config = {
// // Provides a way to set the codec preference on desktop based endpoints.
// codecPreferenceOrder: [ 'VP9', 'VP8', 'H264' ],
//
// // Provides a way to set the codec for screenshare.
// screenshareCodec: 'AV1',
// mobileScreenshareCodec: 'VP8',
//
// // Codec specific settings for scalability modes and max bitrates.
// av1: {
// maxBitratesVideo: {
@@ -513,7 +506,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
@@ -1054,10 +1047,6 @@ var config = {
// Provides a way to set the codec preference on desktop based endpoints.
// codecPreferenceOrder: [ 'VP9', 'VP8', 'H264 ],
// Provides a way to set the codec for screenshare.
// screenshareCodec: 'AV1',
// mobileScreenshareCodec: 'VP8',
// How long we're going to wait, before going back to P2P after the 3rd
// participant has left the conference (to filter out page reload).
// backToP2PDelay: 5,
@@ -1747,8 +1736,6 @@ var config = {
// tileTime: 5000,
// // Limit results by rating: g, pg, pg-13, r. Default value: g.
// rating: 'pg',
// // The proxy server url for giphy requests in the web app.
// proxyUrl: 'https://giphy-proxy.example.com',
// },
// Logging

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -375,7 +375,6 @@
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
0BB7DA181EC9E695007AAE98 /* Adjust ATS */,
DE4F6D6E22005C0400DE699E /* Setup Dropbox */,
E9D850368D253EFA8AB3B8D1 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -612,23 +611,6 @@
shellPath = /bin/sh;
shellScript = "INFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nDROPBOX_KEY_FILE=\"$PROJECT_DIR/dropbox.key\"\n\nif [[ -f $DROPBOX_KEY_FILE ]]; then\n /usr/libexec/PlistBuddy -c \"Delete :LSApplicationQueriesSchemes\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes array\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes:0 string 'dbapi-2'\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes:1 string 'dbapi-8-emm'\" $INFO_PLIST\n\n DROPBOX_KEY=$(head -n 1 $DROPBOX_KEY_FILE)\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLName string dropbox\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLSchemes array\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLSchemes:0 string $DROPBOX_KEY\" $INFO_PLIST\nfi\n";
};
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 */
@@ -986,7 +968,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -1043,7 +1025,6 @@
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
USE_HERMES = false;
};
name = Debug;
};
@@ -1052,7 +1033,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -1105,7 +1086,6 @@
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
TARGETED_DEVICE_FAMILY = "1,2";
USE_HERMES = false;
VALIDATE_PRODUCT = YES;
};
name = Release;

View File

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

View File

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

View File

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

View File

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

View File

@@ -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++20";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -785,7 +785,6 @@
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
USE_HERMES = false;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
@@ -798,7 +797,7 @@
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -850,7 +849,6 @@
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
TARGETED_DEVICE_FAMILY = "1,2";
USE_HERMES = false;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";

View File

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

View File

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

View File

@@ -925,7 +925,7 @@
"iWantToDialIn": "Mi volas alvoki",
"initiated": "Voko komencita",
"joinAudioByPhone": "Aliĝu kun telefona mikrofono",
"joinMeeting": "Aliĝu al la kunveno",
"joinMeeting": "Aliĝu al la kunvenon",
"joinMeetingInLowBandwidthMode": "Aliĝu en malaltkapacita modo",
"joinWithoutAudio": "Aliĝu sen mikrofono",
"keyboardShortcuts": "Ŝaltu fulmoklavojn",

View File

@@ -128,7 +128,6 @@
"privateNotice": "Message privé à {{recipient}}",
"sendButton": "Envoyer",
"smileysPanel": "Panneaux des Émojis",
"systemDisplayName": "Système",
"tabs": {
"chat": "Chat",
"polls": "Sondages"
@@ -220,9 +219,7 @@
"joinInBrowser": "Rejoindre depuis le navigateur",
"launchMeetingLabel": "Comment voulez-vous rejoindre la réunion ?",
"launchWebButton": "Lancer dans le navigateur",
"noDesktopApp": "Vous n'avez pas l'application ?",
"noMobileApp": "Vous navez pas lapplication ?",
"or": "OU",
"termsAndConditions": "En continuant, vous acceptez nos <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>conditions générales dutilisation.</a>",
"title": "Lancement de votre réunion dans {{app}} en cours ...",
"titleNew": "Lancement de votre réunion ...",
@@ -264,7 +261,6 @@
"Share": "Partager",
"Submit": "Soumettre",
"WaitForHostMsg": "La conférence n'a pas encore commencé. Si vous en êtes l'hôte, veuillez vous authentifier. Sinon, veuillez attendre son arrivée.",
"WaitForHostNoAuthMsg": "La conférence n'a pas encore commencé car aucun modérateur n'est encore arrivé. Veuillez patienter.",
"WaitingForHostButton": "Attendre l'hôte",
"WaitingForHostTitle": "En attente de l'hôte ...",
"Yes": "Oui",
@@ -307,8 +303,6 @@
"contactSupport": "Contacter le support",
"copied": "Copié",
"copy": "Copier",
"demoteParticipantDialog": "Êtes-vous sûr de vouloir déplacer ce participant en visiteur ?",
"demoteParticipantTitle": "Déplacer en visiteur",
"dismiss": "Rejeter",
"displayNameRequired": "Bonjour ! Quel est votre nom ?",
"done": "Terminé",
@@ -320,7 +314,6 @@
"embedMeeting": "Intégrer la réunion",
"enterDisplayName": "Merci de saisir votre nom ici",
"error": "Erreur",
"errorRoomCreationRestriction": "Vous avez essayé de rejoindre trop rapidement, veuillez revenir dans un moment.",
"gracefulShutdown": "Notre service est actuellement en maintenance. Veuillez réessayer plus tard.",
"grantModeratorDialog": "Êtes-vous sûr de vouloir rendre ce participant modérateur ?",
"grantModeratorTitle": "Nommer modérateur",
@@ -565,7 +558,6 @@
"noNumbers": "Numéros non trouvés",
"noPassword": "Aucun",
"noRoom": "Aucune réunion n'a été spécifiée pour l'appel entrant.",
"noWhiteboard": "Impossible de charger le tableau blanc.",
"numbers": "Numéros d'appel",
"password": "$t(lockRoomPasswordUppercase) :",
"reachedLimit": "Vous avez atteint la limite de votre abonnement.",
@@ -573,8 +565,7 @@
"sipAudioOnly": "Adresse SIP en audio uniquement",
"title": "Partager",
"tooltip": "Partager le lien et les informations de connexion pour cette conférence",
"upgradeOptions": "Veuillez vérifier les options de mise à niveau",
"whiteboardError": "Erreur de chargement du tableau blanc. Veuillez réessayer plus tard."
"upgradeOptions": "Veuillez vérifier les options de mise à niveau"
},
"inlineDialogFailure": {
"msg": "Il y a eu un petit problème.",
@@ -738,8 +729,6 @@
"connectedTwoMembers": "{{first}} et {{second}} ont rejoint la réunion",
"dataChannelClosed": "Qualité vidéo dégradée",
"dataChannelClosedDescription": "Le canal de communication avec le Bridge a été interrompu, la qualité vidéo se trouve limitée à sa valeur la plus faible.",
"dataChannelClosedDescriptionWithAudio": "Le canal de pont est fermé, ce qui peut entraîner des perturbations de l'audio et de la vidéo.",
"dataChannelClosedWithAudio": "La qualité de l'audio et de la vidéo peut être altérée",
"disabledIframe": "L'intégration Iframe est uniquement destinée à des démos, cet appel se terminera dans {{timeout}} minutes.",
"disabledIframeSecondary": "L'intégration Iframe de {{domaine}} est uniquement destinée à des démos, cet appel se terminera dans {{timeout}} minutes.",
"disconnected": "déconnecté",
@@ -811,19 +800,13 @@
"startSilentTitle": "Vous avez rejoint sans sortie audio !",
"suboptimalBrowserWarning": "Nous craignons que votre expérience de réunion en ligne ne soit pas idéale ici. Nous cherchons des moyens d'améliorer cela, mais d'ici-là, essayez d'utiliser l'un des <a href='{{recommendedBrowserPageLink}}' target='_blank'>navigateurs supportés</a>.",
"suboptimalExperienceTitle": "Avertissement du navigateur",
"suggestRecordingAction": "Démarrer",
"suggestRecordingDescription": "Souhaitez-vous démarrer un enregistrement ?",
"suggestRecordingTitle": "Enregistrer cette réunion",
"unmute": "Rétablir le son",
"videoMutedRemotelyDescription": "Vous pouvez toujours la réactiver.",
"videoMutedRemotelyTitle": "Votre caméra a été coupée par {{participantDisplayName}}!",
"videoUnmuteBlockedDescription": "Le rétablissement de la vidéo a été bloqué temporairement en raison de limites système.",
"videoUnmuteBlockedTitle": "Rétablissement de la caméra bloqué !",
"viewLobby": "Voir la salle d'attente",
"viewVisitors": "Voir les visiteurs",
"waitingParticipants": "{{waitingParticipants}} personnes",
"waitingVisitors": "Visiteurs en attente dans la file : {{waitingVisitors}}",
"waitingVisitorsTitle": "La réunion n'est pas encore en direct !",
"whiteboardLimitDescription": "Veuillez sauvegarder votre progression, car la limite dutilisation du tableau blanc sera bientôt atteinte et celui-ci sera fermé.",
"whiteboardLimitTitle": "Utiilisation du tableau blanc"
},
@@ -837,7 +820,6 @@
"audioModeration": "Rouvrir leur micro",
"blockEveryoneMicCamera": "Bloquer tous les micros et caméras",
"breakoutRooms": "Salles annexes",
"goLive": "Passer en direct",
"invite": "Inviter quelqu'un",
"moreModerationActions": "Options de modération supplémentaires",
"moreModerationControls": "Options de modération supplémentaires",
@@ -855,7 +837,6 @@
"headings": {
"lobby": "Salle d'attente ({{count}})",
"participantsList": "Participants de la réunion ({{count}})",
"visitorInQueue": " (en attente {{count}})",
"visitorRequests": "(Demande {{count}} )",
"visitors": "Visiteurs {{count}}",
"waitingLobby": "Dans la salle d'attente ({{count}})"
@@ -869,8 +850,6 @@
"pinnedParticipant": "Participant toujours affiché",
"polls": {
"answer": {
"edit": "Modifier",
"send": "Envoyer",
"skip": "Passer",
"submit": "Envoyer"
},
@@ -884,7 +863,6 @@
"pollQuestion": "Question du sondage",
"questionPlaceholder": "Poser une question",
"removeOption": "Supprimer l'option",
"save": "Enregistrer",
"send": "Envoyer"
},
"errors": {
@@ -957,7 +935,6 @@
"or": "ou",
"premeeting": "Pré-séance",
"proceedAnyway": "Continuer quand même",
"recordingWarning": "D'autres participants peuvent enregistrer cet appel",
"screenSharingError": "Erreur de partage d'écran:",
"showScreen": "Activer l'écran de pré-séance",
"startWithPhone": "Commencez avec l'audio du téléphone",
@@ -1379,9 +1356,13 @@
},
"transcribing": {
"ccButtonTooltip": "Activer / Désactiver les sous-titres",
"error": "Échec de la transcription. Veuillez réessayer.",
"expandedLabel": "La transcription est actuellement activée",
"failedToStart": "Échec de démarrage de la transcription",
"labelToolTip": "La transcription de la réunion est en cours",
"off": "La transcription est désactivée",
"on": "La transcription est activée",
"pending": "Préparation de la transcription de la réunion ...",
"sourceLanguageDesc": "Actuellement, la langue de la réunion est sélectionnée à <b>{{sourceLanguage}}</b>. <br/> Vous pouvez la changer à partir de ",
"sourceLanguageHere": "ici",
"start": "Activer les sous-titres",
@@ -1437,7 +1418,6 @@
},
"videothumbnail": {
"connectionInfo": "Informations de la connexion",
"demote": "Déplacer en visiteur",
"domute": "Couper le micro",
"domuteOthers": "Couper le micro de tous les autres",
"domuteVideo": "Couper la caméra",
@@ -1492,15 +1472,9 @@
"chatIndicator": "(visiteur)",
"labelTooltip": "Nombre de Visiteurs",
"notification": {
"demoteDescription": "Envoyé ici par {{actor}}, levez la main pour participer",
"description": "Pour participer lever la main.",
"noMainParticipantsDescription": "Un participant doit démarrer la réunion. Veuillez réessayer dans un moment.",
"noMainParticipantsTitle": "Cette réunion n'a pas encore commencé.",
"noVisitorLobby": "Vous ne pouvez pas rejoindre tant qu'une salle d'attente est activée pour la réunion.",
"notAllowedPromotion": "Un participant doit d'abord autoriser votre demande.",
"title": "Vous êtes visiteur dans cette réunion"
},
"waitingMessage": "Vous rejoindrez la réunion dès qu'elle sera en direct !"
}
},
"volumeSlider": "Curseur de volume",
"welcomepage": {
@@ -1558,7 +1532,6 @@
"whiteboard": {
"accessibilityLabel": {
"heading": "Tableau blanc"
},
"screenTitle": "Tableau blanc"
}
}
}

View File

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

View File

@@ -263,8 +263,7 @@
"Remove": "Noņemt",
"Share": "Kopīgot",
"Submit": "Iesniegt",
"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.",
"WaitForHostMsg": "Sapulce vēl nav sākusies. Ja esat sapulces rīkotājs, lūdzu autorizējaties. Pretējā gadījumā, lūdzu, uzgaidiet.",
"WaitingForHostButton": "Gaidīt rīkotāju",
"WaitingForHostTitle": "Gaida rīkotāju...",
"Yes": "Jā",
@@ -865,8 +864,6 @@
"pinnedParticipant": "Dalībnieks ir piesprausts",
"polls": {
"answer": {
"edit": "Labot",
"send": "Nosūtīt",
"skip": "Izlaist",
"submit": "Iesniegt"
},
@@ -880,7 +877,6 @@
"pollQuestion": "Aptaujas Jautājums",
"questionPlaceholder": "Uzdod jautājumu",
"removeOption": "Noņemt opciju",
"save": "Saglabāt",
"send": "Nosūtīt"
},
"errors": {

View File

@@ -263,8 +263,7 @@
"Remove": "Kaldır",
"Share": "Paylaş",
"Submit": "Gönder",
"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.",
"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.",
"WaitingForHostButton": "Toplantı sahibini bekle",
"WaitingForHostTitle": "Toplantı sahibi bekleniyor ...",
"Yes": "Evet",
@@ -320,7 +319,6 @@
"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",
@@ -822,8 +820,6 @@
"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ı"
},
@@ -837,7 +833,6 @@
"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ü",
@@ -855,7 +850,6 @@
"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}})"
@@ -869,8 +863,6 @@
"pinnedParticipant": "Katılımcı sabitlendi",
"polls": {
"answer": {
"edit": "Düzenle",
"send": "Gönder",
"skip": "Geç",
"submit": "Gönder"
},
@@ -884,7 +876,6 @@
"pollQuestion": "Anket Sorusu",
"questionPlaceholder": "Soru sor",
"removeOption": "Seçeneği sil",
"save": "Kaydet",
"send": "Gönder"
},
"errors": {
@@ -1494,13 +1485,8 @@
"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": {

View File

@@ -443,7 +443,7 @@
"shareVideoTitle": "Share video",
"shareYourScreen": "Share your screen",
"shareYourScreenDisabled": "Screen sharing disabled.",
"sharedVideoDialogError": "Error: Invalid URL",
"sharedVideoDialogError": "Error: Invalid or forbidden URL",
"sharedVideoLinkPlaceholder": "YouTube link or direct video link",
"show": "Show",
"start": "Start ",
@@ -786,7 +786,6 @@
"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.",
@@ -823,8 +822,6 @@
"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"
},
@@ -838,10 +835,7 @@
"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",
@@ -858,7 +852,6 @@
"headings": {
"lobby": "Lobby ({{count}})",
"participantsList": "Meeting participants ({{count}})",
"visitorInQueue": " (waiting {{count}})",
"visitorRequests": " (requests {{count}})",
"visitors": "Visitors {{count}}",
"waitingLobby": "Waiting in lobby ({{count}})"
@@ -878,7 +871,6 @@
"submit": "Submit"
},
"by": "By {{ name }}",
"closeButton": "Close poll",
"create": {
"addOption": "Add option",
"answerPlaceholder": "Option {{index}}",
@@ -1509,8 +1501,7 @@
"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": {

View File

@@ -1126,12 +1126,7 @@ class API {
this.notifyBrowserSupport(isSupportedBrowser());
// Let the embedder know we are ready.
this._sendEvent({
name: 'ready',
// XXX: Here we are using window.config since this is fired really early.
info: window.config.deploymentInfo
});
this._sendEvent({ name: 'ready' });
}
/**
@@ -1819,9 +1814,9 @@ class API {
* Notify external application of a participant, remote or local, being
* removed from the conference by another participant.
*
* @param {Object} kicked - The participant removed from the
* @param {string} kicked - The ID of the participant removed from the
* conference.
* @param {Object} kicker - The participant that removed the
* @param {string} kicker - The ID of the participant that removed the
* other participant.
* @returns {void}
*/

7001
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@
"author": "",
"readmeFilename": "README.md",
"dependencies": {
"@amplitude/react-native": "2.17.3",
"@amplitude/react-native": "2.7.0",
"@braintree/sanitize-url": "7.0.0",
"@emotion/react": "11.10.6",
"@emotion/styled": "11.10.6",
@@ -31,17 +31,16 @@
"@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.23.1",
"@react-native-clipboard/clipboard": "1.14.1",
"@react-native-async-storage/async-storage": "1.19.4",
"@react-native-community/clipboard": "1.5.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.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",
"@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",
"@svgr/webpack": "6.3.1",
"@tensorflow/tfjs-backend-wasm": "3.13.0",
"@tensorflow/tfjs-core": "3.13.0",
@@ -67,7 +66,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1845.0.0+515a927c/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1836.0.0+d05325f3/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -78,17 +77,17 @@
"punycode": "2.3.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-emoji-render": "2.0.1",
"react-emoji-render": "1.2.4",
"react-focus-on": "3.8.1",
"react-i18next": "10.11.4",
"react-linkify": "1.0.0-alpha",
"react-native": "0.73.8",
"react-native": "0.72.14",
"react-native-background-timer": "2.4.1",
"react-native-calendar-events": "2.2.0",
"react-native-default-preference": "1.4.4",
"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.17.1",
"react-native-gesture-handler": "2.9.0",
"react-native-get-random-values": "1.9.0",
"react-native-immersive-mode": "2.0.1",
"react-native-keep-awake": "4.0.0",
@@ -97,16 +96,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.32.0",
"react-native-screens": "3.24.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.2.0",
"react-native-svg-transformer": "1.1.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": "124.0.3",
"react-native-webrtc": "124.0.1",
"react-native-webview": "13.8.7",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
@@ -127,13 +126,13 @@
"zxcvbn": "4.4.2"
},
"devDependencies": {
"@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",
"@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",
"@jitsi/eslint-config": "4.1.10",
"@react-native/metro-config": "0.73.5",
"@react-native/metro-config": "0.72.12",
"@types/amplitude-js": "8.16.5",
"@types/audioworklet": "0.0.29",
"@types/dom-screen-wake-lock": "1.0.1",
@@ -146,6 +145,7 @@
"@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": "9.1.0",
"babel-loader": "8.2.3",
"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.77.0",
"metro-react-native-babel-preset": "0.75.1",
"patch-package": "6.4.7",
"process": "0.11.10",
"sass": "1.26.8",

View File

@@ -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;

View File

@@ -17,7 +17,6 @@ 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';
@@ -31,7 +30,6 @@ interface IEventListeners {
onConferenceLeft?: Function;
onConferenceWillJoin?: Function;
onEnterPictureInPicture?: Function;
onEndpointMessageReceived?: Function;
onParticipantJoined?: Function;
onParticipantLeft?: ({ id }: { id: string }) => void;
onReadyToClose?: Function;
@@ -56,7 +54,6 @@ interface IAppProps {
export interface JitsiRefProps {
close: Function;
setAudioOnly?: (value: boolean) => void;
setAudioMuted?: (muted: boolean) => void;
setVideoMuted?: (muted: boolean) => void;
getRoomsInfo?: () => IRoomsInfo;
@@ -86,11 +83,6 @@ 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;
@@ -141,7 +133,6 @@ 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

View File

@@ -57,11 +57,10 @@
"@giphy/react-native-sdk": "0.0.0",
"@react-native/metro-config": "*",
"@react-native-async-storage/async-storage": "0.0.0",
"@react-native-clipboard/clipboard": "0.0.0",
"@react-native-community/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",

View File

@@ -21,6 +21,11 @@ function updateDependencies() {
for (const key in RNSDKpackageJSON.peerDependencies) {
if (!packageJSON.dependencies.hasOwnProperty(key)) {
if (packageJSON.devDependencies.hasOwnProperty('@react-native/metro-config')) {
continue;
}
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
updated = true;
}

View File

@@ -39,7 +39,6 @@ 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';

View File

@@ -41,7 +41,6 @@ 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';

View File

@@ -60,7 +60,6 @@ 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';
@@ -150,7 +149,6 @@ 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;

View File

@@ -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,7 +134,9 @@ class LoginDialog extends Component<IProps, IState> {
if (conference) {
dispatch(authenticateAndUpgradeRole(jid, password, conference));
} else {
dispatch(connect(jid, password));
// 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));
}
}

View File

@@ -143,8 +143,7 @@ MiddlewareRegistry.register(store => next => action => {
case CONNECTION_FAILED: {
const { error } = action;
const { getState } = store;
const state = getState();
const state = store.getState();
const { jwt } = state['features/base/jwt'];
if (error

View File

@@ -18,7 +18,7 @@ let pressureObserver: typeof window.PressureObserver;
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(() => (next: Function) => (action: AnyAction) => {
MiddlewareRegistry.register(() => (next: Function) => async (action: AnyAction) => {
switch (action.type) {
case APP_WILL_MOUNT: {

View File

@@ -46,6 +46,6 @@ export function toggleAudioOnly() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const { enabled } = getState()['features/base/audio-only'];
dispatch(setAudioOnly(!enabled));
return dispatch(setAudioOnly(!enabled));
};
}

View File

@@ -228,14 +228,6 @@ 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) => {
@@ -875,7 +867,7 @@ export function setPassword(
password?: string) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
if (!conference) {
return Promise.reject();
return;
}
switch (method) {
case conference.join: {
@@ -981,7 +973,7 @@ export function setStartMutedPolicy(
video: startVideoMuted
});
dispatch(
return dispatch(
onStartMutedPolicyChanged(startAudioMuted, startVideoMuted));
};
}
@@ -1058,20 +1050,14 @@ export function redirect(vnode: string, focusJid: string, username: string) {
return;
}
dispatch(overwriteConfig(newConfig));
dispatch(overwriteConfig(newConfig)) // @ts-ignore
.then(() => dispatch(disconnect(true)))
.then(() => dispatch(setIAmVisitor(Boolean(vnode))))
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());
})
// we do not clear local tracks on error, so we need to manually clear them
.then(() => dispatch(destroyLocalTracks()))
.then(() => dispatch(conferenceWillInit()))
.then(() => dispatch(connect()))
.then(() => {
const media: Array<MediaType> = [];

View File

@@ -21,5 +21,11 @@ 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([]);
}
};
}

View File

@@ -37,6 +37,14 @@ 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.
*

View File

@@ -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 { connectionDisconnected, disconnect } from '../connection/actions';
import { connect, 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,6 +37,7 @@ 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,
@@ -204,11 +205,20 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
const newConfig = restoreConferenceOptions(getState);
if (newConfig) {
dispatch(overwriteConfig(newConfig));
dispatch(conferenceWillLeave(conference));
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);
conference.leave()
.then(() => dispatch(disconnect()));
APP.conference.startConference(jitsiTracks).catch(logger.error);
}
});
}
break;
@@ -696,10 +706,6 @@ 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'];

View File

@@ -4,17 +4,9 @@ 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, browser } from '../lib-jitsi-meet';
import { gumPending, setInitialGUMPromise } from '../media/actions';
import { MEDIA_TYPE } from '../media/constants';
import { IGUMPendingState } from '../media/types';
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { replaceLocalTrack } from '../tracks/actions.any';
import { getLocalTracks } from '../tracks/functions.any';
import {
CONFERENCE_FAILED,
@@ -139,75 +131,6 @@ 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);

View File

@@ -54,9 +54,6 @@ export interface IConferenceMetadata {
recording?: {
isTranscribingEnabled: boolean;
};
visitors?: {
live: boolean;
};
whiteboard?: {
collabDetails: {
roomId: string;
@@ -135,7 +132,6 @@ export interface IJitsiConference {
setAssumedBandwidthBps: (value: number) => void;
setDesktopSharingFrameRate: Function;
setDisplayName: Function;
setIsSilent: Function;
setLocalParticipantProperty: Function;
setMediaEncryptionKey: Function;
setReceiverConstraints: Function;

View File

@@ -287,7 +287,6 @@ export interface IConfig {
disableRemoveRaisedHandOnFocus?: boolean;
disableResponsiveTiles?: boolean;
disableRtx?: boolean;
disableSelfDemote?: boolean;
disableSelfView?: boolean;
disableSelfViewSettings?: boolean;
disableShortcuts?: boolean;
@@ -375,7 +374,6 @@ export interface IConfig {
giphy?: {
displayMode?: 'all' | 'tile' | 'chat';
enabled?: boolean;
proxyUrl?: string;
rating?: 'g' | 'pg' | 'pg-13' | 'r';
sdkKey?: string;
tileTime?: number;
@@ -479,7 +477,6 @@ export interface IConfig {
peopleSearchQueryTypes?: string[];
peopleSearchUrl?: string;
preferBosh?: boolean;
preferVisitor?: boolean;
preferredTranscribeLanguage?: string;
prejoinConfig?: {
enabled?: boolean;

View File

@@ -116,7 +116,6 @@ export default [
'disableRemoteMute',
'disableResponsiveTiles',
'disableRtx',
'disableSelfDemote',
'disableSelfView',
'disableSelfViewSettings',
'disableShortcuts',
@@ -198,7 +197,6 @@ export default [
'participantsPane',
'pcStatsInterval',
'preferBosh',
'preferVisitor',
'prejoinConfig',
'prejoinPageEnabled',
'recordingService',

View File

@@ -6,7 +6,6 @@ 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';
@@ -185,31 +184,6 @@ 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.
*

View File

@@ -80,7 +80,6 @@ export interface IConfigState extends IConfig {
audio?: boolean;
video?: boolean;
};
queueService: string;
};
}

View File

@@ -1,8 +1,5 @@
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';
@@ -16,12 +13,7 @@ export * from './actions.any';
* @returns {Function}
*/
export function connect(id?: string, password?: string) {
return (dispatch: IStore['dispatch']) => dispatch(_connectInternal(id, password))
.catch(error => {
if (error === JitsiConnectionErrors.NOT_LIVE_ERROR) {
navigateRoot(screen.visitorsQueue);
}
});
return (dispatch: IStore['dispatch']) => dispatch(_connectInternal(id, password));
}
/**

View File

@@ -34,11 +34,8 @@ export function connect(id?: string, password?: string) {
return getJaasJWT(state);
}
})
.then(j => {
j && dispatch(setJWT(j));
return dispatch(_connectInternal(id, password));
});
.then(j => j && dispatch(setJWT(j)))
.then(() => dispatch(_connectInternal(id, password)));
}
// used by jibri

View File

@@ -147,13 +147,6 @@ 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,
@@ -161,7 +154,7 @@ function _connectionFailed(
passwordRequired:
error.name === JitsiConnectionErrors.PASSWORD_REQUIRED
? connection : undefined,
preferVisitor
preferVisitor: undefined
});
}

View File

@@ -12,7 +12,7 @@ import logger from './logger';
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
MiddlewareRegistry.register(store => next => async action => {
switch (action.type) {
case I18NEXT_INITIALIZED:
case LANGUAGE_CHANGED:
@@ -23,10 +23,11 @@ MiddlewareRegistry.register(store => next => action => {
: store.getState()['features/dynamic-branding'];
if (language && labels && labels[language]) {
changeLanguageBundle(language, labels[language])
.catch(err => {
try {
await changeLanguageBundle(language, labels[language]);
} catch (err) {
logger.log('Error setting dynamic language bundle', err);
});
}
}
break;
}

View File

@@ -176,12 +176,10 @@ export function validateJwt(jwt: string) {
}
}
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(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)) {

View File

@@ -51,16 +51,6 @@ 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.
*

View File

@@ -9,7 +9,6 @@ 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,
@@ -94,22 +93,6 @@ 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.
*
@@ -139,7 +122,7 @@ export function setScreenshareMuted(
// eslint-disable-next-line no-bitwise
const newValue = muted ? oldValue | authority : oldValue & ~authority;
dispatch({
return dispatch({
type: SET_SCREENSHARE_MUTED,
authority,
ensureTrack,
@@ -197,7 +180,7 @@ export function setVideoMuted(
// eslint-disable-next-line no-bitwise
const newValue = muted ? oldValue | authority : oldValue & ~authority;
dispatch({
return dispatch({
type: SET_VIDEO_MUTED,
authority,
ensureTrack,

View File

@@ -10,7 +10,6 @@ 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,
@@ -88,22 +87,6 @@ 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.
*
@@ -264,11 +247,6 @@ interface IAudioState {
unmuteBlocked: boolean;
}
type initialGUMPromise = Promise<{
errors?: any;
tracks: Array<any>;
}> | null;
interface IScreenshareState {
available: boolean;
muted: number;
@@ -286,7 +264,6 @@ interface IVideoState {
export interface IMediaState {
audio: IAudioState;
initialGUMPromise: initialGUMPromise;
screenshare: IScreenshareState;
video: IVideoState;
}
@@ -303,7 +280,6 @@ export interface IMediaState {
*/
ReducerRegistry.register<IMediaState>('features/base/media', combineReducers({
audio: _audio,
initialGUMPromise: _initialGUMPromise,
screenshare: _screenshare,
video: _video
}));

View File

@@ -1,12 +1,3 @@
/**
* 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.
*

View File

@@ -544,27 +544,23 @@ 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: kickedId,
kicker: kickerId
kicked: kicked.getId(),
kicker: kicker?.getId()
});
if (kicked.isReplaced?.() || kickerId === localParticipant?.id) {
if (kicked.isReplaced?.()) {
return;
}
dispatch(showNotification({
titleArguments: {
kicked:
getParticipantDisplayName(state, kickedId),
getParticipantDisplayName(getState, kicked.getId()),
kicker:
getParticipantDisplayName(state, kickerId)
getParticipantDisplayName(getState, kicker.getId())
},
titleKey: 'notify.kickParticipant'
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));

View File

@@ -8,6 +8,7 @@ import {
isTrackStreamingStatusInactive
} from '../../../connection-indicator/functions';
import SharedVideo from '../../../shared-video/components/native/SharedVideo';
import { isSharedVideoEnabled } from '../../../shared-video/functions';
import { IStateful } from '../../app/types';
import Avatar from '../../avatar/components/Avatar';
import { translate } from '../../i18n/functions';
@@ -236,7 +237,8 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
_isConnectionInactive: isTrackStreamingStatusInactive(videoTrack),
_isSharedVideoParticipant: isSharedVideoParticipant(participant),
_participantName: getParticipantDisplayName(state, participantId),
_renderVideo: shouldRenderParticipantVideo(state, participantId) && !disableVideo,
_renderVideo: shouldRenderParticipantVideo(state, participantId) && !disableVideo
&& isSharedVideoEnabled(state),
_videoTrack: videoTrack
};
}

View File

@@ -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_JOINED, CONFERENCE_WILL_JOIN } from '../conference/actionTypes';
import { CONFERENCE_WILL_JOIN } from '../conference/actionTypes';
import { forEachConference, getCurrentConference } from '../conference/functions';
import { IJitsiConference } from '../conference/reducer';
import { SET_CONFIG } from '../config/actionTypes';
@@ -201,28 +201,6 @@ 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;
@@ -258,8 +236,10 @@ MiddlewareRegistry.register(store => next => action => {
let queue = getRaiseHandsQueue(store.getState());
if (participant.raisedHandTimestamp) {
queue = [ ...queue, { id: participant.id,
raisedHandTimestamp: participant.raisedHandTimestamp } ];
queue.push({
id: participant.id,
raisedHandTimestamp: participant.raisedHandTimestamp
});
// sort the queue before adding to store.
queue = queue.sort(({ raisedHandTimestamp: a }, { raisedHandTimestamp: b }) => a - b);

View File

@@ -6,7 +6,6 @@ import { set } from '../redux/functions';
import {
DOMINANT_SPEAKER_CHANGED,
NOTIFIED_TO_SPEAK,
OVERWRITE_PARTICIPANT_NAME,
PARTICIPANT_ID_CHANGED,
PARTICIPANT_JOINED,
@@ -93,7 +92,7 @@ export interface IParticipantsState {
numberOfParticipantsNotSupportingE2EE: number;
overwrittenNameList: { [id: string]: string; };
pinnedParticipant?: string;
raisedHandsQueue: Array<{ hasBeenNotified?: boolean; id: string; raisedHandTimestamp: number; }>;
raisedHandsQueue: Array<{ id: string; raisedHandTimestamp: number; }>;
remote: Map<string, IParticipant>;
remoteVideoSources: Set<string>;
sortedRemoteParticipants: Map<string, string>;
@@ -115,19 +114,6 @@ 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;

View File

@@ -1,16 +1,11 @@
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, hasBeenNotified, isNextToSpeak } from '../config/functions.any';
import { getSsrcRewritingFeatureFlag } 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,
@@ -30,15 +25,6 @@ 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.
@@ -135,23 +121,3 @@ 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
});
});
}

View File

@@ -25,7 +25,6 @@ export interface IParticipant {
isJigasi?: boolean;
isReplaced?: boolean;
isReplacing?: number;
isSilent?: boolean;
jwtId?: string;
loadableAvatarUrl?: string;
loadableAvatarUrlUseCORS?: boolean;

View File

@@ -1,14 +1,21 @@
import React, { Component, ReactNode } from 'react';
import { toArray } from 'react-emoji-render';
import { connect } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import GifMessage from '../../../../chat/components/web/GifMessage';
import { GIF_PREFIX } from '../../../../gifs/constants';
import { isGifMessage } from '../../../../gifs/functions.web';
import { isGifEnabled, isGifMessage } from '../../../../gifs/functions.web';
import Linkify from './Linkify';
interface IProps {
/**
* Whether the gifs are enabled or not.
*/
gifEnabled: boolean;
/**
* The body of the message.
*/
@@ -43,11 +50,11 @@ class Message extends Component<IProps> {
// Tokenize the text in order to avoid emoji substitution for URLs
const tokens = text ? text.split(' ') : [];
const content = [];
const { gifEnabled } = this.props;
// check if the message is a GIF
if (isGifMessage(text)) {
if (gifEnabled && isGifMessage(text)) {
const url = text.substring(GIF_PREFIX.length, text.length - 1);
content.push(<GifMessage
@@ -93,4 +100,16 @@ class Message extends Component<IProps> {
}
}
export default Message;
/**
* Maps part of the redux state to the props of this component.
*
* @param {IReduxState} state - The Redux state.
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState) {
return {
gifEnabled: isGifEnabled(state)
};
}
export default connect(_mapStateToProps)(Message);

View File

@@ -64,7 +64,7 @@ export function addLocalTrack(newTrack: any) {
const isMuted = newTrack.isMuted();
logger.log(`Adding ${newTrack.getType()} track - ${isMuted ? 'muted' : 'unmuted'}`);
dispatch(setMuted(isMuted));
await dispatch(setMuted(isMuted));
return dispatch(_addTracks([ newTrack ]));
};
@@ -233,11 +233,12 @@ 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)
@@ -247,6 +248,7 @@ export function destroyLocalTracks(track: any = null) {
getState()['features/base/tracks']
.filter(t => t.local)
.map(t => t.jitsiTrack))));
};
}
/**
@@ -272,7 +274,7 @@ export function noDataFromSource(track: any) {
* @returns {Function}
*/
export function showNoDataFromSourceVideoError(jitsiTrack: any) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
let notificationInfo;
const track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
@@ -284,7 +286,7 @@ export function showNoDataFromSourceVideoError(jitsiTrack: any) {
if (track.isReceivingData) {
notificationInfo = undefined;
} else {
const notificationAction = dispatch(showErrorNotification({
const notificationAction = await dispatch(showErrorNotification({
descriptionKey: 'dialog.cameraNotSendingData',
titleKey: 'dialog.cameraNotSendingDataTitle'
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
@@ -357,7 +359,7 @@ function replaceStoredTracks(oldTrack: any, newTrack: any) {
sendAnalytics(createTrackMutedEvent(newTrack.getType(), 'track.replaced', isMuted));
logger.log(`Replace ${newTrack.getType()} track - ${isMuted ? 'muted' : 'unmuted'}`);
dispatch(setMuted(isMuted));
await dispatch(setMuted(isMuted));
await dispatch(_addTracks([ newTrack ]));
}
};
@@ -371,7 +373,7 @@ function replaceStoredTracks(oldTrack: any, newTrack: any) {
* @returns {Function}
*/
export function trackAdded(track: any) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
track.on(
JitsiTrackEvents.TRACK_MUTE_CHANGED,
() => dispatch(trackMutedChanged(track)));
@@ -398,7 +400,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 = dispatch(showNotification({
const notificationAction = await dispatch(showNotification({
descriptionKey: 'dialog.micNotSendingData',
titleKey: 'dialog.micNotSendingDataTitle'
}, NOTIFICATION_TIMEOUT_TYPE.LONG));

View File

@@ -2,4 +2,3 @@
* 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';

View File

@@ -183,7 +183,7 @@ function _getLocalTrack(
* @private
* @returns {void}
*/
function _setMuted(store: IStore, { ensureTrack, muted }: {
async function _setMuted(store: IStore, { ensureTrack, muted }: {
ensureTrack: boolean; muted: boolean; }, mediaType: MediaType) {
const { dispatch, getState } = store;
const localTrack = _getLocalTrack(store, mediaType, /* includePending */ true);

View File

@@ -1,4 +1,4 @@
import Clipboard from '@react-native-clipboard/clipboard';
import Clipboard from '@react-native-community/clipboard';
/**
* Tries to copy a given text to the clipboard.

View File

@@ -18,7 +18,7 @@ export default function BreakoutRoomNamePrompt({ breakoutRoomJid, initialRoomNam
const formattedRoomName = roomName?.trim();
if (formattedRoomName) {
dispatch(renameBreakoutRoom(breakoutRoomJid, formattedRoomName));
dispatch(renameBreakoutRoom(formattedRoomName, roomName));
return true;
}

View File

@@ -6,7 +6,7 @@ import { IReduxState } from '../../../app/types';
import Avatar from '../../../base/avatar/components/Avatar';
import { translate } from '../../../base/i18n/functions';
import Linkify from '../../../base/react/components/native/Linkify';
import { isGifMessage } from '../../../gifs/functions.native';
import { isGifEnabled, isGifMessage } from '../../../gifs/functions.native';
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
import {
getCanReplyToMessage,
@@ -32,7 +32,7 @@ class ChatMessage extends Component<IChatMessageProps> {
* @inheritdoc
*/
render() {
const { message, knocking } = this.props;
const { gifEnabled, message, knocking } = this.props;
const localMessage = message.messageType === MESSAGE_TYPE_LOCAL;
const { privateMessage, lobbyChat } = message;
@@ -82,7 +82,7 @@ class ChatMessage extends Component<IChatMessageProps> {
<View style = { messageBubbleStyle }>
<View style = { styles.textWrapper as ViewStyle } >
{ this._renderDisplayName() }
{ isGifMessage(messageText)
{ gifEnabled && isGifMessage(messageText)
? <GifMessage message = { messageText } />
: (
<Linkify
@@ -210,6 +210,7 @@ class ChatMessage extends Component<IChatMessageProps> {
function _mapStateToProps(state: IReduxState, { message }: IChatMessageProps) {
return {
canReply: getCanReplyToMessage(state, message),
gifEnabled: isGifEnabled(state),
knocking: state['features/lobby'].knocking
};
}

View File

@@ -25,7 +25,7 @@ import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
import { addGif } from '../gifs/actions';
import { GIF_PREFIX } from '../gifs/constants';
import { getGifDisplayMode, isGifMessage } from '../gifs/function.any';
import { getGifDisplayMode, isGifEnabled, isGifMessage } from '../gifs/function.any';
import { showMessageNotification } from '../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
import { resetNbUnreadPollsMessages } from '../polls/actions';
@@ -314,7 +314,7 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
function _onConferenceMessageReceived(store: IStore, { displayName, id, isGuest, message, timestamp, privateMessage }: {
displayName?: string; id: string; isGuest?: boolean;
message: string; privateMessage: boolean; timestamp: number; }) {
const isGif = isGifMessage(message);
const isGif = isGifEnabled(store.getState()) && isGifMessage(message);
if (isGif) {
_handleGifMessageReceived(store, id, message);

View File

@@ -44,6 +44,11 @@ export interface IChatMessageProps extends WithTranslation {
*/
canReply?: boolean;
/**
* Whether gifs are enabled or not.
*/
gifEnabled?: boolean;
/**
* Whether current participant is currently knocking in the lobby room.
*/

View File

@@ -44,7 +44,7 @@ export default {
},
displayNameContainer: {
margin: BaseTheme.spacing[3]
margin: 10
},
/**

View File

@@ -30,8 +30,6 @@ 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 {
@@ -102,11 +100,6 @@ interface IProps extends AbstractProps, WithTranslation {
*/
_showPrejoin: boolean;
/**
* If visitors queue page is visible or not.
*/
_showVisitorsQueue: boolean;
dispatch: IStore['dispatch'];
}
@@ -213,7 +206,6 @@ class Conference extends AbstractConference<IProps, any> {
_overflowDrawer,
_showLobby,
_showPrejoin,
_showVisitorsQueue,
t
} = this.props;
@@ -265,9 +257,8 @@ class Conference extends AbstractConference<IProps, any> {
<CalleeInfoContainer />
{ (_showPrejoin && !_showVisitorsQueue) && <Prejoin />}
{ (_showLobby && !_showVisitorsQueue) && <LobbyScreen />}
{ _showVisitorsQueue && <VisitorsQueue />}
{ _showPrejoin && <Prejoin />}
{ _showLobby && <LobbyScreen />}
</div>
<ParticipantsPane />
<ReactionAnimations />
@@ -411,8 +402,7 @@ function _mapStateToProps(state: IReduxState) {
_overflowDrawer: overflowDrawer,
_roomName: getConferenceNameForTitle(state),
_showLobby: getIsLobbyVisible(state),
_showPrejoin: isPrejoinPageVisible(state),
_showVisitorsQueue: showVisitorsQueue(state)
_showPrejoin: isPrejoinPageVisible(state)
};
}

View File

@@ -9,8 +9,7 @@ import { IReduxState, IStore } from '../app/types';
import {
CONFERENCE_FAILED,
CONFERENCE_JOINED,
CONFERENCE_LEFT,
ENDPOINT_MESSAGE_RECEIVED
CONFERENCE_LEFT
} from '../base/conference/actionTypes';
import { getCurrentConference } from '../base/conference/functions';
import { getURLWithoutParamsNormalized } from '../base/connection/utils';
@@ -20,11 +19,10 @@ 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, raiseHand, raiseHandClear } from '../base/participants/actions';
import { pinParticipant, 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';
@@ -73,15 +71,6 @@ 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;

View File

@@ -29,18 +29,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const etherpadBaseUrl = sanitizeUrl(etherpadBase);
if (etherpadBaseUrl) {
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();
url = new URL(value, etherpadBaseUrl.toString()).toString();
}
dispatch(setDocumentUrl(url));

View File

@@ -21,8 +21,7 @@ import {
import {
getDominantSpeakerParticipant,
getLocalParticipant,
getParticipantById,
getParticipantDisplayName
getParticipantById
} from '../base/participants/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { getBaseUrl } from '../base/util/helpers';
@@ -134,29 +133,15 @@ MiddlewareRegistry.register(store => next => action => {
APP.API.notifyDataChannelOpened();
break;
case KICKED_OUT: {
const state = store.getState();
const localParticipant = getLocalParticipant(state);
if (!localParticipant) {
break;
}
const pId = action.participant.getId();
case KICKED_OUT:
APP.API.notifyKickedOut(
{
id: localParticipant.id,
name: getParticipantDisplayName(state, localParticipant.id),
id: getLocalParticipant(store.getState())?.id,
local: true
},
{
id: pId,
name: getParticipantDisplayName(state, pId)
}
{ id: action.participant ? action.participant.getId() : undefined }
);
break;
}
case NOTIFY_CAMERA_ERROR:
if (action.error) {
@@ -171,28 +156,14 @@ MiddlewareRegistry.register(store => next => action => {
}
break;
case PARTICIPANT_KICKED: {
const state = store.getState();
const kickedParticipant = getParticipantById(state, action.kicked);
const kickerParticipant = getParticipantById(state, action.kicker);
if (!kickerParticipant || !kickedParticipant) {
break;
}
case PARTICIPANT_KICKED:
APP.API.notifyKickedOut(
{
id: kickedParticipant.id,
local: kickedParticipant.local,
name: getParticipantDisplayName(state, kickedParticipant.id)
id: action.kicked,
local: false
},
{
id: kickerParticipant.id,
local: kickerParticipant.local,
name: getParticipantDisplayName(state, kickerParticipant.id)
});
{ id: action.kicker });
break;
}
case PARTICIPANT_LEFT: {
const { participant } = action;

View File

@@ -9,7 +9,7 @@ import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import Input from '../../../base/ui/components/native/Input';
import { sendMessage } from '../../../chat/actions.any';
import { goBack } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { formatGifUrlMessage, getGifRating, getGifUrl, getGiphyProxyUrl } from '../../functions.native';
import { formatGifUrlMessage, getGifRating, getGifUrl } from '../../functions.native';
import GifsMenuFooter from './GifsMenuFooter';
import styles from './styles';
@@ -19,8 +19,6 @@ const GifsMenu = () => {
const dispatch = useDispatch();
const { t } = useTranslation();
const rating = useSelector(getGifRating) as GiphyRating;
const proxyUrl = useSelector(getGiphyProxyUrl);
const options = {
mediaType: GiphyMediaType.Gif,
limit: 20,
@@ -35,7 +33,7 @@ const GifsMenu = () => {
});
const sendGif = useCallback(e => {
const url = getGifUrl(e.nativeEvent.media, proxyUrl);
const url = getGifUrl(e.nativeEvent.media);
sendAnalytics(createGifSentEvent());

View File

@@ -1,4 +1,4 @@
import { GiphyFetch, TrendingOptions, setServerUrl } from '@giphy/js-fetch-api';
import { GiphyFetch, TrendingOptions } from '@giphy/js-fetch-api';
import { Grid } from '@giphy/react-components';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -20,8 +20,7 @@ import {
formatGifUrlMessage,
getGifAPIKey,
getGifRating,
getGifUrl,
getGiphyProxyUrl
getGifUrl
} from '../../function.any';
const OVERFLOW_DRAWER_PADDING = 16;
@@ -104,7 +103,6 @@ function GifsMenu({ columns = 2, parent }: IProps) {
= parent === IReactionsMenuParent.OverflowDrawer || parent === IReactionsMenuParent.OverflowMenu;
const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
const rating = useSelector(getGifRating);
const proxyUrl = useSelector(getGiphyProxyUrl);
const fetchGifs = useCallback(async (offset = 0) => {
const options: TrendingOptions = {
@@ -126,7 +124,7 @@ function GifsMenu({ columns = 2, parent }: IProps) {
const handleGifClick = useCallback((gif, e) => {
e?.stopPropagation();
const url = getGifUrl(gif, proxyUrl);
const url = getGifUrl(gif);
sendAnalytics(createGifSentEvent());
batch(() => {
@@ -189,12 +187,6 @@ function GifsMenu({ columns = 2, parent }: IProps) {
// This fixes that.
useEffect(() => setSearchKey(''), []);
useEffect(() => {
if (proxyUrl) {
setServerUrl(proxyUrl);
}
}, []);
const onInputKeyPress = useCallback((e: React.KeyboardEvent) => {
e.stopPropagation();
}, []);

View File

@@ -33,15 +33,6 @@ export function getGifRating(state: IReduxState) {
return getGifConfig(state).rating || GIF_DEFAULT_RATING;
}
/**
* Get the Giphy proxy url.
*
* @param {IReduxState} state - Redux state.
* @returns {string}
*/
export function getGiphyProxyUrl(state: IReduxState) {
return getGifConfig(state).proxyUrl;
}
/**
* Gets the URL of the GIF for the given participant or null if there's none.
@@ -54,6 +45,26 @@ export function getGifForParticipant(state: IReduxState, participantId: string):
return isGifEnabled(state) ? state['features/gifs'].gifList.get(participantId) || {} : {};
}
/**
* Returns true if a given URL is allowed to be rendered as gif and false otherwise.
*
* @param {string} url - The URL to be validated.
* @returns {boolean} - True if a given URL is allowed to be rendered as gif and false otherwise.
*/
export function isGifUrlAllowed(url: string) {
let hostname: string | undefined;
try {
const urlObject = new URL(url);
hostname = urlObject?.hostname;
} catch (_error) {
return false;
}
return hostname === 'i.giphy.com';
}
/**
* Whether or not the message is a GIF message.
*
@@ -61,26 +72,23 @@ export function getGifForParticipant(state: IReduxState, participantId: string):
* @returns {boolean}
*/
export function isGifMessage(message: string) {
const url = message.substring(GIF_PREFIX.length, message.length - 1);
return message.trim().toLowerCase()
.startsWith(GIF_PREFIX);
.startsWith(GIF_PREFIX) && isGifUrlAllowed(url);
}
/**
* Returns the url of the gif selected in the gifs menu.
*
* @param {Object} gif - The gif data.
* @param {string} proxyUrl - The proxy server url.
* @returns {boolean}
*/
export function getGifUrl(gif?: { data?: { embed_url: string; }; embed_url?: string; }, proxyUrl?: string) {
export function getGifUrl(gif?: { data?: { embed_url: string; }; embed_url?: string; }) {
const embedUrl = gif?.embed_url || gif?.data?.embed_url || '';
const idx = embedUrl.lastIndexOf('/');
const id = embedUrl.substr(idx + 1);
if (proxyUrl) {
return `${proxyUrl}gifs/id/${id}`;
}
return `https://i.giphy.com/media/${id}/giphy.gif`;
}

View File

@@ -13,7 +13,7 @@ import { getVpaasTenant, isVpaasMeeting } from './functions';
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
MiddlewareRegistry.register(store => next => async action => {
switch (action.type) {
case CONFERENCE_JOINED: {
_maybeTrackVpaasConferenceJoin(store.getState());

View File

@@ -16,7 +16,7 @@ import logger from './logger';
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
MiddlewareRegistry.register(store => next => async action => {
switch (action.type) {
case CONFERENCE_JOINED: {
const { conference } = action;

View File

@@ -32,7 +32,7 @@ import { IKnockingParticipant } from './types';
* @returns {Function}
*/
export function joinWithPassword(password: string) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const conference = getCurrentConference(getState);
dispatch(setPassword(conference, conference?.join, password));
@@ -79,7 +79,7 @@ export function participantIsKnockingOrUpdated(participant: IKnockingParticipant
* @returns {Function}
*/
export function answerKnockingParticipant(id: string, approved: boolean) {
return (dispatch: IStore['dispatch']) => {
return async (dispatch: IStore['dispatch']) => {
dispatch(setKnockingParticipantApproval(id, approved));
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
};
@@ -202,7 +202,7 @@ export function setPasswordJoinFailed(failed: boolean) {
* @returns {Function}
*/
export function startKnocking() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { membersOnly } = state['features/base/conference'];
@@ -287,7 +287,7 @@ export function hideLobbyScreen() {
* @returns {Promise<void>}
*/
export function handleLobbyChatInitialized(payload: { attendee: IParticipant; moderator: IParticipant; }) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const conference = getCurrentConference(state);
@@ -323,7 +323,7 @@ export function handleLobbyChatInitialized(payload: { attendee: IParticipant; mo
* @returns {Promise<void>}
*/
export function onSendMessage(message: string) {
return (dispatch: IStore['dispatch']) => {
return async (dispatch: IStore['dispatch']) => {
dispatch(sendMessage(message));
};
}
@@ -349,7 +349,7 @@ export function sendLobbyChatMessage(message: Object) {
* @returns {Function}
*/
export function maybeSetLobbyChatMessageListener() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const lobbyEnabled = getLobbyEnabled(state);
@@ -366,7 +366,7 @@ export function maybeSetLobbyChatMessageListener() {
* @returns {Function}
*/
export function updateLobbyParticipantOnLeave(participantId: string) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { knocking, knockingParticipants } = state['features/lobby'];
const { lobbyMessageRecipient } = state['features/chat'];
@@ -400,7 +400,7 @@ export function updateLobbyParticipantOnLeave(participantId: string) {
* @returns {Function}
*/
export function setLobbyMessageListener() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const conference = getCurrentConference(state);
const { enableChat = true } = getLobbyConfig(state);

View File

@@ -9,7 +9,6 @@ import DialInSummary from '../../../invite/components/dial-in-summary/native/Dia
import Prejoin from '../../../prejoin/components/native/Prejoin';
import UnsafeRoomWarning from '../../../prejoin/components/native/UnsafeRoomWarning';
import { isUnsafeRoomWarningEnabled } from '../../../prejoin/functions';
import VisitorsQueue from '../../../visitors/components/native/VisitorsQueue';
// eslint-disable-next-line
// @ts-ignore
import WelcomePage from '../../../welcome/components/WelcomePage';
@@ -24,7 +23,6 @@ import {
navigationContainerTheme,
preJoinScreenOptions,
unsafeMeetingScreenOptions,
visitorsScreenOptions,
welcomeScreenOptions
} from '../screenOptions';
@@ -107,10 +105,6 @@ const RootNavigationContainer = ({ dispatch, isUnsafeRoomWarningAvailable, isWel
name = { screen.unsafeRoomWarning }
options = { unsafeMeetingScreenOptions } />
}
<RootStack.Screen
component = { VisitorsQueue }
name = { screen.visitorsQueue }
options = { visitorsScreenOptions } />
<RootStack.Screen
component = { ConferenceNavigationContainer }
name = { screen.conference.root }

View File

@@ -44,7 +44,6 @@ export const screen = {
profile: 'Profile'
},
unsafeRoomWarning: 'Unsafe Room Warning',
visitorsQueue: 'Visitors Queue',
welcome: {
main: 'Welcome',
tabs: {

View File

@@ -55,11 +55,6 @@ export const welcomeScreenOptions = {
*/
export const conferenceScreenOptions = fullScreenOptions;
/**
* Screen options for visitors queue.
*/
export const visitorsScreenOptions = fullScreenOptions;
/**
* Tab bar options for chat screen.
*/

View File

@@ -4,8 +4,7 @@ import {
CONFERENCE_FOCUSED,
CONFERENCE_JOINED,
CONFERENCE_LEFT,
CONFERENCE_WILL_JOIN,
ENDPOINT_MESSAGE_RECEIVED
CONFERENCE_WILL_JOIN
} from '../../base/conference/actionTypes';
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../../base/media/actionTypes';
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../../base/participants/actionTypes';
@@ -30,43 +29,34 @@ const externalAPIEnabled = isExternalAPIAvailable();
switch (type) {
case SET_AUDIO_MUTED:
rnSdkHandlers?.onAudioMutedChanged?.(action.muted);
rnSdkHandlers?.onAudioMutedChanged && rnSdkHandlers?.onAudioMutedChanged(action.muted);
break;
case SET_VIDEO_MUTED:
rnSdkHandlers?.onVideoMutedChanged?.(Boolean(action.muted));
rnSdkHandlers?.onVideoMutedChanged && rnSdkHandlers?.onVideoMutedChanged(Boolean(action.muted));
break;
case CONFERENCE_BLURRED:
rnSdkHandlers?.onConferenceBlurred?.();
rnSdkHandlers?.onConferenceBlurred && rnSdkHandlers?.onConferenceBlurred();
break;
case CONFERENCE_FOCUSED:
rnSdkHandlers?.onConferenceFocused?.();
rnSdkHandlers?.onConferenceFocused && rnSdkHandlers?.onConferenceFocused();
break;
case CONFERENCE_JOINED:
rnSdkHandlers?.onConferenceJoined?.();
rnSdkHandlers?.onConferenceJoined && rnSdkHandlers?.onConferenceJoined();
break;
case CONFERENCE_LEFT:
// Props are torn down at this point, perhaps need to leave this one out
break;
case CONFERENCE_WILL_JOIN:
rnSdkHandlers?.onConferenceWillJoin?.();
rnSdkHandlers?.onConferenceWillJoin && rnSdkHandlers?.onConferenceWillJoin();
break;
case ENTER_PICTURE_IN_PICTURE:
rnSdkHandlers?.onEnterPictureInPicture?.();
rnSdkHandlers?.onEnterPictureInPicture && rnSdkHandlers?.onEnterPictureInPicture();
break;
case ENDPOINT_MESSAGE_RECEIVED: {
const { data, participant } = action;
rnSdkHandlers?.onEndpointMessageReceived?.({
data,
participant
});
break;
}
case PARTICIPANT_JOINED: {
const { participant } = action;
const participantInfo = participantToParticipantInfo(participant);
rnSdkHandlers?.onParticipantJoined?.(participantInfo);
rnSdkHandlers?.onParticipantJoined && rnSdkHandlers?.onParticipantJoined(participantInfo);
break;
}
case PARTICIPANT_LEFT: {
@@ -74,11 +64,11 @@ const externalAPIEnabled = isExternalAPIAvailable();
const { id } = participant ?? {};
rnSdkHandlers?.onParticipantLeft?.({ id });
rnSdkHandlers?.onParticipantLeft && rnSdkHandlers?.onParticipantLeft({ id });
break;
}
case READY_TO_CLOSE:
rnSdkHandlers?.onReadyToClose?.();
rnSdkHandlers?.onReadyToClose && rnSdkHandlers?.onReadyToClose();
break;
}

View File

@@ -18,7 +18,7 @@ import DialInLink from './components/DialInLink';
import { NO_AUDIO_SIGNAL_SOUND_ID } from './constants';
import { NO_AUDIO_SIGNAL_SOUND_FILE } from './sounds';
MiddlewareRegistry.register(store => next => action => {
MiddlewareRegistry.register(store => next => async action => {
const result = next(action);
const { dispatch } = store;
@@ -107,7 +107,7 @@ async function _handleNoAudioSignalNotification({ dispatch, getState }: IStore,
} ];
}
const notification = dispatch(showNotification({
const notification = await dispatch(showNotification({
titleKey: 'toolbar.noAudioSignalTitle',
description: <DialInLink />,
descriptionKey,

View File

@@ -36,8 +36,8 @@ MiddlewareRegistry.register(store => next => action => {
}
});
conference.on(
JitsiConferenceEvents.NOISY_MIC, () => {
const notification = dispatch(showNotification({
JitsiConferenceEvents.NOISY_MIC, async () => {
const notification = await dispatch(showNotification({
titleKey: 'toolbar.noisyAudioInputTitle',
descriptionKey: 'toolbar.noisyAudioInputDesc'
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));

View File

@@ -14,9 +14,10 @@ const notification = {
display: 'flex',
flexDirection: 'row',
height: 'auto',
marginVertical: BaseTheme.spacing[1],
maxWidth: 416,
width: '100%'
paddingBottom: BaseTheme.spacing[2],
paddingHorizontal: BaseTheme.spacing[2],
maxWidth: 432,
width: 'auto'
};
/**

View File

@@ -178,7 +178,6 @@ const Notification = ({
description,
descriptionArguments,
descriptionKey,
disableClosing,
hideErrorSupportLink,
icon,
onDismissed,
@@ -337,16 +336,14 @@ const Notification = ({
))}
</div>
</div>
{ !disableClosing && (
<Icon
className = { classes.closeIcon }
color = { theme.palette.icon04 }
id = 'close-notification'
onClick = { onDismiss }
size = { 20 }
src = { IconCloseLarge }
testId = { `${titleKey || descriptionKey}-dismiss` } />
)}
<Icon
className = { classes.closeIcon }
color = { theme.palette.icon04 }
id = 'close-notification'
onClick = { onDismiss }
size = { 20 }
src = { IconCloseLarge }
testId = { `${titleKey || descriptionKey}-dismiss` } />
</div>
</div>
);

View File

@@ -104,19 +104,12 @@ export const RAISE_HAND_NOTIFICATION_ID = 'RAISE_HAND_NOTIFICATION';
export const SALESFORCE_LINK_NOTIFICATION_ID = 'SALESFORCE_LINK_NOTIFICATION';
/**
* The identifier of the visitors promotion notification.
* The identifier of the lobby notification.
*
* @type {string}
*/
export const VISITORS_PROMOTION_NOTIFICATION_ID = 'VISITORS_PROMOTION_NOTIFICATION';
/**
* The identifier of the visitors notification indicating the meeting is not live.
*
* @type {string}
*/
export const VISITORS_NOT_LIVE_NOTIFICATION_ID = 'VISITORS_NOT_LIVE_NOTIFICATION_ID';
/**
* Amount of participants beyond which no join notification will be emitted.
*/

View File

@@ -9,7 +9,6 @@ export interface INotificationProps {
description?: string | React.ReactNode;
descriptionArguments?: Object;
descriptionKey?: string;
disableClosing?: boolean;
hideErrorSupportLink?: boolean;
icon?: string;
maxLines?: number;

View File

@@ -1,5 +1,5 @@
import { IStore } from '../app/types';
import { JitsiConferenceErrors, JitsiConnectionErrors } from '../base/lib-jitsi-meet';
import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
import {
isFatalJitsiConferenceError,
isFatalJitsiConnectionError
@@ -8,6 +8,7 @@ import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { openPageReloadDialog } from './actions';
/**
* Error type. Basically like Error, but augmented with a recoverable property.
*/
@@ -33,7 +34,6 @@ type ErrorType = {
* List of errors that are not fatal (or handled differently) so then the page reload dialog won't kick in.
*/
const RN_NO_RELOAD_DIALOG_ERRORS = [
JitsiConnectionErrors.NOT_LIVE_ERROR,
JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED,
JitsiConferenceErrors.CONFERENCE_DESTROYED,
JitsiConferenceErrors.CONNECTION_ERROR,

View File

@@ -15,16 +15,12 @@ import {
isEnabled as isAvModerationEnabled,
isSupported as isAvModerationSupported
} from '../../../av-moderation/functions';
import { getCurrentConference } from '../../../base/conference/functions';
import { hideSheet, openDialog } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import Icon from '../../../base/icons/components/Icon';
import { IconCheck, IconRaiseHand, IconVideoOff } from '../../../base/icons/svg';
import { IconCheck, IconVideoOff } from '../../../base/icons/svg';
import { MEDIA_TYPE } from '../../../base/media/constants';
import { raiseHand } from '../../../base/participants/actions';
import { getParticipantCount, getRaiseHandsQueue, isEveryoneModerator, isLocalParticipantModerator }
from '../../../base/participants/functions';
import { LOWER_HAND_MESSAGE } from '../../../base/tracks/constants';
import { getParticipantCount, isEveryoneModerator } from '../../../base/participants/functions';
import MuteEveryonesVideoDialog
from '../../../video-menu/components/native/MuteEveryonesVideoDialog';
@@ -36,14 +32,6 @@ export const ContextMenuMore = () => {
dispatch(openDialog(MuteEveryonesVideoDialog));
dispatch(hideSheet());
}, [ dispatch ]);
const conference = useSelector(getCurrentConference);
const raisedHandsQueue = useSelector(getRaiseHandsQueue);
const moderator = useSelector(isLocalParticipantModerator);
const lowerAllHands = useCallback(() => {
dispatch(raiseHand(false));
conference?.sendEndpointMessage('', { name: LOWER_HAND_MESSAGE });
dispatch(hideSheet());
}, [ dispatch ]);
const { t } = useTranslation();
const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
@@ -71,14 +59,6 @@ export const ContextMenuMore = () => {
src = { IconVideoOff } />
<Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.stopEveryonesVideo')}</Text>
</TouchableOpacity>
{ moderator && raisedHandsQueue.length !== 0 && <TouchableOpacity
onPress = { lowerAllHands }
style = { styles.contextMenuItem as ViewStyle }>
<Icon
size = { 24 }
src = { IconRaiseHand } />
<Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.lowerAllHands')}</Text>
</TouchableOpacity> }
{isModerationSupported && ((participantCount === 1 || !allModerators)) && <>
{/* @ts-ignore */}
<Divider style = { styles.divider } />

View File

@@ -3,21 +3,17 @@ import { useTranslation } from 'react-i18next';
import { Text, View, ViewStyle } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import Button from '../../../base/ui/components/native/Button';
import { BUTTON_MODES, BUTTON_TYPES } from '../../../base/ui/constants.native';
import { admitMultiple, goLive } from '../../../visitors/actions';
import {
getPromotionRequests,
getVisitorsCount,
getVisitorsInQueueCount,
isVisitorsLive
} from '../../../visitors/functions';
import { admitMultiple } from '../../../visitors/actions';
import { getPromotionRequests } from '../../../visitors/functions';
import { VisitorsItem } from './VisitorsItem';
import styles from './styles';
const VisitorsList = () => {
const visitorsCount = useSelector(getVisitorsCount);
const visitorsCount = useSelector((state: IReduxState) => state['features/visitors'].count || 0);
const dispatch = useDispatch();
@@ -26,16 +22,9 @@ const VisitorsList = () => {
const admitAll = useCallback(() => {
dispatch(admitMultiple(requests));
}, [ dispatch, requests ]);
const goLiveCb = useCallback(() => {
dispatch(goLive());
}, [ dispatch ]);
const { t } = useTranslation();
const visitorsInQueueCount = useSelector(getVisitorsInQueueCount);
const isLive = useSelector(isVisitorsLive);
const showVisitorsInQueue = visitorsInQueueCount > 0 && isLive === false;
if (visitorsCount <= 0 && !showVisitorsInQueue) {
if (visitorsCount <= 0) {
return null;
}
@@ -45,10 +34,6 @@ const VisitorsList = () => {
title += t('participantsPane.headings.visitorRequests', { count: requests.length });
}
if (showVisitorsInQueue) {
title += t('participantsPane.headings.visitorInQueue', { count: visitorsInQueueCount });
}
return (
<>
<View style = { styles.listDetails as ViewStyle } >
@@ -56,7 +41,7 @@ const VisitorsList = () => {
{ title }
</Text>
{
requests.length > 1 && !showVisitorsInQueue && (
requests.length > 1 && (
<Button
accessibilityLabel = 'participantsPane.actions.admitAll'
labelKey = 'participantsPane.actions.admitAll'
@@ -65,16 +50,6 @@ const VisitorsList = () => {
type = { BUTTON_TYPES.PRIMARY } />
)
}
{
showVisitorsInQueue && (
<Button
accessibilityLabel = 'participantsPane.actions.goLive'
labelKey = 'participantsPane.actions.goLive'
mode = { BUTTON_MODES.TEXT }
onClick = { goLiveCb }
type = { BUTTON_TYPES.PRIMARY } />
)
}
</View>
{
requests.map(r => (

View File

@@ -23,7 +23,6 @@ import {
import { MEDIA_TYPE } from '../../../base/media/constants';
import {
getParticipantCount,
getRaiseHandsQueue,
isEveryoneModerator
} from '../../../base/participants/functions';
import { withPixelLineHeight } from '../../../base/styles/functions.web';
@@ -33,7 +32,6 @@ import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
import { openSettingsDialog } from '../../../settings/actions.web';
import { SETTINGS_TABS } from '../../../settings/constants';
import { shouldShowModeratorSettings } from '../../../settings/functions.web';
import LowerHandButton from '../../../video-menu/components/web/LowerHandButton';
import MuteEveryonesVideoDialog from '../../../video-menu/components/web/MuteEveryonesVideoDialog';
const useStyles = makeStyles()(theme => {
@@ -87,7 +85,6 @@ interface IProps {
export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProps) => {
const dispatch = useDispatch();
const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
const raisedHandsQueue = useSelector(getRaiseHandsQueue);
const allModerators = useSelector(isEveryoneModerator);
const isModeratorSettingsTabEnabled = useSelector(shouldShowModeratorSettings);
const participantCount = useSelector(getParticipantCount);
@@ -150,7 +147,6 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
onClick: muteAllVideo,
text: t('participantsPane.actions.stopEveryonesVideo')
} ] } />
{raisedHandsQueue.length !== 0 && <LowerHandButton />}
{!isBreakoutRoom && isModerationSupported && (participantCount === 1 || !allModerators) && (
<ContextMenuItemGroup actions = { actions }>
<div className = { classes.text }>

View File

@@ -3,14 +3,10 @@ import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../app/types';
import { withPixelLineHeight } from '../../../base/styles/functions.web';
import { admitMultiple, goLive } from '../../../visitors/actions';
import {
getPromotionRequests,
getVisitorsCount,
getVisitorsInQueueCount,
isVisitorsLive
} from '../../../visitors/functions';
import { admitMultiple } from '../../../visitors/actions';
import { getPromotionRequests } from '../../../visitors/functions';
import { VisitorsItem } from './VisitorsItem';
@@ -70,10 +66,7 @@ const useStyles = makeStyles()(theme => {
*/
export default function VisitorsList() {
const requests = useSelector(getPromotionRequests);
const visitorsCount = useSelector(getVisitorsCount);
const visitorsInQueueCount = useSelector(getVisitorsInQueueCount);
const isLive = useSelector(isVisitorsLive);
const showVisitorsInQueue = visitorsInQueueCount > 0 && isLive === false;
const visitorsCount = useSelector((state: IReduxState) => state['features/visitors'].count || 0);
const { t } = useTranslation();
const { classes, cx } = useStyles();
@@ -83,11 +76,7 @@ export default function VisitorsList() {
dispatch(admitMultiple(requests));
}, [ dispatch, requests ]);
const goLiveCb = useCallback(() => {
dispatch(goLive());
}, [ dispatch ]);
if (visitorsCount <= 0 && !showVisitorsInQueue) {
if (visitorsCount <= 0) {
return null;
}
@@ -98,20 +87,12 @@ export default function VisitorsList() {
{ t('participantsPane.headings.visitors', { count: visitorsCount })}
{ requests.length > 0
&& t('participantsPane.headings.visitorRequests', { count: requests.length }) }
{ showVisitorsInQueue
&& t('participantsPane.headings.visitorInQueue', { count: visitorsInQueueCount }) }
</div>
{
requests.length > 1 && !showVisitorsInQueue // Go live button is with higher priority
requests.length > 1
&& <div
className = { classes.link }
onClick = { admitAll }>{ t('participantsPane.actions.admitAll') }</div>
}
{
showVisitorsInQueue
&& <div
className = { classes.link }
onClick = { goLiveCb }>{ t('participantsPane.actions.goLive') }</div>
onClick = { admitAll }>{t('participantsPane.actions.admitAll')}</div>
}
</div>
<div

View File

@@ -63,10 +63,6 @@ export function getParticipantAudioMediaState(participant: IParticipant | undefi
muted: Boolean, state: IReduxState) {
const dominantSpeaker = getDominantSpeakerParticipant(state);
if (participant?.isSilent) {
return MEDIA_STATE.NONE;
}
if (muted) {
if (isForceMuted(participant, MEDIA_TYPE.AUDIO, state)) {
return MEDIA_STATE.FORCE_MUTED;
@@ -150,10 +146,9 @@ export function getQuickActionButtonType(
state: IReduxState) {
// handled only by moderators
const isVideoForceMuted = isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
const isParticipantSilent = participant?.isSilent || false;
if (isLocalParticipantModerator(state)) {
if (!isAudioMuted && !isParticipantSilent) {
if (!isAudioMuted) {
return QUICK_ACTION_BUTTON.MUTE;
}
if (!isVideoMuted) {
@@ -162,7 +157,7 @@ export function getQuickActionButtonType(
if (isVideoForceMuted) {
return QUICK_ACTION_BUTTON.ALLOW_VIDEO;
}
if (isSupported()(state) && !isParticipantSilent) {
if (isSupported()(state)) {
return QUICK_ACTION_BUTTON.ASK_TO_UNMUTE;
}
}

View File

@@ -1,23 +0,0 @@
/**
* The type of the action which signals that we need to remove poll from the history(local storage).
*
* {
* type: REMOVE_POLL_FROM_HISTORY,
* meetingId: string,
* pollId: string,
* poll: IPoll
* }
*/
export const REMOVE_POLL_FROM_HISTORY = 'REMOVE_POLL_FROM_HISTORY';
/**
* The type of the action triggered when the poll is saved in history(local storage).
*
* {
* type: SAVE_POLL_IN_HISTORY,
* poll: Poll,
* pollId: string,
* saved: boolean
* }
*/
export const SAVE_POLL_IN_HISTORY = 'SAVE_POLL_IN_HISTORY';

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