Compare commits

...

34 Commits

Author SHA1 Message Date
damencho
980153e299 fix: Disallow visitor joining directly to main room.
When a vpaas visitor tries to join a room that has not been created and there are no main participants, we deny access.
2024-04-26 12:09:58 -05:00
damencho
d2b4043c7f fix: Skip flip processing for password when there is no room password. 2024-04-26 12:09:51 -05:00
Saúl Ibarra Corretgé
ce727d87a8 fix(ios) Xcode 15.2 updates
- Update project files
- Make sure CI uses Xcode 15.2
- Fix custom Xcode build scripts to depend on the Info.plist file
  generation
- Combine 2 scripts related to Firebase into a single one
2024-04-26 11:51:41 +02:00
Jaya Allamsetty
b540452583 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1816.0.0+f16cadce...v1818.0.0+799236df
2024-04-26 07:25:42 +02:00
Jaya Allamsetty
f995eb2698 fix(tracks): Remove code that handles TRACK_OWNER_CHANGED event. (#14652) 2024-04-25 06:28:45 -04:00
Jaya Allamsetty
393c78aad3 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1813.0.0+09993fc8...v1815.0.0+c4cee681
2024-04-24 17:10:15 -04:00
damencho
6452c998a2 fix: Parse/decode the URL params before constructing the url.
Fixes #14672.

When passing url param config.subject="Example%20Title" we will send config.subject%22%3A%22%2522Example%2520Title%2522%22 which is double quotes.
2024-04-24 15:08:21 -05:00
Jaya Allamsetty
038292305e fix(config): Update the bitrate settings for 1080p and 4K. 2024-04-24 10:52:25 -04:00
Hristo Terezov
aa04692e9b feat(visitors): enableMediaOnPromote option for mobile. 2024-04-23 08:39:05 -05:00
Calinteodor
219e6ce1ca feat(android): list post notifications permission (#14657)
* feat(android): list post notifications permission
2024-04-23 13:16:23 +03:00
Avram Tudor
6b2d586aee fix: allow multiple jids to be specified for sending system messages (#14669) 2024-04-23 12:05:58 +03:00
damencho
28a9850111 fix: Updates jvb user account check.
In certain cases (lib-unbound not found message from lua) we can detect that jvb account is not existing, and we will re-create causing jvb to not able to connect, as password is changed only in prosody.
2024-04-22 17:43:03 -05:00
Edgars Voroboks
d9ef9dc6a0 fix(lang): Update missing Latvian lang translation (#14664) 2024-04-22 09:41:29 +02:00
Hristo Terezov
88b6cdf39b ref(settings): remove changeLocalDisplayName action 2024-04-21 19:42:41 -05:00
Hristo Terezov
e3ab6c9f33 ref(participants): remove unused action. 2024-04-21 19:42:41 -05:00
Hristo Terezov
9bb27b83d9 ref(modules/UI): remove events system.
Many of the events are not used at all or used only on one place. For the rest of them the listeners were added 2 times on promoted visitors and not cleaned at all.
2024-04-21 19:42:41 -05:00
Christoph Settgast
d8b0710a19 ci: move to node 20 based GH actions as current ones are deprecated (#14663) 2024-04-21 22:50:26 +02:00
Roland Meyer
b02b7ac769 lang: update German translation (#14662) 2024-04-21 11:46:08 +02:00
Jaya Allamsetty
b90e187a73 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1812.0.0+2eddb859...v1813.0.0+09993fc8
2024-04-19 11:19:01 -04:00
kerem
582bbf890b fix descriptions
fix descriptions
2024-04-19 15:27:31 +03:00
Ilayda Dastan
ce840cc7b6 fix(chat) improve new message visibility for local participant
Closes: https://github.com/jitsi/jitsi-meet/issues/14561
2024-04-19 09:58:40 +02:00
Saúl Ibarra Corretgé
cc03949b90 chore(deps) react-native-webrtc@118.0.7 2024-04-18 16:56:08 +02:00
Calin-Teodor
158e1a56e5 feat(react-native-sdk): update readme file 2024-04-18 17:55:54 +03:00
Calin-Teodor
16597a2535 feat(android): list media projection permission in manifest 2024-04-18 17:14:25 +03:00
Saúl Ibarra Corretgé
2f3cf9f530 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1811.0.0+86e2fb2b...v1812.0.0+2eddb859
2024-04-18 15:11:59 +02:00
Дамян Минков
347cc32ecc chore(deps) lib-jitsi-meet@latest (#14642) 2024-04-16 19:53:23 -04:00
Calin-Teodor
c0602abbca feat(base/config): whitelist customToolbarButtons 2024-04-16 17:39:20 +03:00
Calin-Teodor
55e9136b91 feat(toolbox/native): fixed icon for CustomOptionButton and styles 2024-04-16 16:21:12 +03:00
damencho
da01ca23db fix(visitors): Fixes promote all. 2024-04-16 06:20:06 -05:00
Hristo Terezov
b470c201b2 fix(conference): use up to date state in useVideoStream. 2024-04-12 16:08:21 -05:00
Mihaela Dumitru
e3ee99754c fix(debug) enable webview debugging (#14623) 2024-04-12 16:32:45 +03:00
Mihaela Dumitru
b3e1865fd8 fix(whiteboard/native) disable Android local storage 2024-04-12 12:28:50 +02:00
Saúl Ibarra Corretgé
a4e3716632 chore(rn,deps) react-native-webrtc@118.0.6
Fixes duplicated audio on iOS.
2024-04-11 23:17:20 +02:00
Hristo Terezov
5ed4b470e7 feat(visitors-config): Enable media on promotion. 2024-04-11 15:48:23 -05:00
52 changed files with 529 additions and 565 deletions

View File

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

View File

@@ -7,8 +7,8 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
cache: 'npm'
@@ -34,8 +34,8 @@ jobs:
name: Build Frontend (Linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
cache: 'npm'
@@ -45,8 +45,8 @@ jobs:
name: Build Frontend (macOS)
runs-on: macOS-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
cache: 'npm'
@@ -56,8 +56,8 @@ jobs:
name: Build mobile bundle (Android)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
cache: 'npm'
@@ -65,14 +65,19 @@ jobs:
- run: npx react-native bundle --entry-file react/index.native.js --platform android --bundle-output /tmp/android.bundle --reset-cache
ios-build:
name: Build mobile bundle (iOS)
runs-on: macOS-latest
runs-on: macOS-13
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
cache: 'npm'
- run: npm install
- name: setup Xcode
run: |
xcode-select -p
sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
xcodebuild -version
- name: setup-cocoapods
uses: maxim-lobanov/setup-cocoapods@v1
with:
@@ -89,8 +94,8 @@ jobs:
name: Test Debian packages build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
cache: 'npm'

View File

@@ -13,6 +13,8 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature
android:glEsVersion="0x00020000"

View File

@@ -16,6 +16,8 @@
package org.jitsi.meet.sdk;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
@@ -24,6 +26,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.Bundle;
@@ -31,6 +34,8 @@ import android.os.IBinder;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.facebook.react.modules.core.PermissionListener;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.HashMap;
@@ -52,12 +57,13 @@ public class JitsiMeetOngoingConferenceService extends Service
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver();
private static final int POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE);
private boolean isAudioMuted;
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
public static void launch(Context context, HashMap<String, Object> extraData) {
private static void doLaunch(Context context, HashMap<String, Object> extraData) {
OngoingNotification.createNotificationChannel((Activity) context);
@@ -87,6 +93,31 @@ public class JitsiMeetOngoingConferenceService extends Service
}
}
public static void launch(Context context, HashMap<String, Object> extraData) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
PermissionListener listener = new PermissionListener() {
@Override
public boolean onRequestPermissionsResult(int i, String[] strings, int[] results) {
if (results.length > 0 && results[0] == PackageManager.PERMISSION_GRANTED) {
doLaunch(context, extraData);
}
return true;
}
};
JitsiMeetActivityDelegate.requestPermissions(
(Activity) context,
new String[]{POST_NOTIFICATIONS},
POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE,
listener
);
} else {
doLaunch(context, extraData);
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
context.stopService(intent);

View File

@@ -2,7 +2,6 @@
import { jitsiLocalStorage } from '@jitsi/js-utils';
import Logger from '@jitsi/logger';
import EventEmitter from 'events';
import { ENDPOINT_TEXT_MESSAGE_NAME } from './modules/API/constants';
import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from './modules/UI/UIErrors';
@@ -165,12 +164,9 @@ import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/Au
import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
import { muteLocal } from './react/features/video-menu/actions.any';
import UIEvents from './service/UI/UIEvents';
const logger = Logger.getLogger(__filename);
const eventEmitter = new EventEmitter();
let room;
/*
@@ -1337,12 +1333,11 @@ export default {
* @returns {Promise}
*/
useVideoStream(newTrack) {
const state = APP.store.getState();
logger.debug(`useVideoStream: ${newTrack}`);
return new Promise((resolve, reject) => {
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
const state = APP.store.getState();
const oldTrack = getLocalJitsiVideoTrack(state);
logger.debug(`useVideoStream: Replacing ${oldTrack} with ${newTrack}`);
@@ -1917,21 +1912,12 @@ export default {
JitsiE2ePingEvents.E2E_RTT_CHANGED,
(...args) => APP.store.dispatch(e2eRttChanged(...args)));
APP.UI.addListener(UIEvents.AUDIO_MUTED, muted => {
this.muteAudio(muted);
});
APP.UI.addListener(UIEvents.VIDEO_MUTED, (muted, showUI = false) => {
this.muteVideo(muted, showUI);
});
room.addCommandListener(this.commands.defaults.ETHERPAD,
({ value }) => {
APP.UI.initEtherpad(value);
}
);
APP.UI.addListener(UIEvents.EMAIL_CHANGED,
this.changeLocalEmail.bind(this));
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
APP.store.dispatch(participantUpdated({
conference: room,
@@ -1951,9 +1937,6 @@ export default {
}));
});
APP.UI.addListener(UIEvents.NICKNAME_CHANGED,
this.changeLocalDisplayName.bind(this));
room.on(
JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
({ audio, video }) => {
@@ -2008,115 +1991,119 @@ export default {
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}
);
},
// call hangup
APP.UI.addListener(UIEvents.HANGUP, () => {
this.hangup(true);
/**
* Handles audio device changes.
*
* @param {string} cameraDeviceId - The new device id.
* @returns {Promise}
*/
async onAudioDeviceChanged(micDeviceId) {
const audioWasMuted = this.isLocalAudioMuted();
// Disable noise suppression if it was enabled on the previous track.
await APP.store.dispatch(setNoiseSuppressionEnabled(false));
// When the 'default' mic needs to be selected, we need to pass the real device id to gUM instead of
// 'default' in order to get the correct MediaStreamTrack from chrome because of the following bug.
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689.
const isDefaultMicSelected = micDeviceId === 'default';
const selectedDeviceId = isDefaultMicSelected
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: micDeviceId;
logger.info(`Switching audio input device to ${selectedDeviceId}`);
sendAnalytics(createDeviceChangedEvent('audio', 'input'));
createLocalTracksF({
devices: [ 'audio' ],
micDeviceId: selectedDeviceId
})
.then(([ stream ]) => {
// if audio was muted before changing the device, mute
// with the new device
if (audioWasMuted) {
return stream.mute()
.then(() => stream);
}
return stream;
})
.then(async stream => {
await this._maybeApplyAudioMixerEffect(stream);
return this.useAudioStream(stream);
})
.then(() => {
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
if (localAudio && isDefaultMicSelected) {
// workaround for the default device to be shown as selected in the
// settings even when the real device id was passed to gUM because of the
// above mentioned chrome bug.
localAudio._realDeviceId = localAudio.deviceId = 'default';
}
})
.catch(err => {
logger.error(`Failed to switch to selected audio input device ${selectedDeviceId}, error=${err}`);
APP.store.dispatch(notifyMicError(err));
});
},
APP.UI.addListener(
UIEvents.VIDEO_DEVICE_CHANGED,
cameraDeviceId => {
const videoWasMuted = this.isLocalVideoMuted();
const localVideoTrack = getLocalJitsiVideoTrack(APP.store.getState());
/**
* Handles video device changes.
*
* @param {string} cameraDeviceId - The new device id.
* @returns {void}
*/
onVideoDeviceChanged(cameraDeviceId) {
const videoWasMuted = this.isLocalVideoMuted();
const localVideoTrack = getLocalJitsiVideoTrack(APP.store.getState());
if (localVideoTrack?.getDeviceId() === cameraDeviceId) {
return;
}
if (localVideoTrack?.getDeviceId() === cameraDeviceId) {
return;
}
sendAnalytics(createDeviceChangedEvent('video', 'input'));
sendAnalytics(createDeviceChangedEvent('video', 'input'));
createLocalTracksF({
devices: [ 'video' ],
cameraDeviceId
})
.then(([ stream ]) => {
// if we are in audio only mode or video was muted before
// changing device, then mute
if (this.isAudioOnly() || videoWasMuted) {
return stream.mute()
.then(() => stream);
}
return stream;
})
.then(stream => {
logger.info(`Switching the local video device to ${cameraDeviceId}.`);
return this.useVideoStream(stream);
})
.catch(error => {
logger.error(`Failed to switch to selected camera:${cameraDeviceId}, error:${error}`);
return APP.store.dispatch(notifyCameraError(error));
});
createLocalTracksF({
devices: [ 'video' ],
cameraDeviceId
})
.then(([ stream ]) => {
// if we are in audio only mode or video was muted before
// changing device, then mute
if (this.isAudioOnly() || videoWasMuted) {
return stream.mute()
.then(() => stream);
}
);
APP.UI.addListener(
UIEvents.AUDIO_DEVICE_CHANGED,
async micDeviceId => {
const audioWasMuted = this.isLocalAudioMuted();
return stream;
})
.then(stream => {
logger.info(`Switching the local video device to ${cameraDeviceId}.`);
// Disable noise suppression if it was enabled on the previous track.
await APP.store.dispatch(setNoiseSuppressionEnabled(false));
return this.useVideoStream(stream);
})
.catch(error => {
logger.error(`Failed to switch to selected camera:${cameraDeviceId}, error:${error}`);
// When the 'default' mic needs to be selected, we need to pass the real device id to gUM instead of
// 'default' in order to get the correct MediaStreamTrack from chrome because of the following bug.
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689.
const isDefaultMicSelected = micDeviceId === 'default';
const selectedDeviceId = isDefaultMicSelected
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: micDeviceId;
logger.info(`Switching audio input device to ${selectedDeviceId}`);
sendAnalytics(createDeviceChangedEvent('audio', 'input'));
createLocalTracksF({
devices: [ 'audio' ],
micDeviceId: selectedDeviceId
})
.then(([ stream ]) => {
// if audio was muted before changing the device, mute
// with the new device
if (audioWasMuted) {
return stream.mute()
.then(() => stream);
}
return stream;
})
.then(async stream => {
await this._maybeApplyAudioMixerEffect(stream);
return this.useAudioStream(stream);
})
.then(() => {
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
if (localAudio && isDefaultMicSelected) {
// workaround for the default device to be shown as selected in the
// settings even when the real device id was passed to gUM because of the
// above mentioned chrome bug.
localAudio._realDeviceId = localAudio.deviceId = 'default';
}
})
.catch(err => {
logger.error(`Failed to switch to selected audio input device ${selectedDeviceId}, error=${err}`);
APP.store.dispatch(notifyMicError(err));
});
}
);
APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, () => {
// Immediately update the UI by having remote videos and the large video update themselves.
const displayedUserId = APP.UI.getLargeVideoID();
if (displayedUserId) {
APP.UI.updateLargeVideo(displayedUserId, true);
}
return APP.store.dispatch(notifyCameraError(error));
});
},
/**
* Handles audio only changes.
*/
onToggleAudioOnly() {
// Immediately update the UI by having remote videos and the large video update themselves.
const displayedUserId = APP.UI.getLargeVideoID();
if (displayedUserId) {
APP.UI.updateLargeVideo(displayedUserId, true);
}
},
/**
* Cleanups local conference on suspend.
*/
@@ -2409,8 +2396,6 @@ export default {
this.deviceChangeListener);
}
APP.UI.removeAllListeners();
let feedbackResultPromise = Promise.resolve({});
if (requestFeedback) {
@@ -2517,37 +2502,6 @@ export default {
room.sendEndpointMessage(to, payload);
},
/**
* Adds new listener.
* @param {String} eventName the name of the event
* @param {Function} listener the listener.
*/
addListener(eventName, listener) {
eventEmitter.addListener(eventName, listener);
},
/**
* Removes listener.
* @param {String} eventName the name of the event that triggers the
* listener
* @param {Function} listener the listener.
*/
removeListener(eventName, listener) {
eventEmitter.removeListener(eventName, listener);
},
/**
* Changes the display name for the local user
* @param nickname {string} the new display name
*/
changeLocalDisplayName(nickname = '') {
const formattedNickname = getNormalizedDisplayName(nickname);
APP.store.dispatch(updateSettings({
displayName: formattedNickname
}));
},
/**
* Callback invoked by the external api create or update a direct connection
* from the local client to an external client.

View File

@@ -467,6 +467,8 @@ var config = {
// low: 100000,
// standard: 300000,
// high: 1000000,
// fullHd: 2000000,
// ultraHd: 4000000,
// ssHigh: 2500000
// },
// scalabilityModeEnabled: true,
@@ -478,6 +480,8 @@ var config = {
// low: 200000,
// standard: 500000,
// high: 1500000,
// fullHd: 3000000,
// ultraHd: 6000000,
// ssHigh: 2500000
// },
// scalabilityModeEnabled: true
@@ -487,6 +491,8 @@ var config = {
// low: 200000,
// standard: 500000,
// high: 1500000,
// fullHd: 3000000,
// ultraHd: 6000000,
// ssHigh: 2500000
// },
// scalabilityModeEnabled: false
@@ -496,6 +502,8 @@ var config = {
// low: 100000,
// standard: 300000,
// high: 1200000,
// fullHd: 2500000,
// ultraHd: 5000000,
// ssHigh: 2500000
// },
// scalabilityModeEnabled: true,

View File

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

View File

@@ -453,7 +453,7 @@ PODS:
- react-native-video/Video (6.0.0-alpha.11):
- PromisesSwift
- React-Core
- react-native-webrtc (118.0.5):
- react-native-webrtc (118.0.7):
- JitsiWebRTC (~> 118.0.0)
- React-Core
- react-native-webview (13.5.1):
@@ -881,7 +881,7 @@ SPEC CHECKSUMS:
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
react-native-video: 472b7c366eaaaa0207e546d9a50410df89790bcf
react-native-webrtc: 88fb4b2994b06b4d6ad67aee7f8ce7e7aa7012bd
react-native-webrtc: 8b024c7bb9a005d2b9efeba4c691172dbd00268d
react-native-webview: 8baa0f5c6d336d6ba488e942bcadea5bf51f050a
React-NativeModulesApple: 4225ac31a26696c02c54b471052b3e85e74a9a0c
React-perflogger: cb433f318c6667060fc1f62e26eb58d6eb30a627

View File

@@ -367,13 +367,12 @@
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
0B26BE701EC5BC3C00EEFB41 /* Embed Frameworks */,
0BB7DA181EC9E695007AAE98 /* Adjust ATS */,
DEF4813D224925A2002AD03A /* Copy Google Plist file */,
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
DE4F6D6E22005C0400DE699E /* Setup Dropbox */,
4E81688528A408E600F8FA9E /* Update App Entitlements */,
0BEA5C491F7B8F73000D0AB4 /* Embed Watch Content */,
4EC49B9025BED71300E76218 /* Embed App Extensions */,
4E81688528A408E600F8FA9E /* Update App Entitlements */,
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
0BB7DA181EC9E695007AAE98 /* Adjust ATS */,
DE4F6D6E22005C0400DE699E /* Setup Dropbox */,
);
buildRules = (
);
@@ -499,10 +498,12 @@
/* Begin PBXShellScriptBuildPhase section */
0BB7DA181EC9E695007AAE98 /* Adjust ATS */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH",
);
name = "Adjust ATS";
outputPaths = (
@@ -513,6 +514,7 @@
};
0BBA83C41EC9F7600075A103 /* Run React packager */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -527,12 +529,14 @@
};
4E81688528A408E600F8FA9E /* Update App Entitlements */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$PROJECT_DIR/app.entitlements",
);
name = "Update App Entitlements";
outputFileListPaths = (
@@ -567,12 +571,14 @@
};
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH",
);
name = "Setup Google reverse URL handler";
outputFileListPaths = (
@@ -581,16 +587,18 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "INFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nGOOGLE_PLIST=\"$PROJECT_DIR/GoogleService-Info.plist\"\n\nif [[ -f $GOOGLE_PLIST ]]; then\n REVERSED_CLIENT_ID=$(/usr/libexec/PlistBuddy -c \"Print :REVERSED_CLIENT_ID:\" $GOOGLE_PLIST)\n /usr/libexec/PlistBuddy -c \"Set :CFBundleURLTypes:1:CFBundleURLSchemes:0 $REVERSED_CLIENT_ID\" $INFO_PLIST\nfi\n";
shellScript = "GOOGLE_PLIST_NAME=\"GoogleService-Info.plist\"\nGOOGLE_PLIST=\"$PROJECT_DIR/$GOOGLE_PLIST_NAME\"\nBUILD_APP_DIR=\"$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.app\"\nINFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\n\nif [[ -f $GOOGLE_PLIST ]]; then\n cp $GOOGLE_PLIST \"$BUILD_APP_DIR/$GOOGLE_PLIST_NAME\"\n REVERSED_CLIENT_ID=$(/usr/libexec/PlistBuddy -c \"Print :REVERSED_CLIENT_ID:\" $GOOGLE_PLIST)\n /usr/libexec/PlistBuddy -c \"Set :CFBundleURLTypes:1:CFBundleURLSchemes:0 $REVERSED_CLIENT_ID\" $INFO_PLIST\nfi\n";
};
DE4F6D6E22005C0400DE699E /* Setup Dropbox */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH",
);
name = "Setup Dropbox";
outputFileListPaths = (
@@ -601,24 +609,6 @@
shellPath = /bin/sh;
shellScript = "INFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nDROPBOX_KEY_FILE=\"$PROJECT_DIR/dropbox.key\"\n\nif [[ -f $DROPBOX_KEY_FILE ]]; then\n /usr/libexec/PlistBuddy -c \"Delete :LSApplicationQueriesSchemes\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes array\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes:0 string 'dbapi-2'\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes:1 string 'dbapi-8-emm'\" $INFO_PLIST\n\n DROPBOX_KEY=$(head -n 1 $DROPBOX_KEY_FILE)\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLName string dropbox\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLSchemes array\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLSchemes:0 string $DROPBOX_KEY\" $INFO_PLIST\nfi\n";
};
DEF4813D224925A2002AD03A /* Copy Google Plist file */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Copy Google Plist file";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "GOOGLE_PLIST_NAME=\"GoogleService-Info.plist\"\nGOOGLE_PLIST=\"$PROJECT_DIR/$GOOGLE_PLIST_NAME\"\nBUILD_APP_DIR=\"$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.app\"\n\nif [[ -f $GOOGLE_PLIST ]]; then\n cp $GOOGLE_PLIST \"$BUILD_APP_DIR/$GOOGLE_PLIST_NAME\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -1031,7 +1021,8 @@
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
@@ -1094,7 +1085,8 @@
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;

View File

@@ -781,7 +781,8 @@
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
@@ -847,7 +848,8 @@
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;

View File

@@ -128,6 +128,7 @@
"privateNotice": "Private Nachricht an {{recipient}}",
"sendButton": "Senden",
"smileysPanel": "Emoji-Auswahl",
"systemDisplayName": "System",
"tabs": {
"chat": "Chatten",
"polls": "Umfragen"
@@ -305,6 +306,8 @@
"contactSupport": "Support kontaktieren",
"copied": "Kopiert",
"copy": "Kopieren",
"demoteParticipantDialog": "Sind Sie sicher, dass Sie diese Person zu den Gästen verschieben möchten?",
"demoteParticipantTitle": "Zu Gästen verschieben",
"dismiss": "OK",
"displayNameRequired": "Hallo! Wie ist Ihr Name?",
"done": "Fertig",
@@ -560,6 +563,7 @@
"noNumbers": "Keine Telefonnummern verfügbar.",
"noPassword": "Kein Passwort benötigt",
"noRoom": "Keine Konferenz für die Einwahlinformationen angegeben.",
"noWhiteboard": "Whiteboard konnte nicht geladen werden.",
"numbers": "Einwahlnummern",
"password": "$t(lockRoomPasswordUppercase):",
"reachedLimit": "Sie haben die Grenzen Ihres Tarifs erreicht.",
@@ -567,7 +571,8 @@
"sipAudioOnly": "SIP-Adresse (nur Ton)",
"title": "Teilen",
"tooltip": "Freigabe-Link und Einwahlinformationen für dieses Meeting",
"upgradeOptions": "Bitte prüfen Sie Ihre Upgrade-Optionen auf"
"upgradeOptions": "Bitte prüfen Sie Ihre Upgrade-Optionen auf",
"whiteboardError": "Whiteboard konnte nicht geladen werden. Bitte versuchen Sie es später erneut."
},
"inlineDialogFailure": {
"msg": "Es ist ein Fehler aufgetreten.",
@@ -802,12 +807,16 @@
"startSilentTitle": "Sie sind ohne Audioausgabe beigetreten!",
"suboptimalBrowserWarning": "Tut uns leid, aber die Konferenz wird mit {{appName}} kein großartiges Erlebnis. Wir versuchen immer die Situation zu verbessern, bis dahin empfehlen wir aber die Verwendung einer der <a href=\"{{recommendedBrowserPageLink}}\" target=\"_blank\">vollständig unterstützen Browser</a>.",
"suboptimalExperienceTitle": "Browserwarnung",
"suggestRecordingAction": "Starten",
"suggestRecordingDescription": "Möchten Sie eine Aufzeichnung starten?",
"suggestRecordingTitle": "Konferenz aufzeichnen",
"unmute": "Stummschaltung aufheben",
"videoMutedRemotelyDescription": "Sie können sie jederzeit wieder einschalten.",
"videoMutedRemotelyTitle": "Ihre Kamera wurde von {{participantDisplayName}} ausgeschaltet!",
"videoUnmuteBlockedDescription": "Die Kamera und Bildschirmfreigabe kann aus Überlastungsschutzgründen temporär nicht eingeschaltet werden.",
"videoUnmuteBlockedTitle": "Kamera und Bildschirmfreigabe kann nicht aktiviert werden!",
"viewLobby": "Lobby ansehen",
"viewVisitors": "Gäste anzeigen",
"waitingParticipants": "{{waitingParticipants}} Personen",
"whiteboardLimitDescription": "Bitte speichern Sie Ihre Inhalte, da das Nutzungslimit bald erreicht wird und dann Ihr Whiteboard geschlossen wird.",
"whiteboardLimitTitle": "Whiteboard-Nutzung"
@@ -937,6 +946,7 @@
"or": "oder",
"premeeting": "Vorschau",
"proceedAnyway": "Trotzdem fortsetzen",
"recordingWarning": "Diese Konferenz wird möglicherweise von anderen Personen aufgezeichnet",
"screenSharingError": "Fehler bei Bildschirmfreigabe:",
"showScreen": "Konferenzvorschau aktivieren",
"startWithPhone": "Mit Telefonaudio starten",
@@ -1003,7 +1013,6 @@
"limitNotificationDescriptionNative": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} Min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <3>{{app}}</3>.",
"limitNotificationDescriptionWeb": "Wegen hoher Nachfrage ist Ihre Aufnahme auf {{limit}} Min. begrenzt. Für unlimitierte Aufnahmen nutzen Sie bitte <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"linkGenerated": "Link zur Aufzeichnung wurde generiert.",
"live": "LIVE",
"localRecordingNoNotificationWarning": "Die Aufzeichnung wird anderen Anwesenden nicht mitgeteilt. Sie müssen diese selbst darauf hinweisen, dass die Konferenz aufgezeichnet wird.",
"localRecordingNoVideo": "Videos werden nicht aufgenommen",
"localRecordingStartWarning": "Bitte beenden Sie die Aufzeichnung vor dem Verlassen der Konferenz, um die Aufzeichnung zu speichern.",
@@ -1020,7 +1029,6 @@
"onBy": "{{name}} startete die Aufnahme",
"onlyRecordSelf": "Nur eigenes Kamerabild und Ton aufzeichnen",
"pending": "Aufzeichnung des Meetings wird vorbereitet…",
"rec": "AUFZ",
"recordAudioAndVideo": "Kamera und Ton aufzeichnen",
"recordTranscription": "Transkription aufzeichnen",
"saveLocalRecording": "Aufzeichnung lokal abspeichern",
@@ -1177,7 +1185,7 @@
"audioOnly": "„Nur Audio“ ein-/ausschalten",
"audioRoute": "Audiogerät auswählen",
"boo": "Buhen",
"breakoutRoom": "Breakout-Räume betreten/verlassen",
"breakoutRooms": "Breakout-Räume",
"callQuality": "Qualitätseinstellungen",
"carmode": "Automodus",
"cc": "Untertitel ein-/ausschalten",
@@ -1360,13 +1368,9 @@
},
"transcribing": {
"ccButtonTooltip": "Untertitel ein-/ausschalten",
"error": "Die Aufzeichnung ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
"expandedLabel": "Transkribieren ist derzeit eingeschaltet",
"failedToStart": "Transkribieren konnte nicht gestartet werden",
"labelToolTip": "Das Meeting wird transkribiert",
"off": "Transkribieren gestoppt",
"on": "Transkribieren gestartet",
"pending": "Transkribieren des Meetings wird vorbereitet…",
"sourceLanguageDesc": "Aktuell ist die Sprache der Konferenz auf <b>{{sourceLanguage}}</b> eingestellt. <br/> Sie könne dies hier ",
"sourceLanguageHere": "ändern",
"start": "Anzeige der Untertitel starten",
@@ -1422,6 +1426,7 @@
},
"videothumbnail": {
"connectionInfo": "Verbindungsinformationen",
"demote": "Zu Gästen verschieben",
"domute": "Stummschalten",
"domuteOthers": "Alle anderen stummschalten",
"domuteVideo": "Kamera ausschalten",
@@ -1476,6 +1481,7 @@
"chatIndicator": "(Gast)",
"labelTooltip": "Anzahl Gäste: {{count}}",
"notification": {
"demoteDescription": "Hierhin verschoben von {{actor}}, bitte melden Sie sich um teilzunehmen",
"description": "Bitte melden Sie sich um teilzunehmen",
"title": "Sie sind Gast in der Konferenz"
}
@@ -1536,6 +1542,7 @@
"whiteboard": {
"accessibilityLabel": {
"heading": "Whiteboard"
}
},
"screenTitle": "Whiteboard"
}
}

View File

@@ -128,6 +128,7 @@
"privateNotice": "Privāta ziņa adresātam {{recipient}}",
"sendButton": "Nosūtīt",
"smileysPanel": "Emociju panelis",
"systemDisplayName": "Sistēma",
"tabs": {
"chat": "Tērzēšana",
"polls": "Aptaujas"

View File

@@ -41,6 +41,7 @@ import {
import { LOCAL_PARTICIPANT_DEFAULT_ID } from '../../react/features/base/participants/constants';
import {
getLocalParticipant,
getNormalizedDisplayName,
getParticipantById,
getScreenshareParticipantIds,
getVirtualScreenshareParticipantByOwnerId,
@@ -76,6 +77,7 @@ import { setMediaEncryptionKey, toggleE2EE } from '../../react/features/e2ee/act
import {
addStageParticipant,
resizeFilmStrip,
setFilmstripVisible,
setVolume,
togglePinStageParticipant
} from '../../react/features/filmstrip/actions.web';
@@ -199,7 +201,7 @@ function initCommands() {
},
'display-name': displayName => {
sendAnalytics(createApiEvent('display.name.changed'));
APP.conference.changeLocalDisplayName(displayName);
APP.store.dispatch(updateSettings({ displayName: getNormalizedDisplayName(displayName) }));
},
'local-subject': localSubject => {
sendAnalytics(createApiEvent('local.subject.changed'));
@@ -376,7 +378,9 @@ function initCommands() {
},
'toggle-film-strip': () => {
sendAnalytics(createApiEvent('film.strip.toggled'));
APP.UI.toggleFilmstrip();
const { visible } = APP.store.getState()['features/filmstrip'];
APP.store.dispatch(setFilmstripVisible(!visible));
},
/*

View File

@@ -4,7 +4,6 @@
const UI = {};
import Logger from '@jitsi/logger';
import EventEmitter from 'events';
import {
conferenceWillInit
@@ -13,7 +12,6 @@ import { isMobileBrowser } from '../../react/features/base/environment/utils';
import { setColorAlpha } from '../../react/features/base/util/helpers';
import { sanitizeUrl } from '../../react/features/base/util/uri';
import { setDocumentUrl } from '../../react/features/etherpad/actions';
import { setFilmstripVisible } from '../../react/features/filmstrip/actions.any';
import {
setNotificationsEnabled,
showNotification
@@ -25,7 +23,6 @@ import {
setToolboxEnabled,
showToolbox
} from '../../react/features/toolbox/actions.web';
import UIEvents from '../../service/UI/UIEvents';
import EtherpadManager from './etherpad/Etherpad';
import UIUtil from './util/UIUtil';
@@ -33,22 +30,8 @@ import VideoLayout from './videolayout/VideoLayout';
const logger = Logger.getLogger(__filename);
const eventEmitter = new EventEmitter();
UI.eventEmitter = eventEmitter;
let etherpadManager;
const UIListeners = new Map([
[
UIEvents.ETHERPAD_CLICKED,
() => etherpadManager && etherpadManager.toggleEtherpad()
], [
UIEvents.TOGGLE_FILMSTRIP,
() => UI.toggleFilmstrip()
]
]);
/**
* Indicates if we're currently in full screen mode.
*
@@ -96,10 +79,11 @@ UI.start = function() {
};
/**
* Setup some UI event listeners.
* Handles etherpad click.
*/
UI.registerListeners
= () => UIListeners.forEach((value, key) => UI.addListener(key, value));
UI.onEtherpadClicked = function() {
etherpadManager && etherpadManager.toggleEtherpad();
};
/**
*
@@ -143,7 +127,7 @@ UI.initEtherpad = name => {
}
logger.log('Etherpad is enabled');
etherpadManager = new EtherpadManager(eventEmitter);
etherpadManager = new EtherpadManager();
const url = new URL(name, etherpadBaseUrl);
@@ -197,15 +181,6 @@ UI.updateUserStatus = (user, status) => {
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
};
/**
* Toggles filmstrip.
*/
UI.toggleFilmstrip = function() {
const { visible } = APP.store.getState()['features/filmstrip'];
APP.store.dispatch(setFilmstripVisible(!visible));
};
/**
* Sets muted video state for participant
*/
@@ -219,33 +194,6 @@ UI.setVideoMuted = function(id) {
UI.updateLargeVideo = (id, forceUpdate) => VideoLayout.updateLargeVideo(id, forceUpdate);
/**
* Adds a listener that would be notified on the given type of event.
*
* @param type the type of the event we're listening for
* @param listener a function that would be called when notified
*/
UI.addListener = function(type, listener) {
eventEmitter.on(type, listener);
};
/**
* Removes the all listeners for all events.
*
* @returns {void}
*/
UI.removeAllListeners = function() {
eventEmitter.removeAllListeners();
};
/**
* Emits the event of given type by specifying the parameters in options.
*
* @param type the type of the event we're emitting
* @param options the parameters for the event
*/
UI.emitEvent = (type, ...options) => eventEmitter.emit(type, ...options);
// Used by torture.
UI.showToolbar = timeout => APP.store.dispatch(showToolbox(timeout));

View File

@@ -138,8 +138,7 @@ export default class EtherpadManager {
/**
*
*/
constructor(eventEmitter) {
this.eventEmitter = eventEmitter;
constructor() {
this.etherpad = null;
}

View File

@@ -11,6 +11,7 @@ import { createScreenSharingIssueEvent } from '../../../react/features/analytics
import { sendAnalytics } from '../../../react/features/analytics/functions';
import Avatar from '../../../react/features/base/avatar/components/Avatar';
import theme from '../../../react/features/base/components/themes/participantsPaneTheme.json';
import { getSsrcRewritingFeatureFlag } from '../../../react/features/base/config/functions.any';
import i18next from '../../../react/features/base/i18n/i18next';
import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
import { VIDEO_TYPE } from '../../../react/features/base/media/constants';
@@ -221,10 +222,10 @@ export default class LargeVideoManager {
this.updateInProcess = true;
// Include hide()/fadeOut only if we're switching between users
// eslint-disable-next-line eqeqeq
// Include hide()/fadeOut if we're switching between users or between different sources of the same user.
const container = this.getCurrentContainer();
const isUserSwitch = this.newStreamData.id !== container.id;
const isUserSwitch = container.id !== this.newStreamData.id
|| container.stream?.getSourceName() !== this.newStreamData.stream?.getSourceName();
const preUpdate = isUserSwitch ? container.hide() : Promise.resolve();
preUpdate.then(() => {

View File

@@ -501,8 +501,7 @@ export class VideoContainer extends LargeContainer {
* @param {string} videoType video type
*/
setStream(userID, stream, videoType) {
this.userId = userID;
if (this.stream === stream && !stream?.forceStreamToReattach) {
if (this.userId === userID && this.stream === stream && !stream?.forceStreamToReattach) {
logger.debug(`SetStream on the large video for user ${userID} ignored: the stream is not changed!`);
// Handles the use case for the remote participants when the
@@ -516,6 +515,8 @@ export class VideoContainer extends LargeContainer {
return;
}
this.userId = userID;
if (stream?.forceStreamToReattach) {
delete stream.forceStreamToReattach;
}
@@ -540,9 +541,8 @@ export class VideoContainer extends LargeContainer {
logger.error(`Attaching the remote track ${stream} to large video has failed with `, error);
});
// Ensure large video gets play() called on it when a new stream is attached to it. This is necessary in the
// case of Safari as autoplay doesn't kick-in automatically on Safari 15 and newer versions.
browser.isWebKitBased() && this._play();
// Ensure large video gets play() called on it when a new stream is attached to it.
this._play();
const flipX = stream.isLocal() && this.localFlipX && !this.isScreenSharing();

24
package-lock.json generated
View File

@@ -61,7 +61,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/v1807.0.0+b59b8cb0/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1818.0.0+799236df/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -100,7 +100,7 @@
"react-native-url-polyfill": "2.0.0",
"react-native-video": "6.0.0-alpha.11",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "118.0.5",
"react-native-webrtc": "118.0.7",
"react-native-webview": "13.5.1",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
@@ -12866,8 +12866,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1807.0.0+b59b8cb0/lib-jitsi-meet.tgz",
"integrity": "sha512-M51XeZWqPiSUzRNz8JPxbi45iBBYzK3Z+7l/MFDGBoDtNNe3KXnWUjwL/F6PK/AL1VlN5nvp51sxQ8K/gOXl1g==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1818.0.0+799236df/lib-jitsi-meet.tgz",
"integrity": "sha512-JEwp9BzqU0nxVC0jbCOvTGjUEU3zSLRWx+N3VToeq9hdd5qrf2j72+2PRZ6ANp15Cu9bglkmjNNE94FeuxYJ2w==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -16914,9 +16914,9 @@
}
},
"node_modules/react-native-webrtc": {
"version": "118.0.5",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-118.0.5.tgz",
"integrity": "sha512-R665M4V8pJhpOvFT0iD0T70TNxX1Q92ikt7h8uTLaNDt08OymlnJ+5S95XS2qIDRUjN1aMjjnzS+HlmZ4C1AQg==",
"version": "118.0.7",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-118.0.7.tgz",
"integrity": "sha512-odgd4CNSGQmI8n/pEbxlUtJBTJ8uqE51B1/NUEAvO1AQbeXsyFNHEG0H2T27eMefo5u0GKcRpNkZpXi6fctTkQ==",
"dependencies": {
"base64-js": "1.5.1",
"debug": "4.3.4",
@@ -29374,8 +29374,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1807.0.0+b59b8cb0/lib-jitsi-meet.tgz",
"integrity": "sha512-M51XeZWqPiSUzRNz8JPxbi45iBBYzK3Z+7l/MFDGBoDtNNe3KXnWUjwL/F6PK/AL1VlN5nvp51sxQ8K/gOXl1g==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1818.0.0+799236df/lib-jitsi-meet.tgz",
"integrity": "sha512-JEwp9BzqU0nxVC0jbCOvTGjUEU3zSLRWx+N3VToeq9hdd5qrf2j72+2PRZ6ANp15Cu9bglkmjNNE94FeuxYJ2w==",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
@@ -32312,9 +32312,9 @@
}
},
"react-native-webrtc": {
"version": "118.0.5",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-118.0.5.tgz",
"integrity": "sha512-R665M4V8pJhpOvFT0iD0T70TNxX1Q92ikt7h8uTLaNDt08OymlnJ+5S95XS2qIDRUjN1aMjjnzS+HlmZ4C1AQg==",
"version": "118.0.7",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-118.0.7.tgz",
"integrity": "sha512-odgd4CNSGQmI8n/pEbxlUtJBTJ8uqE51B1/NUEAvO1AQbeXsyFNHEG0H2T27eMefo5u0GKcRpNkZpXi6fctTkQ==",
"requires": {
"base64-js": "1.5.1",
"debug": "4.3.4",

View File

@@ -67,7 +67,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1807.0.0+b59b8cb0/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1818.0.0+799236df/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -106,7 +106,7 @@
"react-native-url-polyfill": "2.0.0",
"react-native-video": "6.0.0-alpha.11",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "118.0.5",
"react-native-webrtc": "118.0.7",
"react-native-webview": "13.5.1",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",

View File

@@ -6,13 +6,15 @@ Inside your project, run;
```console
npm i @jitsi/react-native-sdk
```
<br/><br/>Additionally, if not already installed, some dependencies will need to be added.
If there are conflicts, you can use ```--force```
<br/>Additionally, if not already installed, some dependencies will need to be added.
This can be done by running the following script:
```console
node node_modules/@jitsi/react-native-sdk/update_dependencies.js
```
This will check and update all your dependencies.<br/><br/>
After that you need to ```npm i```, if some dependency versions were updated.
[comment]: # (These deps definitely need to be added manually, more could be neccesary)
@@ -47,7 +49,7 @@ module.exports = (async () => {
})();
```
### iOS
## iOS
#### Project Info.plist
- Add a *Privacy - Camera Usage Description*
@@ -65,7 +67,7 @@ Run;
cd ios && pod install && cd ..
```
### Android
## Android
- In your build.gradle have at least `minSdkVersion = 24`
- In `android/app/src/debug/AndroidManifest.xml` and `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag, include
@@ -73,26 +75,56 @@ cd ios && pod install && cd ..
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
```
- In `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag, include
### Services
#### Screen share
- Go to your `MainApplication.java` file and add:
1. `import com.oney.WebRTCModule.WebRTCModuleOptions;` that comes from `react-native-webrtc` dependency.
2. `WebRTCModuleOptions options = WebRTCModuleOptions.getInstance();` instance it.
3. `options.enableMediaProjectionService = true;` enable foreground service that takes care of screen-sharing feature.
- Go to your `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag and include
```xml
<service
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
android:foregroundServiceType="mediaProjection" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
```
This will take care of the screen share feature.
If you want to test all the steps before applying them to your app, you can check our React Native SDK sample app here:
https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native
### Using JWT tokens
- If you are planning to use tokens or another domain you can do that by updating the following props, as shown below.
## Using JWT tokens
- If you are planning to use tokens or another domain, you can do that by updating the following props, as shown below.
- For example:
```javascript
<JitsiMeeting
room={'ThisIsNotATestRoomName'}
serverURL={'https://meet.jit.si/'}
token={'dkhalhfajhflahlfaahalhfahfsl'} />
room = { 'ThisIsNotATestRoomName' }
serverURL = { 'https://meet.jit.si/' }
token={ 'dkhalhfajhflahlfaahalhfahfsl' } />
```
## Using custom overflow menu buttons
- If you are planning to add custom overflow menu buttons, you can do that by updating the ```config``` prop, as shown below.
- For example:
```javascript
<JitsiMeeting
config = {{
customToolbarButtons: [
{
icon: "https://w7.pngwing.com/pngs/987/537/png-transparent-download-downloading-save-basic-user-interface-icon-thumbnail.png",
id: "btn1",
text: "Button one"
}, {
icon: "https://w7.pngwing.com/pngs/987/537/png-transparent-download-downloading-save-basic-user-interface-icon-thumbnail.png",
id: "btn2",
text: "Button two"
}
]
}}
room = { 'ThisIsNotATestRoomName' }
serverURL = { 'https://meet.jit.si/' }
token = { 'dkhalhfajhflahlfaahalhfahfsl' } />
```
For more details on how you can use React Native SDK with React Native app, you can follow this link:

View File

@@ -72,8 +72,7 @@ export const _getTokenAuthState = (
// @ts-ignore
state['config.startWithVideoMuted'] = true;
}
const params = parseURLParams(locationURL, true);
const params = parseURLParams(locationURL);
for (const key of Object.keys(params)) {
// we allow only config and interfaceConfig overrides in the state

View File

@@ -1,5 +1,3 @@
// @ts-expect-error
import UIEvents from '../../../../service/UI/UIEvents';
import { createAudioOnlyChangedEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { IStore } from '../../app/types';
@@ -33,7 +31,7 @@ export function setAudioOnly(audioOnly: boolean) {
if (typeof APP !== 'undefined') {
// TODO This should be a temporary solution that lasts only until video
// tracks and all ui is moved into react/redux on the web.
APP.UI.emitEvent(UIEvents.TOGGLE_AUDIO_ONLY, audioOnly);
APP.conference.onToggleAudioOnly();
}
}
};

View File

@@ -7,6 +7,7 @@ import { overwriteConfig } from '../config/actions';
import { getReplaceParticipant } from '../config/functions';
import { connect, disconnect, hangup } from '../connection/actions';
import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
import { hasAvailableDevices } from '../devices/functions.any';
import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
import {
gumPending,
@@ -15,7 +16,7 @@ import {
setVideoMuted,
setVideoUnmutePermissions
} from '../media/actions';
import { MEDIA_TYPE } from '../media/constants';
import { MEDIA_TYPE, VIDEO_MUTISM_AUTHORITY } from '../media/constants';
import { IGUMPendingState } from '../media/types';
import {
dominantSpeakerChanged,
@@ -1059,11 +1060,43 @@ export function redirect(vnode: string, focusJid: string, username: string) {
.then(() => dispatch(conferenceWillInit()))
.then(() => dispatch(connect()))
.then(() => {
// Clear the gum pending state in case we have set it to pending since we are starting the
// conference without tracks.
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
if (!vnode) {
const state = getState();
const { enableMediaOnPromote = {} } = state['features/base/config'].visitors ?? {};
const { audio = false, video = false } = enableMediaOnPromote;
if (audio) {
const { available, muted, unmuteBlocked } = state['features/base/media'].audio;
const { startSilent } = state['features/base/config'];
// do not unmute the user if he was muted before (on the prejoin, the config
// or URL param, etc.)
if (!unmuteBlocked && !muted && !startSilent && available) {
dispatch(setAudioMuted(false, true));
// // FIXME: The old conference logic still relies on this event being emitted.
typeof APP === 'undefined' || APP.conference.muteAudio(false);
}
}
if (video) {
const { muted, unmuteBlocked } = state['features/base/media'].video;
// do not unmute the user if he was muted before (on the prejoin, the config, URL param or
// audo only, etc)
if (!unmuteBlocked && !muted && hasAvailableDevices(state, 'videoInput')) {
dispatch(setVideoMuted(false, VIDEO_MUTISM_AUTHORITY.USER, true));
// // FIXME: The old conference logic still relies on this event being emitted.
typeof APP === 'undefined' || APP.conference.muteVideo(false, false);
}
}
}
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
if (typeof APP !== 'undefined') {
APP.conference.startConference([]);

View File

@@ -76,6 +76,7 @@ export default [
'channelLastN',
'connectionIndicators',
'constraints',
'customToolbarButtons',
'brandingRoomAlias',
'debug',
'debugAudioLevels',
@@ -227,6 +228,7 @@ export default [
'useHostPageLocalStorage',
'useTurnUdp',
'videoQuality',
'visitors',
'watchRTCConfigParams',
'webrtcIceTcpDisable',
'webrtcIceUdpDisable',

View File

@@ -75,6 +75,12 @@ export interface IConfigState extends IConfig {
p2p?: object;
websocket?: string;
};
visitors?: {
enableMediaOnPromote?: {
audio?: boolean;
video?: boolean;
};
};
}
ReducerRegistry.register<IConfigState>('features/base/config', (state = _getInitialState(), action): IConfigState => {

View File

@@ -1,7 +1,5 @@
import { AnyAction } from 'redux';
// @ts-expect-error
import UIEvents from '../../../../service/UI/UIEvents';
import { IStore } from '../../app/types';
import { processExternalDeviceRequest } from '../../device-selection/functions';
import { showNotification, showWarningNotification } from '../../notifications/actions';
@@ -161,7 +159,7 @@ MiddlewareRegistry.register(store => next => action => {
if (isPrejoinPageVisible(store.getState())) {
store.dispatch(replaceAudioTrackById(action.deviceId));
} else {
APP.UI.emitEvent(UIEvents.AUDIO_DEVICE_CHANGED, action.deviceId);
APP.conference.onAudioDeviceChanged(action.deviceId);
}
break;
case SET_VIDEO_INPUT_DEVICE: {
@@ -174,7 +172,7 @@ MiddlewareRegistry.register(store => next => action => {
if (isPrejoinPageVisible(store.getState())) {
store.dispatch(replaceVideoTrackById(action.deviceId));
} else {
APP.UI.emitEvent(UIEvents.VIDEO_DEVICE_CHANGED, action.deviceId);
APP.conference.onVideoDeviceChanged(action.deviceId);
}
break;
}

View File

@@ -43,18 +43,6 @@ export const KICK_PARTICIPANT = 'KICK_PARTICIPANT';
*/
export const MUTE_REMOTE_PARTICIPANT = 'MUTE_REMOTE_PARTICIPANT';
/**
* Create an action for when the local participant's display name is updated.
*
* {
* type: PARTICIPANT_DISPLAY_NAME_CHANGED,
* id: string,
* name: string
* }
*/
export const PARTICIPANT_DISPLAY_NAME_CHANGED
= 'PARTICIPANT_DISPLAY_NAME_CHANGED';
/**
* Action to signal that ID of participant has changed. This happens when
* local participant joins a new conference or quits one.

View File

@@ -12,9 +12,9 @@ import { getCurrentConference } from '../conference/functions';
import { ADD_PEOPLE_ENABLED } from '../flags/constants';
import { getFeatureFlag } from '../flags/functions';
import i18next from '../i18n/i18next';
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
import { MEDIA_TYPE, MediaType, VIDEO_TYPE } from '../media/constants';
import { toState } from '../redux/functions';
import { getScreenShareTrack } from '../tracks/functions.any';
import { getScreenShareTrack, isLocalTrackMuted } from '../tracks/functions.any';
import { createDeferred } from '../util/helpers';
import {
@@ -362,6 +362,42 @@ export function getRemoteParticipantCountWithFake(stateful: IStateful) {
return participantsState.remote.size;
}
/**
* Returns the muted state of the given media source for a given participant.
*
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's.
* @param {IParticipant} participant - The participant entity.
* @param {MediaType} mediaType - The media type.
* @returns {boolean} - True its muted, false otherwise.
*/
export function getMutedStateByParticipantAndMediaType(
stateful: IStateful,
participant: IParticipant,
mediaType: MediaType): boolean {
const type = mediaType === MEDIA_TYPE.SCREENSHARE ? 'video' : mediaType;
if (participant.local) {
const state = toState(stateful);
const tracks = state['features/base/tracks'];
return isLocalTrackMuted(tracks, mediaType);
}
const sources = participant.sources?.get(type);
if (!sources) {
return true;
}
if (mediaType === MEDIA_TYPE.AUDIO) {
return Array.from(sources.values())[0].muted;
}
const videoType = mediaType === MEDIA_TYPE.VIDEO ? VIDEO_TYPE.CAMERA : VIDEO_TYPE.SCREENSHARE;
const source = Array.from(sources.values()).find(src => src.videoType === videoType);
return source?.muted ?? true;
}
/**
* Returns a count of the known participants in the passed in redux state,
* including fake participants.
@@ -468,28 +504,28 @@ export function getScreenshareParticipantIds(stateful: IStateful): Array<string>
}
/**
* Returns a list source name associated with a given remote participant and for the given media type.
* Returns a list of source names associated with a given remote participant and for the given media type.
*
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
* retrieve the state.
* @param {string} id - The id of the participant whose source names are to be retrieved.
* @param {string} mediaType - The type of source, audio or video.
* @returns {Array<string>|undefined}
* @returns {Array<string>}
*/
export function getSourceNamesByMediaType(
export function getSourceNamesByMediaTypeAndParticipant(
stateful: IStateful,
id: string,
mediaType: string): Array<string> | undefined {
mediaType: string): Array<string> {
const participant: IParticipant | undefined = getParticipantById(stateful, id);
if (!participant) {
return;
return [];
}
const sources = participant.sources;
if (!sources) {
return;
return [];
}
return Array.from(sources.get(mediaType) ?? new Map())
@@ -497,6 +533,37 @@ export function getSourceNamesByMediaType(
.map(s => s[0]);
}
/**
* Returns a list of source names associated with a given remote participant and for the given video type (only for
* video sources).
*
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
* retrieve the state.
* @param {string} id - The id of the participant whose source names are to be retrieved.
* @param {string} videoType - The type of video, camera or desktop.
* @returns {Array<string>}
*/
export function getSourceNamesByVideoTypeAndParticipant(
stateful: IStateful,
id: string,
videoType: string): Array<string> {
const participant: IParticipant | undefined = getParticipantById(stateful, id);
if (!participant) {
return [];
}
const sources = participant.sources;
if (!sources) {
return [];
}
return Array.from(sources.get(MEDIA_TYPE.VIDEO) ?? new Map())
.filter(source => source[1].videoType === videoType && (videoType === VIDEO_TYPE.CAMERA || !source[1].muted))
.map(s => s[0]);
}
/**
* Returns the presence status of a participant associated with the passed id.
*

View File

@@ -2,8 +2,6 @@ import i18n from 'i18next';
import { batch } from 'react-redux';
import { AnyAction } from 'redux';
// @ts-expect-error
import UIEvents from '../../../../service/UI/UIEvents';
import { IStore } from '../../app/types';
import { approveParticipant } from '../../av-moderation/actions';
import { UPDATE_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
@@ -41,7 +39,6 @@ import {
MUTE_REMOTE_PARTICIPANT,
OVERWRITE_PARTICIPANTS_NAMES,
OVERWRITE_PARTICIPANT_NAME,
PARTICIPANT_DISPLAY_NAME_CHANGED,
PARTICIPANT_JOINED,
PARTICIPANT_LEFT,
PARTICIPANT_UPDATED,
@@ -234,20 +231,6 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
// TODO Remove this middleware when the local display name update flow is
// fully brought into redux.
case PARTICIPANT_DISPLAY_NAME_CHANGED: {
if (typeof APP !== 'undefined') {
const participant = getLocalParticipant(store.getState());
if (participant && participant.id === action.id) {
APP.UI.emitEvent(UIEvents.NICKNAME_CHANGED, action.name);
}
}
break;
}
case RAISE_HAND_UPDATED: {
const { participant } = action;
let queue = getRaiseHandsQueue(store.getState());

View File

@@ -66,16 +66,6 @@ export const TRACK_MUTE_UNMUTE_FAILED = 'TRACK_MUTE_UNMUTE_FAILED';
*/
export const TRACK_NO_DATA_FROM_SOURCE = 'TRACK_NO_DATA_FROM_SOURCE';
/**
* The type of redux action dispatched when the owner of a track changes due to ssrc remapping.
*
* {
* type: TRACK_OWNER_CHANGED,
* track: Track
* }
*/
export const TRACK_OWNER_CHANGED = 'TRACK_OWNER_CHANGED';
/**
* The type of redux action dispatched when a track has been (locally or
* remotely) removed from the conference.

View File

@@ -26,7 +26,6 @@ import {
TRACK_CREATE_ERROR,
TRACK_MUTE_UNMUTE_FAILED,
TRACK_NO_DATA_FROM_SOURCE,
TRACK_OWNER_CHANGED,
TRACK_REMOVED,
TRACK_STOPPED,
TRACK_UPDATED,
@@ -381,9 +380,6 @@ export function trackAdded(track: any) {
track.on(
JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,
(type: VideoType) => dispatch(trackVideoTypeChanged(track, type)));
track.on(
JitsiTrackEvents.TRACK_OWNER_CHANGED,
(owner: string) => dispatch(trackOwnerChanged(track, owner)));
const local = track.isLocal();
const mediaType = track.getVideoType() === VIDEO_TYPE.DESKTOP
? MEDIA_TYPE.SCREENSHARE
@@ -625,32 +621,6 @@ export function trackStreamingStatusChanged(track: any, streamingStatus: string)
};
}
/**
* Create an action for when the owner of the track changes due to ssrc remapping.
*
* @param {(JitsiRemoteTrack)} track - JitsiTrack instance.
* @param {string} participantId - New owner's participant ID.
* @returns {{
* type: TRACK_OWNER_CHANGED,
* track: Track
* }}
*/
export function trackOwnerChanged(track: any, participantId: string): {
track: {
jitsiTrack: any;
participantId: string;
};
type: 'TRACK_OWNER_CHANGED';
} {
return {
type: TRACK_OWNER_CHANGED,
track: {
jitsiTrack: track,
participantId
}
};
}
/**
* Signals passed tracks to be added.
*

View File

@@ -15,7 +15,6 @@ import {
TRACK_ADDED,
TRACK_MUTE_UNMUTE_FAILED,
TRACK_NO_DATA_FROM_SOURCE,
TRACK_OWNER_CHANGED,
TRACK_REMOVED,
TRACK_STOPPED,
TRACK_UPDATED
@@ -82,23 +81,6 @@ MiddlewareRegistry.register(store => next => action => {
return result;
}
case TRACK_OWNER_CHANGED: {
const oldTrack = getTrackByJitsiTrack(store.getState()['features/base/tracks'], action.track?.jitsiTrack);
const oldOwner = oldTrack?.participantId;
const result = next(action);
const newOwner = action.track?.participantId;
if (oldOwner) {
logTracksForParticipant(store.getState()['features/base/tracks'], oldOwner, 'Owner changed');
}
if (newOwner) {
logTracksForParticipant(store.getState()['features/base/tracks'], newOwner, 'Owner changed');
}
return result;
}
case TRACK_MUTE_UNMUTE_FAILED: {
const { jitsiTrack } = action.track;
const muted = action.wasMuted;

View File

@@ -10,7 +10,6 @@ import {
TRACK_CREATE_CANCELED,
TRACK_CREATE_ERROR,
TRACK_NO_DATA_FROM_SOURCE,
TRACK_OWNER_CHANGED,
TRACK_REMOVED,
TRACK_UPDATED,
TRACK_WILL_CREATE
@@ -43,18 +42,6 @@ function track(state: ITrack, action: AnyAction) {
}
break;
case TRACK_OWNER_CHANGED: {
const t = action.track;
if (state.jitsiTrack === t.jitsiTrack) {
return {
...state,
participantId: t.participantId
};
}
break;
}
case TRACK_UPDATED: {
const t = action.track;
@@ -104,7 +91,6 @@ ReducerRegistry.register<ITracksState>('features/base/tracks', (state = [], acti
switch (action.type) {
case PARTICIPANT_ID_CHANGED:
case TRACK_NO_DATA_FROM_SOURCE:
case TRACK_OWNER_CHANGED:
case TRACK_UPDATED:
return state.map((t: ITrack) => track(t, action));
case TRACK_ADDED: {

View File

@@ -2,7 +2,7 @@ import throttle from 'lodash/throttle';
import React, { RefObject } from 'react';
import { scrollIntoView } from 'seamless-scroll-polyfill';
import { MESSAGE_TYPE_REMOTE } from '../../constants';
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
import AbstractMessageContainer, { IProps } from '../AbstractMessageContainer';
import ChatMessageGroup from './ChatMessageGroup';
@@ -134,17 +134,19 @@ export default class MessageContainer extends AbstractMessageContainer<IProps, I
/**
* Implements {@code Component#componentDidUpdate}.
* If the user receive a new message scroll automatically to the bottom if scroll position was at the bottom.
* If the user receive a new message or the local user send a new message,
* scroll automatically to the bottom if scroll position was at the bottom.
* Otherwise update hasNewMessages from component state.
*
* @inheritdoc
* @returns {void}
*/
componentDidUpdate(prevProps: IProps) {
const hasNewMessages = this.props.messages.length !== prevProps.messages.length;
const newMessages = this.props.messages.filter(message => !prevProps.messages.includes(message));
const hasLocalMessage = newMessages.map(message => message.messageType).includes(MESSAGE_TYPE_LOCAL);
if (hasNewMessages) {
if (this.state.isScrolledToBottom) {
if (newMessages.length > 0) {
if (this.state.isScrolledToBottom || hasLocalMessage) {
this.scrollToElement(false, null);
} else {
// eslint-disable-next-line react/no-did-update-set-state

View File

@@ -368,8 +368,6 @@ class Conference extends AbstractConference<IProps, any> {
*/
_start() {
APP.UI.start();
APP.UI.registerListeners();
APP.UI.bindEvents();
FULL_SCREEN_EVENTS.forEach(name =>

View File

@@ -60,7 +60,8 @@ class SharedDocument extends PureComponent<IProps> {
renderLoading = { this._renderLoading }
source = {{ uri: _documentUrl ?? '' }}
startInLoadingState = { true }
style = { styles.sharedDoc } />
style = { styles.sharedDoc }
webviewDebuggingEnabled = { true } />
</JitsiScreen>
);
}

View File

@@ -1,5 +1,3 @@
// @ts-expect-error
import UIEvents from '../../../service/UI/UIEvents';
import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
import { getCurrentConference } from '../base/conference/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
@@ -41,7 +39,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
}
case TOGGLE_DOCUMENT_EDITING: {
if (typeof APP !== 'undefined') {
APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
APP.UI.onEtherpadClicked();
}
break;
}

View File

@@ -83,7 +83,8 @@ class DialInSummary extends PureComponent<IProps> {
setSupportMultipleWindows = { false }
source = {{ uri: getDialInfoPageURLForURIString(summaryUrl) ?? '' }}
startInLoadingState = { true }
style = { styles.webView } />
style = { styles.webView }
webviewDebuggingEnabled = { true } />
</JitsiScreen>
);
}

View File

@@ -2,9 +2,12 @@ import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../../app/types';
import { getSsrcRewritingFeatureFlag } from '../../../base/config/functions.any';
import { translate } from '../../../base/i18n/functions';
import { MEDIA_TYPE } from '../../../base/media/constants';
import {
getLocalParticipant,
getMutedStateByParticipantAndMediaType,
getParticipantById,
getParticipantDisplayName,
hasRaisedHand,
@@ -166,8 +169,12 @@ function mapStateToProps(state: IReduxState, ownProps: any) {
const { participant } = ownProps;
const { ownerId } = state['features/shared-video'];
const localParticipantId = getLocalParticipant(state)?.id;
const _isAudioMuted = Boolean(participant && isParticipantAudioMuted(participant, state));
const _isVideoMuted = isParticipantVideoMuted(participant, state);
const _isAudioMuted = getSsrcRewritingFeatureFlag(state)
? Boolean(participant && getMutedStateByParticipantAndMediaType(state, participant, MEDIA_TYPE.AUDIO))
: Boolean(participant && isParticipantAudioMuted(participant, state));
const _isVideoMuted = getSsrcRewritingFeatureFlag(state)
? Boolean(participant && getMutedStateByParticipantAndMediaType(state, participant, MEDIA_TYPE.VIDEO))
: isParticipantVideoMuted(participant, state);
const audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state);
const videoMediaState = getParticipantVideoMediaState(participant, _isVideoMuted, state);
const { disableModeratorIndicator } = state['features/base/config'];

View File

@@ -2,10 +2,12 @@ import React, { useCallback, useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { getSsrcRewritingFeatureFlag } from '../../../base/config/functions.any';
import { JitsiTrackEvents } from '../../../base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../../../base/media/constants';
import {
getLocalParticipant,
getMutedStateByParticipantAndMediaType,
getParticipantByIdOrUndefined,
getParticipantDisplayName,
hasRaisedHand,
@@ -299,15 +301,15 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
const { participantID, searchString } = ownProps;
const { ownerId } = state['features/shared-video'];
const localParticipantId = getLocalParticipant(state)?.id;
const participant = getParticipantByIdOrUndefined(state, participantID);
const _displayName = getParticipantDisplayName(state, participant?.id ?? '');
const _matchesSearch = participantMatchesSearch(participant, searchString);
const _isAudioMuted = Boolean(participant && isParticipantAudioMuted(participant, state));
const _isVideoMuted = isParticipantVideoMuted(participant, state);
const _isAudioMuted = getSsrcRewritingFeatureFlag(state)
? Boolean(participant && getMutedStateByParticipantAndMediaType(state, participant, MEDIA_TYPE.AUDIO))
: Boolean(participant && isParticipantAudioMuted(participant, state));
const _isVideoMuted = getSsrcRewritingFeatureFlag(state)
? Boolean(participant && getMutedStateByParticipantAndMediaType(state, participant, MEDIA_TYPE.VIDEO))
: isParticipantVideoMuted(participant, state);
const _audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state);
const _videoMediaState = getParticipantVideoMediaState(participant, _isVideoMuted, state);
const _quickActionButtonType = getQuickActionButtonType(participant, _isAudioMuted, _isVideoMuted, state);

View File

@@ -1,7 +1,5 @@
import { AnyAction } from 'redux';
// @ts-expect-error
import UIEvents from '../../../service/UI/UIEvents';
import { IStore } from '../app/types';
import {
CONFERENCE_FAILED,
@@ -36,12 +34,6 @@ MiddlewareRegistry.register(store => next => action => {
return _conferenceJoined(store, next, action);
case LOCK_STATE_CHANGED: {
// TODO Remove this logic when all components interested in the lock
// state change event are moved into react/redux.
if (typeof APP !== 'undefined') {
APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK, action.locked);
}
const previousLockedState = store.getState()['features/base/conference'].locked;
const result = next(action);

View File

@@ -12,6 +12,7 @@ import { hangup } from '../base/connection/actions.web';
import { openDialog } from '../base/dialog/actions';
import i18next from '../base/i18n/i18next';
import { browser } from '../base/lib-jitsi-meet';
import { getNormalizedDisplayName } from '../base/participants/functions';
import { updateSettings } from '../base/settings/actions';
import { getLocalVideoTrack } from '../base/tracks/functions.web';
import { appendURLHashParam } from '../base/util/uri';
@@ -191,7 +192,7 @@ export function submitProfileTab(newState: any) {
const currentState = getProfileTabProps(getState());
if (newState.displayName !== currentState.displayName) {
APP.conference.changeLocalDisplayName(newState.displayName);
dispatch(updateSettings({ displayName: getNormalizedDisplayName(newState.displayName) }));
}
if (newState.email !== currentState.email) {

View File

@@ -1,5 +1,3 @@
// @ts-expect-error
import UIEvents from '../../../service/UI/UIEvents';
import { VIDEO_MUTE, createToolbarEvent } from '../analytics/AnalyticsEvents';
import { sendAnalytics } from '../analytics/functions';
import { IStore } from '../app/types';
@@ -103,7 +101,7 @@ export function handleToggleVideoMuted(muted: boolean, showUI: boolean, ensureTr
// FIXME: The old conference logic still relies on this event being
// emitted.
typeof APP === 'undefined'
|| APP.UI.emitEvent(UIEvents.VIDEO_MUTED, muted, showUI);
|| APP.conference.muteVideo(muted, showUI);
};
}

View File

@@ -1,10 +1,10 @@
import React from 'react';
import { Image } from 'react-native';
import { SvgCssUri } from 'react-native-svg';
import { connect } from 'react-redux';
import { translate } from '../../../base/i18n/functions';
import AbstractButton, { IProps as AbstractButtonProps }
from '../../../base/toolbox/components/AbstractButton';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import styles from './styles';
@@ -30,11 +30,30 @@ class CustomOptionButton extends AbstractButton<IProps> {
*
* @returns {React.Component}
*/
icon = () => (
<Image
source = {{ uri: this.iconSrc }}
style = { styles.iconImageStyles } />
);
icon = () => {
let iconComponent;
if (!this.iconSrc) {
return null;
}
if (this.iconSrc?.includes('svg')) {
iconComponent
= (
<SvgCssUri
style = { styles.iconImageStyles }
uri = { this.iconSrc } />);
} else {
iconComponent
= (
<Image
source = {{ uri: this.iconSrc }}
style = { styles.iconImageStyles }
tintColor = { 'white' } />);
}
return iconComponent;
};
label = this.text;
}

View File

@@ -106,8 +106,8 @@ const styles = {
},
iconImageStyles: {
height: BaseTheme.spacing[5],
width: BaseTheme.spacing[5]
height: BaseTheme.spacing[4],
width: BaseTheme.spacing[4]
}
};

View File

@@ -1,5 +1,3 @@
// @ts-expect-error
import UIEvents from '../../../service/UI/UIEvents';
import {
AUDIO_MUTE,
VIDEO_MUTE,
@@ -55,8 +53,9 @@ export function muteLocal(enable: boolean, mediaType: MediaType, stopScreenShari
: setVideoMuted(enable, VIDEO_MUTISM_AUTHORITY.USER, /* ensureTrack */ true));
// FIXME: The old conference logic still relies on this event being emitted.
typeof APP === 'undefined'
|| APP.UI.emitEvent(isAudio ? UIEvents.AUDIO_MUTED : UIEvents.VIDEO_MUTED, enable);
if (typeof APP !== 'undefined') {
isAudio ? APP.conference.muteAudio(enable) : APP.conference.muteVideo(enable, false);
}
};
}

View File

@@ -3,10 +3,11 @@ import debounce from 'lodash/debounce';
import { IReduxState, IStore } from '../app/types';
import { _handleParticipantError } from '../base/conference/functions';
import { getSsrcRewritingFeatureFlag } from '../base/config/functions.any';
import { MEDIA_TYPE } from '../base/media/constants';
import { MEDIA_TYPE, VIDEO_TYPE } from '../base/media/constants';
import {
getLocalParticipant,
getSourceNamesByMediaType
getSourceNamesByMediaTypeAndParticipant,
getSourceNamesByVideoTypeAndParticipant
} from '../base/participants/functions';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { getTrackSourceNameByMediaTypeAndParticipant } from '../base/tracks/functions';
@@ -321,10 +322,10 @@ function _getSourceNames(participantList: Array<string>, state: IReduxState): Ar
participantList.forEach(participantId => {
if (getSsrcRewritingFeatureFlag(state)) {
const sourceNames: string[] | undefined
= getSourceNamesByMediaType(state, participantId, MEDIA_TYPE.VIDEO);
const sourceNames: string[]
= getSourceNamesByMediaTypeAndParticipant(state, participantId, MEDIA_TYPE.VIDEO);
sourceNames?.length && sourceNamesList.push(...sourceNames);
sourceNames.length && sourceNamesList.push(...sourceNames);
} else {
let sourceName: string;
@@ -428,8 +429,9 @@ function _updateReceiverVideoConstraints({ getState }: IStore) {
if (remoteScreenShares.includes(largeVideoParticipantId)) {
largeVideoSourceName = largeVideoParticipantId;
} else {
largeVideoSourceName = getTrackSourceNameByMediaTypeAndParticipant(
tracks, MEDIA_TYPE.VIDEO, largeVideoParticipantId);
largeVideoSourceName = getSsrcRewritingFeatureFlag(state)
? getSourceNamesByVideoTypeAndParticipant(state, largeVideoParticipantId, VIDEO_TYPE.CAMERA)[0]
: getTrackSourceNameByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, largeVideoParticipantId);
}
}

View File

@@ -128,6 +128,7 @@ class Whiteboard extends PureComponent<IProps> {
safeAreaInsets = { [ 'bottom', 'left', 'right' ] }
style = { styles.backDrop }>
<WebView
domStorageEnabled = { false }
incognito = { true }
javaScriptEnabled = { true }
nestedScrollEnabled = { true }
@@ -139,7 +140,8 @@ class Whiteboard extends PureComponent<IProps> {
setSupportMultipleWindows = { false }
source = {{ uri }}
startInLoadingState = { true }
style = { styles.webView } />
style = { styles.webView }
webviewDebuggingEnabled = { true } />
</JitsiScreen>
);
}

View File

@@ -65,24 +65,27 @@ module:hook("muc-occupant-pre-join", function(event)
room:set_affiliation(true, jid_bare(occupant_jid), "member")
room:save_occupant(occupant);
end
-- bypass password on the flip device
local join = stanza:get_child("x", MUC_NS);
if not join then
join = stanza:tag("x", { xmlns = MUC_NS });
end
local password = join:get_child("password", MUC_NS);
if password then
join:maptags(
function(tag)
for k, v in pairs(tag) do
if k == "name" and v == "password" then
return nil
if room:get_password() then
-- bypass password on the flip device
local join = stanza:get_child("x", MUC_NS);
if not join then
join = stanza:tag("x", { xmlns = MUC_NS });
end
local password = join:get_child("password", MUC_NS);
if password then
join:maptags(
function(tag)
for k, v in pairs(tag) do
if k == "name" and v == "password" then
return nil
end
end
end
return tag
end);
return tag
end);
end
join:tag("password", { xmlns = MUC_NS }):text(room:get_password());
end
join:tag("password", { xmlns = MUC_NS }):text(room:get_password());
elseif not session.jitsi_meet_context_features.flip or session.jitsi_meet_context_features.flip == false or session.jitsi_meet_context_features.flip == "false" then
module:log("warn", "Flip device tag present without jwt permission")
--remove flip_device tag if somebody wants to abuse this feature

View File

@@ -1,8 +1,8 @@
-- Module which can be used as an http endpoint to send system chat messages to meeting participants. The provided token
-- Module which can be used as an http endpoint to send system private chat messages to meeting participants. The provided token
--- in the request is verified whether it has the right to do so. This module should be loaded under the virtual host.
-- Copyright (C) 2024-present 8x8, Inc.
-- curl https://{host}/send-system-message -d '{"message": "testmessage", "to": "{connection_jid}", "room": "{room_jid}"}' -H "content-type: application/json" -H "authorization: Bearer {token}"
-- curl https://{host}/send-system-chat-message -d '{"message": "testmessage", "connectionJIDs": ["{connection_jid}"], "room": "{room_jid}"}' -H "content-type: application/json" -H "authorization: Bearer {token}"
local util = module:require "util";
local token_util = module:require "token/util".new(module);
@@ -60,11 +60,11 @@ function handle_send_system_message (event)
local displayName = payload["displayName"];
local message = payload["message"];
local to = payload["to"];
local connectionJIDs = payload["connectionJIDs"];
local payload_room = payload["room"];
if not message or not to or not payload_room then
module:log("error", "One of [message, to, room] was not provided");
if not message or not connectionJIDs or not payload_room then
module:log("error", "One of [message, connectionJIDs, room] was not provided");
return { status_code = 400; }
end
@@ -99,15 +99,17 @@ function handle_send_system_message (event)
message = message,
};
local stanza = st.message({
from = room.jid,
to = to
})
:tag('json-message', { xmlns = 'http://jitsi.org/jitmeet' })
:text(json.encode(data))
:up();
for _, to in ipairs(connectionJIDs) do
local stanza = st.message({
from = room.jid,
to = to
})
:tag('json-message', { xmlns = 'http://jitsi.org/jitmeet' })
:text(json.encode(data))
:up();
room:route_stanza(stanza);
room:route_stanza(stanza);
end
return { status_code = 200 };
end

View File

@@ -311,7 +311,7 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
-- if visitor mode is started, then you are not allowed to join without request/response exchange of iqs -> deny access
-- check list of allowed jids for the room
host_module:hook('muc-occupant-pre-join', function (event)
local room, stanza, occupant, origin = event.room, event.stanza, event.occupant, event.origin;
local room, stanza, occupant, session = event.room, event.stanza, event.occupant, event.origin;
if is_healthcheck_room(room.jid) or is_admin(occupant.bare_jid) then
return;
@@ -341,8 +341,16 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
return;
end
module:log('error', 'Visitor needs to be allowed by a moderator %s', stanza.attr.from);
origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Visitor needs to be allowed by a moderator'));
session.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Visitor needs to be allowed by a moderator'));
return true;
elseif is_vpaas(room) then
-- special case for vpaas where if someone with a visitor token tries to join a room, where
-- there are no visitors yet, we deny access
if session.jitsi_meet_context_user and session.jitsi_meet_context_user.role == 'visitor' then
session.log('warn', 'Deny user join as visitor in the main meeting, not approved');
session.send(st.error_reply(
stanza, 'cancel', 'not-allowed', 'Visitor tried to join the main room without approval'));
end
end
end, 7); -- after muc_meeting_id, the logic for not joining before jicofo
@@ -443,8 +451,8 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
process_promotion_response(room, data.id, data.approved and 'true' or 'false');
else
-- we are in the case with admit all, we need to read data.ids
for i in pairs(data.ids) do
process_promotion_response(room, data.id, data.approved and 'true' or 'false');
for _,value in pairs(data.ids) do
process_promotion_response(room, value, data.approved and 'true' or 'false');
end
end
end

View File

@@ -1,57 +0,0 @@
export default {
NICKNAME_CHANGED: 'UI.nickname_changed',
/**
* Notifies that local user changed email.
*/
EMAIL_CHANGED: 'UI.email_changed',
/**
* Notifies that "start muted" settings changed.
*/
AUDIO_MUTED: 'UI.audio_muted',
VIDEO_MUTED: 'UI.video_muted',
ETHERPAD_CLICKED: 'UI.etherpad_clicked',
/**
* Updates shared video with params: url, state, time(optional)
* Where url is the video link, state is stop/start/pause and time is the
* current video playing time.
*/
TOGGLE_FULLSCREEN: 'UI.toogle_fullscreen',
FULLSCREEN_TOGGLED: 'UI.fullscreen_toggled',
/**
* Notifies that the audio only mode was toggled.
*/
TOGGLE_AUDIO_ONLY: 'UI.toggle_audioonly',
/**
* Notifies that a command to toggle the filmstrip has been issued. The
* event may optionally specify a {Boolean} (primitive) value to assign to
* the visibility of the filmstrip (i.e. the event may act as a setter).
* The very toggling of the filmstrip may or may not occurred at the time
* of the receipt of the event depending on the position of the receiving
* event listener in relation to the event listener which carries out the
* command to toggle the filmstrip.
*
* @see {TOGGLED_FILMSTRIP}
*/
TOGGLE_FILMSTRIP: 'UI.toggle_filmstrip',
HANGUP: 'UI.hangup',
VIDEO_DEVICE_CHANGED: 'UI.video_device_changed',
AUDIO_DEVICE_CHANGED: 'UI.audio_device_changed',
/**
* Notifies that the side toolbar container has been toggled. The actual
* event must contain the identifier of the container that has been toggled
* and information about toggle on or off.
*/
SIDE_TOOLBAR_CONTAINER_TOGGLED: 'UI.side_container_toggled',
/**
* Notifies that the raise hand has been changed.
*/
LOCAL_RAISE_HAND_CHANGED: 'UI.local_raise_hand_changed'
};