Compare commits

...

40 Commits

Author SHA1 Message Date
Edgars Voroboks
57bd074d00 fix(lang): Make existing phrasing in Latvian language more clear (#14721)
* fix(lang): More updates on existing phrasing in Latvian language

* fix(lang): Update Latvian language translation
2024-05-06 13:48:37 -05:00
Edgars Voroboks
843cdf05f5 fix(lang): Update Latvian language translation (#14720)
* fix(lang): Update Latvian language translation

* fix(lang): Use better wording in Latvian translation
2024-05-06 11:50:57 -05:00
Saúl Ibarra Corretgé
ede8f7ece9 chore(deps) react-native-webview@13.8.7
Fixes: https://github.com/jitsi/jitsi-meet-flutter-sdk/issues/60
2024-05-06 14:28:03 +02:00
Saúl Ibarra Corretgé
e5b736243d chore(deps) run npm audit fix 2024-05-06 14:27:31 +02:00
Saúl Ibarra Corretgé
1d8a9c11c8 feat(ci) run CI also on macOS on arm64
The macOS-13 image runners are x64, but macOS-14 are arm64.
2024-05-06 14:27:18 +02:00
Christoph Settgast
72d05bb969 feat(config) allow overriding desktop deeplinking toggle (#14712) 2024-05-06 11:44:51 +02:00
Christoph Settgast
2a5fc8cc4a lang: update German translation (#14711)
Signed-off-by: Christoph Settgast <csett86_git@quicksands.de>
2024-05-05 20:56:02 +02:00
damencho
aa9bd8c814 fix: Fixes visitors component.
In f279e63 there is some commented code that sneaked in by mistake.
2024-05-03 17:38:06 -05:00
Дамян Минков
f279e634e4 * feat(visitors): Shows notification when not-allowed error is detected.
* feat(visitors): Adds extensions to errors to distinguish them.

* feat: Shows notification when not-allowed error is detected.

* chore(deps) lib-jitsi-meet@latest

https://github.com/jitsi/lib-jitsi-meet/compare/v1822.0.0+58a91446...v1823.0.0+ec98b020

* squash: Update texts.
2024-05-03 16:30:43 -05:00
Ramis
3f7c8c204b Update main-ru.json fix lang 2024-05-03 13:04:06 -05:00
Nitin Kushwaha
375314cbbd fix(conference): save local recording when conference fails (#14606) 2024-05-02 14:46:09 -05:00
ilaydadastan
62d0d25395 fix(lang): added new tr translations 2024-05-02 06:28:32 -05:00
Jaya Allamsetty
3047af4df6 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1820.0.0+7a5381fb...v1822.0.0+58a91446
2024-04-30 15:23:02 -04:00
Jaya Allamsetty
6682b52a19 fix(notifications): Adds a 60 secs timeout for bridge channel message. (#14690)
* fix(notifications): Adds a 60 secs timeout for bridge channel message.
Also indicates that audio might get disrupted in addition to video when ssrc-rewriting is enabled.

* squash: decrease the timeout to 15 secs if the data channel doesn't establish at all.
2024-04-29 14:05:59 -04:00
Mayuki Arisaka
fee4151f83 fix(android) Keep microphone enabled when app is in background 2024-04-29 17:01:26 +03:00
Dimosthenis Nikoudis
25f2eb012e fix(participants-pane) close overflow menu when selecting option 2024-04-28 11:19:49 +02:00
Yurt Page
6976b45789 feat(fastlane) metadata i18n ru
Signed-off-by: Yurt Page <yurtpage@gmail.com>
2024-04-28 11:07:30 +02:00
Mathieu D'Amours
c69962675b fix(lang) update fr-ca translation 2024-04-28 10:11:00 +02:00
Jaya Allamsetty
2bb1d6dee3 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1819.0.0+2e2189f4...v1820.0.0+7a5381fb
2024-04-26 16:37:45 -04:00
damencho
43c1032b46 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1818.0.0+799236df...v1819.0.0+2e2189f4
2024-04-26 13:01:25 -05:00
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
58 changed files with 1428 additions and 694 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,24 @@ 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: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-13, macos-14]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
cache: 'npm'
- run: npm install
- name: setup Xcode
run: |
uname -a
xcode-select -p
sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
xcodebuild -version
- name: setup-cocoapods
uses: maxim-lobanov/setup-cocoapods@v1
with:
@@ -89,8 +99,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

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

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);
@@ -102,7 +133,7 @@ public class JitsiMeetOngoingConferenceService extends Service
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else {
startForeground(NOTIFICATION_ID, notification);
}

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';
@@ -18,7 +17,6 @@ import {
import { sendAnalytics } from './react/features/analytics/functions';
import {
maybeRedirectToWelcomePage,
redirectToStaticPage,
reloadWithStoredParams
} from './react/features/app/actions';
import { showModeratedNotification } from './react/features/av-moderation/actions';
@@ -56,7 +54,7 @@ import {
getConferenceOptions,
sendLocalParticipant
} from './react/features/base/conference/functions';
import { getReplaceParticipant } from './react/features/base/config/functions';
import { getReplaceParticipant, getSsrcRewritingFeatureFlag } from './react/features/base/config/functions';
import { connect } from './react/features/base/connection/actions.web';
import {
checkAndNotifyForNewDevice,
@@ -165,12 +163,8 @@ import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/Au
import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
import { muteLocal } from './react/features/video-menu/actions.any';
import UIEvents from './service/UI/UIEvents';
const logger = Logger.getLogger(__filename);
const eventEmitter = new EventEmitter();
let room;
/*
@@ -280,12 +274,6 @@ class ConferenceConnector {
switch (err) {
case JitsiConferenceErrors.NOT_ALLOWED_ERROR: {
// let's show some auth not allowed page
APP.store.dispatch(redirectToStaticPage('static/authError.html'));
break;
}
case JitsiConferenceErrors.RESERVATION_ERROR: {
const [ code, msg ] = params;
@@ -1916,21 +1904,12 @@ export default {
JitsiE2ePingEvents.E2E_RTT_CHANGED,
(...args) => APP.store.dispatch(e2eRttChanged(...args)));
APP.UI.addListener(UIEvents.AUDIO_MUTED, muted => {
this.muteAudio(muted);
});
APP.UI.addListener(UIEvents.VIDEO_MUTED, (muted, showUI = false) => {
this.muteVideo(muted, showUI);
});
room.addCommandListener(this.commands.defaults.ETHERPAD,
({ value }) => {
APP.UI.initEtherpad(value);
}
);
APP.UI.addListener(UIEvents.EMAIL_CHANGED,
this.changeLocalEmail.bind(this));
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
APP.store.dispatch(participantUpdated({
conference: room,
@@ -1950,9 +1929,6 @@ export default {
}));
});
APP.UI.addListener(UIEvents.NICKNAME_CHANGED,
this.changeLocalDisplayName.bind(this));
room.on(
JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
({ audio, video }) => {
@@ -1999,123 +1975,146 @@ export default {
room.on(
JitsiConferenceEvents.DATA_CHANNEL_CLOSED, ev => {
APP.store.dispatch(dataChannelClosed(ev.code, ev.reason));
APP.store.dispatch(showWarningNotification({
descriptionKey: 'notify.dataChannelClosedDescription',
titleKey: 'notify.dataChannelClosed',
uid: DATA_CHANNEL_CLOSED_NOTIFICATION_ID
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
const state = APP.store.getState();
const { dataChannelOpen } = state['features/base/conference'];
const timeout = typeof dataChannelOpen === 'undefined' ? 15000 : 60000;
// Show the notification only when the data channel connection doesn't get re-established in 60 secs if
// it was already established at the beginning of the call, show it sooner otherwise. This notification
// can be confusing and alarming to users even when there is no significant impact to user experience
// if the the reconnect happens immediately.
setTimeout(() => {
const { dataChannelOpen: open } = APP.store.getState()['features/base/conference'];
if (!open) {
const descriptionKey = getSsrcRewritingFeatureFlag(state)
? 'notify.dataChannelClosedDescriptionWithAudio' : 'notify.dataChannelClosedDescription';
const titleKey = getSsrcRewritingFeatureFlag(state)
? 'notify.dataChannelClosedWithAudio' : 'notify.dataChannelClosed';
APP.store.dispatch(dataChannelClosed(ev.code, ev.reason));
APP.store.dispatch(showWarningNotification({
descriptionKey,
titleKey,
uid: DATA_CHANNEL_CLOSED_NOTIFICATION_ID
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}
}, timeout);
}
);
},
// call hangup
APP.UI.addListener(UIEvents.HANGUP, () => {
this.hangup(true);
/**
* Handles audio device changes.
*
* @param {string} cameraDeviceId - The new device id.
* @returns {Promise}
*/
async onAudioDeviceChanged(micDeviceId) {
const audioWasMuted = this.isLocalAudioMuted();
// Disable noise suppression if it was enabled on the previous track.
await APP.store.dispatch(setNoiseSuppressionEnabled(false));
// When the 'default' mic needs to be selected, we need to pass the real device id to gUM instead of
// 'default' in order to get the correct MediaStreamTrack from chrome because of the following bug.
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689.
const isDefaultMicSelected = micDeviceId === 'default';
const selectedDeviceId = isDefaultMicSelected
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: micDeviceId;
logger.info(`Switching audio input device to ${selectedDeviceId}`);
sendAnalytics(createDeviceChangedEvent('audio', 'input'));
createLocalTracksF({
devices: [ 'audio' ],
micDeviceId: selectedDeviceId
})
.then(([ stream ]) => {
// if audio was muted before changing the device, mute
// with the new device
if (audioWasMuted) {
return stream.mute()
.then(() => stream);
}
return stream;
})
.then(async stream => {
await this._maybeApplyAudioMixerEffect(stream);
return this.useAudioStream(stream);
})
.then(() => {
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
if (localAudio && isDefaultMicSelected) {
// workaround for the default device to be shown as selected in the
// settings even when the real device id was passed to gUM because of the
// above mentioned chrome bug.
localAudio._realDeviceId = localAudio.deviceId = 'default';
}
})
.catch(err => {
logger.error(`Failed to switch to selected audio input device ${selectedDeviceId}, error=${err}`);
APP.store.dispatch(notifyMicError(err));
});
},
APP.UI.addListener(
UIEvents.VIDEO_DEVICE_CHANGED,
cameraDeviceId => {
const videoWasMuted = this.isLocalVideoMuted();
const localVideoTrack = getLocalJitsiVideoTrack(APP.store.getState());
/**
* Handles video device changes.
*
* @param {string} cameraDeviceId - The new device id.
* @returns {void}
*/
onVideoDeviceChanged(cameraDeviceId) {
const videoWasMuted = this.isLocalVideoMuted();
const localVideoTrack = getLocalJitsiVideoTrack(APP.store.getState());
if (localVideoTrack?.getDeviceId() === cameraDeviceId) {
return;
}
if (localVideoTrack?.getDeviceId() === cameraDeviceId) {
return;
}
sendAnalytics(createDeviceChangedEvent('video', 'input'));
sendAnalytics(createDeviceChangedEvent('video', 'input'));
createLocalTracksF({
devices: [ 'video' ],
cameraDeviceId
})
.then(([ stream ]) => {
// if we are in audio only mode or video was muted before
// changing device, then mute
if (this.isAudioOnly() || videoWasMuted) {
return stream.mute()
.then(() => stream);
}
return stream;
})
.then(stream => {
logger.info(`Switching the local video device to ${cameraDeviceId}.`);
return this.useVideoStream(stream);
})
.catch(error => {
logger.error(`Failed to switch to selected camera:${cameraDeviceId}, error:${error}`);
return APP.store.dispatch(notifyCameraError(error));
});
createLocalTracksF({
devices: [ 'video' ],
cameraDeviceId
})
.then(([ stream ]) => {
// if we are in audio only mode or video was muted before
// changing device, then mute
if (this.isAudioOnly() || videoWasMuted) {
return stream.mute()
.then(() => stream);
}
);
APP.UI.addListener(
UIEvents.AUDIO_DEVICE_CHANGED,
async micDeviceId => {
const audioWasMuted = this.isLocalAudioMuted();
return stream;
})
.then(stream => {
logger.info(`Switching the local video device to ${cameraDeviceId}.`);
// Disable noise suppression if it was enabled on the previous track.
await APP.store.dispatch(setNoiseSuppressionEnabled(false));
return this.useVideoStream(stream);
})
.catch(error => {
logger.error(`Failed to switch to selected camera:${cameraDeviceId}, error:${error}`);
// When the 'default' mic needs to be selected, we need to pass the real device id to gUM instead of
// 'default' in order to get the correct MediaStreamTrack from chrome because of the following bug.
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689.
const isDefaultMicSelected = micDeviceId === 'default';
const selectedDeviceId = isDefaultMicSelected
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: micDeviceId;
logger.info(`Switching audio input device to ${selectedDeviceId}`);
sendAnalytics(createDeviceChangedEvent('audio', 'input'));
createLocalTracksF({
devices: [ 'audio' ],
micDeviceId: selectedDeviceId
})
.then(([ stream ]) => {
// if audio was muted before changing the device, mute
// with the new device
if (audioWasMuted) {
return stream.mute()
.then(() => stream);
}
return stream;
})
.then(async stream => {
await this._maybeApplyAudioMixerEffect(stream);
return this.useAudioStream(stream);
})
.then(() => {
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
if (localAudio && isDefaultMicSelected) {
// workaround for the default device to be shown as selected in the
// settings even when the real device id was passed to gUM because of the
// above mentioned chrome bug.
localAudio._realDeviceId = localAudio.deviceId = 'default';
}
})
.catch(err => {
logger.error(`Failed to switch to selected audio input device ${selectedDeviceId}, error=${err}`);
APP.store.dispatch(notifyMicError(err));
});
}
);
APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, () => {
// Immediately update the UI by having remote videos and the large video update themselves.
const displayedUserId = APP.UI.getLargeVideoID();
if (displayedUserId) {
APP.UI.updateLargeVideo(displayedUserId, true);
}
return APP.store.dispatch(notifyCameraError(error));
});
},
/**
* Handles audio only changes.
*/
onToggleAudioOnly() {
// Immediately update the UI by having remote videos and the large video update themselves.
const displayedUserId = APP.UI.getLargeVideoID();
if (displayedUserId) {
APP.UI.updateLargeVideo(displayedUserId, true);
}
},
/**
* Cleanups local conference on suspend.
*/
@@ -2408,8 +2407,6 @@ export default {
this.deviceChangeListener);
}
APP.UI.removeAllListeners();
let feedbackResultPromise = Promise.resolve({});
if (requestFeedback) {
@@ -2516,37 +2513,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

@@ -456,7 +456,8 @@ PODS:
- react-native-webrtc (118.0.7):
- JitsiWebRTC (~> 118.0.0)
- React-Core
- react-native-webview (13.5.1):
- react-native-webview (13.8.7):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- React-NativeModulesApple (0.72.9):
- React-callinvoker
@@ -882,7 +883,7 @@ SPEC CHECKSUMS:
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
react-native-video: 472b7c366eaaaa0207e546d9a50410df89790bcf
react-native-webrtc: 8b024c7bb9a005d2b9efeba4c691172dbd00268d
react-native-webview: 8baa0f5c6d336d6ba488e942bcadea5bf51f050a
react-native-webview: fb0df3f6551616ac5a7ccb2bc77aca451f58373d
React-NativeModulesApple: 4225ac31a26696c02c54b471052b3e85e74a9a0c
React-perflogger: cb433f318c6667060fc1f62e26eb58d6eb30a627
React-RCTActionSheet: 0af3f8ac067e8a1dde902810b7ad169d0a0ec31e

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",
@@ -316,6 +319,7 @@
"embedMeeting": "Besprechung einbetten",
"enterDisplayName": "Bitte geben Sie hier Ihren Namen ein",
"error": "Fehler",
"errorRoomCreationRestriction": "Sie haben versucht, zu schnell beizutreten, bitte versuchen Sie es gleich noch einmal.",
"gracefulShutdown": "Der Dienst steht momentan wegen Wartungsarbeiten nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",
"grantModeratorDialog": "Möchten Sie wirklich Moderationsrechte an diese Person vergeben?",
"grantModeratorTitle": "Moderationsrechte vergeben",
@@ -560,6 +564,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 +572,8 @@
"sipAudioOnly": "SIP-Adresse (nur Ton)",
"title": "Teilen",
"tooltip": "Freigabe-Link und Einwahlinformationen für dieses Meeting",
"upgradeOptions": "Bitte prüfen Sie Ihre Upgrade-Optionen auf"
"upgradeOptions": "Bitte prüfen Sie Ihre Upgrade-Optionen auf",
"whiteboardError": "Whiteboard konnte nicht geladen werden. Bitte versuchen Sie es später erneut."
},
"inlineDialogFailure": {
"msg": "Es ist ein Fehler aufgetreten.",
@@ -731,6 +737,8 @@
"connectedTwoMembers": "{{first}} und {{second}} nehmen am Meeting teil",
"dataChannelClosed": "Schlechte Videoqualität",
"dataChannelClosedDescription": "Die Steuerungsverbindung (Bridge Channel) wurde unterbrochen, daher ist die Videoqulität auf die schlechteste Stufe limitiert.",
"dataChannelClosedDescriptionWithAudio": "Die Steuerungsverbindung (Bridge Channel) wurde unterbrochen, daher können Video- und Tonprobleme auftreten.",
"dataChannelClosedWithAudio": "Ton- und Videoqualität können beeinträchtigt sein",
"disabledIframe": "Die Einbettung ist nur für Demo-Zwecke vorgesehen. Diese Konferenz wird in {{timeout}} Minuten beendet.",
"disabledIframeSecondary": "Die Einbettung von {{domain}} ist nur für Demo-Zwecke vorgesehen. Diese Konferenz wird in {{timeout}} Minuten beendet. Bitte nutzen Sie <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> für produktive Zwecke!",
"disconnected": "getrennt",
@@ -802,12 +810,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 +949,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 +1016,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 +1032,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 +1188,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 +1371,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 +1429,7 @@
},
"videothumbnail": {
"connectionInfo": "Verbindungsinformationen",
"demote": "Zu Gästen verschieben",
"domute": "Stummschalten",
"domuteOthers": "Alle anderen stummschalten",
"domuteVideo": "Kamera ausschalten",
@@ -1476,7 +1484,12 @@
"chatIndicator": "(Gast)",
"labelTooltip": "Anzahl Gäste: {{count}}",
"notification": {
"demoteDescription": "Hierhin verschoben von {{actor}}, bitte melden Sie sich um teilzunehmen",
"description": "Bitte melden Sie sich um teilzunehmen",
"noMainParticipantsDescription": "Eine Person muss die Konferenz starten. Bitte versuchen Sie es gleich noch einmal.",
"noMainParticipantsTitle": "Diese Konferenz wurde noch nicht gestartet.",
"noVisitorLobby": "Sie können nicht teilnehmen, solange die Lobby für diese Konferenz aktiviert ist.",
"notAllowedPromotion": "Eine Person muss Ihre Anfrage erst erlauben.",
"title": "Sie sind Gast in der Konferenz"
}
},
@@ -1536,6 +1549,7 @@
"whiteboard": {
"accessibilityLabel": {
"heading": "Whiteboard"
}
},
"screenTitle": "Whiteboard"
}
}

View File

@@ -74,7 +74,7 @@
"mainRoom": "Salle principale",
"notifications": {
"joined": "Entrée en salle annexe \"{{name}}\"",
"joinedMainRoom": "Retour à la salle principalem",
"joinedMainRoom": "Retour à la salle principale",
"joinedTitle": "Salles annexes"
},
"showParticipantList": "Afficher la liste des participants",
@@ -82,7 +82,7 @@
},
"calendarSync": {
"addMeetingURL": "Ajouter un lien de conférence",
"confirmAddLink": "Voulez-vous ajouter un lien Jitsi à cet événement ?",
"confirmAddLink": "Voulez-vous ajouter un lien Jitsi à cet événement?",
"error": {
"appConfiguration": "L'intégration du calendrier n'est pas correctement configurée.",
"generic": "Une erreur s'est produite. Veuillez vérifier les paramètres de votre calendrier ou tenter de l'actualiser.",
@@ -138,7 +138,7 @@
},
"chromeExtensionBanner": {
"buttonText": "Installer l'extension Chrome",
"buttonTextEdge": "Installer lextension Edge",
"buttonTextEdge": "Installer l'extension Edge",
"close": "Fermer",
"dontShowAgain": "Ne plus m'afficher ceci",
"installExtensionText": "Installer l'extension pour l'intégration de Google Calendar et Office 365"
@@ -424,7 +424,7 @@
"shareAudioAltText": "Pour partager le contenu voulu, naviguer vers \"Onglet du Navigateur\", sélectionner le contenu, activer le bouton \"partager laudio\" et enfin cliquer sur le bouton \"partager\"",
"shareAudioTitle": "Comment partager le son",
"shareAudioWarningD1": "vous devez cesser le partage d'écran avant de partager votre son.",
"shareAudioWarningD2": "viys devez partager votre écran à nouveau et cocher l'ootion \"Partager l'audio\".",
"shareAudioWarningD2": "vous devez partager votre écran à nouveau et cocher l'option \"Partager l'audio\".",
"shareAudioWarningH1": "Si vous voulez partager uniquement de l'audio:",
"shareAudioWarningTitle": "Vous devez cesser de partager l'écran avant de partager l'audio",
"shareMediaWarningGenericH2": "Si vous voulez partager votre écran et l'audio",

File diff suppressed because it is too large Load Diff

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"
@@ -318,6 +319,7 @@
"embedMeeting": "Iegult sapulci",
"enterDisplayName": "Ievadiet savu vārdu",
"error": "Kļūda",
"errorRoomCreationRestriction": "Jūs mēģinājāt pievienoties pārāk ātri. Lūdzu, atgriezieties vēlāk.",
"gracefulShutdown": "Mūsu serviss pašlaik nedarbojas apkopes dēļ. Lūdzu, pamēģiniet vēlreiz vēlāk.",
"grantModeratorDialog": "Vai tiešām vēlaties piešķirt moderatora tiesības dalībniekam {{participantName}}?",
"grantModeratorTitle": "Piešķirt moderatora tiesības",
@@ -353,28 +355,28 @@
"micPermissionDeniedError": "Nav piekļuves mikrofonam. Jūs varat piedalīties sapulcē, bet citi jūs nedzirdēs. Lai to novērstu, izmantojiet kameras ikonu pārlūkprogrammas adrešu joslā.",
"micTimeoutError": "Nevarēja palaist audio avotu. Iestājās noildze!",
"micUnknownError": "Nevar izmantot mikrofonu nezināma iemesla dēļ.",
"moderationAudioLabel": "Ļaujiet dalībniekiem ieslēgt savu mikrofonu",
"moderationVideoLabel": "Ļaujiet dalībniekiem ieslēgt savu kameru",
"muteEveryoneDialog": "Dalībnieki jebkurā laikā var ieslēgt savu mikrofonu.",
"muteEveryoneDialogModerationOn": "Dalībnieki jebkurā laikā var nosūtīt pieprasījumu runāt.",
"muteEveryoneElseDialog": "Kad skaņa būs izslēgta, jūs nevarēsit to ieslēgt atpakaļ, taču dalībnieki jebkurā laikā to varēs izdarīt paši.",
"moderationAudioLabel": "Atļaut dalībniekiem ieslēgt savu mikrofonu",
"moderationVideoLabel": "Atļaut dalībniekiem ieslēgt savu kameru",
"muteEveryoneDialog": "Dalībnieki paši var ieslēgt savu mikrofonu.",
"muteEveryoneDialogModerationOn": "Dalībnieki var nosūtīt pieprasījumu ieslēgt savu mikrofonu.",
"muteEveryoneElseDialog": "Kad skaņa būs izslēgta, jūs nevarēsiet to ieslēgt atpakaļ, taču dalībnieki to varēs izdarīt paši.",
"muteEveryoneElseTitle": "Vai izslēgt skaņu visiem, izņemot {{whom}}?",
"muteEveryoneElsesVideoDialog": "Kad video būs izslēgts, jūs nevarēsit to ieslēgt atpakaļ, taču dalībnieki jebkurā laikā to varēs izdarīt paši.",
"muteEveryoneElsesVideoDialog": "Kad video būs izslēgts, jūs nevarēsiet to ieslēgt atpakaļ, taču dalībnieki to varēs izdarīt paši.",
"muteEveryoneElsesVideoTitle": "Vai izslēgt video visiem, izņemot {{whom}}?",
"muteEveryoneSelf": "jūs",
"muteEveryoneStartMuted": "No šī brīža visi jauni dalībnieki pieslēdzas ar izslēgt skaņu",
"muteEveryoneTitle": "Vai izslēgt skaņu visiem?",
"muteEveryonesVideoDialog": "Dalībnieki var jebkurā laikā ieslēgt savu video.",
"muteEveryonesVideoDialogModerationOn": "Dalībnieki jebkurā laikā var nosūtīt pieprasījumu ieslēgt viņu video.",
"muteEveryonesVideoDialog": "Dalībnieki var ieslēgt savu video.",
"muteEveryonesVideoDialogModerationOn": "Dalībnieki var nosūtīt pieprasījumu ieslēgt viņu video.",
"muteEveryonesVideoDialogOk": "Atspējot",
"muteEveryonesVideoTitle": "Vai apturēt ikviena video?",
"muteParticipantBody": "Jūs nevarat viņiem ieslēgt skaņu, bet viņi paši to var izdarīt jebkurā laikā.",
"muteParticipantBody": "Jūs nevariet viņiem ieslēgt skaņu, bet viņi paši to var izdarīt jebkurā laikā.",
"muteParticipantButton": "Izslēgt skaņu",
"muteParticipantsVideoBody": "Jūs nevarēsiet atkal ieslēgt kameru, taču viņi var to jebkurā laikā atkal ieslēgt.",
"muteParticipantsVideoBodyModerationOn": "Jūs nevarēsiet atkal ieslēgt kameru, un viņi arī nevarēs.",
"muteParticipantsVideoBody": "Jūs nevarēsiet kameru ieslēgt atpakaļ, taču viņi paši to varēs izdarīt jebkurā laikā.",
"muteParticipantsVideoBodyModerationOn": "Ne Jūs, ne dalībnieki nevarēsiet ieslēgt kameru atpakaļ.",
"muteParticipantsVideoButton": "Pārtraukt video",
"muteParticipantsVideoDialog": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Jūs nevarēsiet atkal ieslēgt kameru, taču viņi var to jebkurā laikā atkal ieslēgt.",
"muteParticipantsVideoDialogModerationOn": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? s nevarēsiet atkal ieslēgt kameru, un viņi arī nevarēs.",
"muteParticipantsVideoDialog": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Jūs nevarēsiet to ieslēgt atpakaļ, taču dalībnieks pats to varēs izdarīt jebkurā laikā.",
"muteParticipantsVideoDialogModerationOn": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Ne Jūs, ne dalībnieks nevarēsiet to ieslēgt atpakaļ.",
"muteParticipantsVideoTitle": "Vai izslēgt šī dalībnieka video?",
"noDropboxToken": "Nav derīga Dropbox tokena",
"password": "Parole",
@@ -733,8 +735,10 @@
"connectedOneMember": "{{name}} ir pievienojies sapulcei",
"connectedThreePlusMembers": "{{name}} un {{count}} citi ir pievienojušies sapulcei",
"connectedTwoMembers": "{{first}} un {{second}} ir pievienojušies sapulcei",
"dataChannelClosed": "Video kvalitāte ir traucēta",
"dataChannelClosedDescription": "Savienojuma kanāls ir atvienots, un tādējādi video kvalitāte ir ierobežota līdz zemākajam iestatījumam.",
"dataChannelClosed": "Video kvalitāte var būt traucēta",
"dataChannelClosedDescription": "Savienojuma kanāls nedarbojas, tāpēc video kvalitāte var būt ierobežota līdz zemākajam iestatījumam.",
"dataChannelClosedDescriptionWithAudio": "Savienojuma kanāls nedarbojas, tāpēc var rasties audio un video traucējumi.",
"dataChannelClosedWithAudio": "Audio un video kvalitāte var būt traucēta",
"disabledIframe": "Iegulšana ir paredzēta tikai demonstrācijas nolūkiem, tāpēc šis zvans tiks atvienots pēc {{timeout}} minūtēm.",
"disabledIframeSecondary": "{{domain}} iegulšana ir paredzēta tikai demonstrācijas nolūkiem, tāpēc šis zvans tiks atvienots pēc {{timeout}} minūtēm. Lūdzu, izmantojiet <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi kā Pakalpojums</a> produkcijas iegulšanai!",
"disconnected": "savienojums pārtraukts",
@@ -832,7 +836,7 @@
"breakoutRooms": "Grupu istabas",
"invite": "Uzaicināt",
"moreModerationActions": "Vairāk moderēšanas iespēju",
"moreModerationControls": "Vairāk moderēšanas kontroļu",
"moreModerationControls": "Vairāk moderēšanas iespēju",
"moreParticipantOptions": "Vairāk dalībnieku iespēju",
"mute": "Apklusināt",
"muteAll": "Apklusināt visus",
@@ -1482,6 +1486,10 @@
"notification": {
"demoteDescription": "{{actor}} pārveidoja par apmeklētāju, paceliet roku, lai piedalītos",
"description": "Paceliet roku, lai piedalītos",
"noMainParticipantsDescription": "Dalībniekam ir jāsāk sapulce. Lūdzu, pēc brīža mēģiniet vēlreiz.",
"noMainParticipantsTitle": "Šī sapulce vēl nav sākusies.",
"noVisitorLobby": "Jūs nevarat pievienoties, kamēr sapulcei ir iespējots vestibils.",
"notAllowedPromotion": "Dalībniekam vispirms ir jāatļauj jūsu pieprasījums.",
"title": "Jūs esat sapulces apmeklētājs"
}
},

View File

@@ -822,8 +822,8 @@
},
"participantsPane": {
"actions": {
"admit": "Признать",
"admitAll": "Признать все",
"admit": "Разрешить",
"admitAll": "Разрешить всем",
"allow": "Разрешить",
"allowVideo": "Разрешить видео",
"askUnmute": "Попросить разрешение включить микрофон",
@@ -837,7 +837,7 @@
"mute": "Выключить звук",
"muteAll": "Выключить звук у всех",
"muteEveryoneElse": "Выключить микрофон у остальных",
"reject": "Отказать",
"reject": "Отклонить",
"stopEveryonesVideo": "Выключить у всех камеру",
"stopVideo": "Остановить видео",
"unblockEveryoneMicCamera": "Разблокировать у всех микрофон и камеру",

View File

@@ -128,6 +128,7 @@
"privateNotice": "{{recipient}} için özel mesaj",
"sendButton": "Gönder",
"smileysPanel": "Emoji paneli",
"systemDisplayName": "Sistem",
"tabs": {
"chat": "Sohbet",
"polls": "Anket"
@@ -735,6 +736,8 @@
"connectedTwoMembers": "{{first}} ve {{second}} toplantıya katıldı",
"dataChannelClosed": "Video kalitesi bozuldu",
"dataChannelClosedDescription": "Köprü kanalının bağlantısı kesildi ve bu nedenle video kalitesi en düşük ayarla sınırlandı.",
"dataChannelClosedDescriptionWithAudio": "Köprü kanalı kapalı olduğu için ses ve video kesintileri yaşanabilir.",
"dataChannelClosedWithAudio": "Ses ve video kalitesi etkilenebilir.",
"disabledIframe": "Yerleştirme yalnızca demo amaçlı olduğundan bu çağrının bağlantısı {{timeout}} dakika içinde kesilecek.",
"disabledIframeSecondary": "{{domain}} alanının yerleştirilmesi yalnızca demo amaçlı olduğundan bu çağrının bağlantısı {{timeout}} dakika içinde kesilecektir. Üretim yerleştirme için lütfen <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Hizmet olarak Jitsi</a>'yi kullanın!",
"disconnected": "bağlantı kesildi",

View File

@@ -319,6 +319,7 @@
"embedMeeting": "Embed meeting",
"enterDisplayName": "Enter your name",
"error": "Error",
"errorRoomCreationRestriction": "You tried to join too quickly, please come back in a bit.",
"gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
"grantModeratorDialog": "Are you sure you want to grant moderator rights to {{participantName}}?",
"grantModeratorTitle": "Grant moderator rights",
@@ -734,8 +735,10 @@
"connectedOneMember": "{{name}} joined the meeting",
"connectedThreePlusMembers": "{{name}} and many others joined the meeting",
"connectedTwoMembers": "{{first}} and {{second}} joined the meeting",
"dataChannelClosed": "Video quality impaired",
"dataChannelClosedDescription": "The bridge channel has been disconnected and thus video quality is limited to its lowest setting.",
"dataChannelClosed": "Video quality may be impaired",
"dataChannelClosedDescription": "The bridge channel is down and thus video quality may be limited to its lowest setting.",
"dataChannelClosedDescriptionWithAudio": "The bridge channel is down and thus disruptions to audio and video may occur.",
"dataChannelClosedWithAudio": "Audio and video quality may be impaired",
"disabledIframe": "Embedding is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes.",
"disabledIframeSecondary": "Embedding {{domain}} is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes. Please use <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> for production embedding!",
"disconnected": "disconnected",
@@ -1483,6 +1486,10 @@
"notification": {
"demoteDescription": "Sent here by {{actor}}, raise your hand to participate",
"description": "To participate raise your hand",
"noMainParticipantsDescription": "A participant needs to start the meeting. Please try again in a bit.",
"noMainParticipantsTitle": "This meeting hasnt started yet.",
"noVisitorLobby": "You cannot join while there is a lobby enabled for the meeting.",
"notAllowedPromotion": "A participant needs to allow your request first.",
"title": "You are a visitor in the meeting"
}
},

View File

@@ -0,0 +1,10 @@
Jitsi Meet позволяет вам оставаться на связи со всеми вашими командами, будь то семья, друзья или коллеги. Мгновенные видеоконференции, эффективно адаптируемые к вашим нуждам.
* Неограниченные пользователи: нет искусственных ограничений на количество пользователей или участников конференции. Мощность сервера и пропускная способность являются единственными ограничивающими факторами.
* Учетная запись не требуется.
* Закрытые комнаты: управляйте доступом к вашим конференциям с помощью пароля.
* Зашифровано по умолчанию.
* Высокое качество: аудио и видео доставляются с чёткостью и возможностями кодеков Opus и VP8.
* Работает в веб браузере: вашим друзьям не требуются загрузка чтобы присоединиться к разговору. Jitsi Meet работает непосредственно в их браузерах. Чтобы начать просто поделитесь своей ссылкой на конференцию с другими.
* 100% открытый исходный код: поддерживается удивительными сообществами со всего мира. И вашими друзьями из 8x8.
* Приглашение по красивому адресу ссылки: вы можете встретиться с легко запоминаемым https://MySite.com/OurConf по своему выбору вместо случайных цифр.

View File

@@ -0,0 +1 @@
Защищённые, простые и масштабируемые видеоконференции с качественным видео

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

80
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/v1812.0.0+2eddb859/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1823.0.0+ec98b020/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -101,7 +101,7 @@
"react-native-video": "6.0.0-alpha.11",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "118.0.7",
"react-native-webview": "13.5.1",
"react-native-webview": "13.8.7",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
"react-textarea-autosize": "8.3.0",
@@ -10875,9 +10875,9 @@
"integrity": "sha512-nPer0rjtzdZ7csVIu233P2cUm/ks/4aVSI+5KUkYrYpgA7ujgC3p6J7FtFU+AIMWwnwYQOB/yeiOITxFeYIXiw=="
},
"node_modules/follow-redirects": {
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true,
"funding": [
{
@@ -10934,9 +10934,9 @@
}
},
"node_modules/fs-monkey": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz",
"integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz",
"integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==",
"dev": true
},
"node_modules/fs.realpath": {
@@ -12866,8 +12866,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1812.0.0+2eddb859/lib-jitsi-meet.tgz",
"integrity": "sha512-svKmkJzs6BNrOgTHST7aAxpGHLAVAlxuX7VQrGVL8HUpU6FDrqHBRSMQXX0937jir6OpIhSCjoFK6maZ9xfOZg==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1823.0.0+ec98b020/lib-jitsi-meet.tgz",
"integrity": "sha512-jbROcnR1IWzp8I7quwj9uA8LMDE99tS6Wkxxf4ESAsYpc5wjbfJdqGaXKiIreWvlqM5NJTd/jMjw+DrZzbif1Q==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -13492,12 +13492,12 @@
}
},
"node_modules/memfs": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz",
"integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==",
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
"integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
"dev": true,
"dependencies": {
"fs-monkey": "1.0.3"
"fs-monkey": "^1.0.4"
},
"engines": {
"node": ">= 4.0.0"
@@ -16938,9 +16938,9 @@
}
},
"node_modules/react-native-webview": {
"version": "13.5.1",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.5.1.tgz",
"integrity": "sha512-SZQJqFUMxYYj1xYWy1Z48WcHpqOGvbXKS5R1cnaLQY/JxefS+1NOVMqWxy1Zwmc128vqtRklES8l9Jus8tKSLg==",
"version": "13.8.7",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.8.7.tgz",
"integrity": "sha512-r8y+qlyh2+mtuPhOvgPB9Ccn2itDXacvaVFuB61AYTfisz1YhJ57iFEE8j32DfDpvl6NhaaFIzzHsumwJk92bQ==",
"dependencies": {
"escape-string-regexp": "2.0.0",
"invariant": "2.2.4"
@@ -19623,13 +19623,13 @@
}
},
"node_modules/webpack-dev-middleware": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz",
"integrity": "sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg==",
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz",
"integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==",
"dev": true,
"dependencies": {
"colorette": "^2.0.10",
"memfs": "^3.4.1",
"memfs": "^3.4.3",
"mime-types": "^2.1.31",
"range-parser": "^1.2.1",
"schema-utils": "^4.0.0"
@@ -27947,9 +27947,9 @@
"integrity": "sha512-nPer0rjtzdZ7csVIu233P2cUm/ks/4aVSI+5KUkYrYpgA7ujgC3p6J7FtFU+AIMWwnwYQOB/yeiOITxFeYIXiw=="
},
"follow-redirects": {
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true
},
"for-each": {
@@ -27983,9 +27983,9 @@
}
},
"fs-monkey": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz",
"integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz",
"integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==",
"dev": true
},
"fs.realpath": {
@@ -29374,8 +29374,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1812.0.0+2eddb859/lib-jitsi-meet.tgz",
"integrity": "sha512-svKmkJzs6BNrOgTHST7aAxpGHLAVAlxuX7VQrGVL8HUpU6FDrqHBRSMQXX0937jir6OpIhSCjoFK6maZ9xfOZg==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1823.0.0+ec98b020/lib-jitsi-meet.tgz",
"integrity": "sha512-jbROcnR1IWzp8I7quwj9uA8LMDE99tS6Wkxxf4ESAsYpc5wjbfJdqGaXKiIreWvlqM5NJTd/jMjw+DrZzbif1Q==",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
@@ -29867,12 +29867,12 @@
"dev": true
},
"memfs": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz",
"integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==",
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
"integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
"dev": true,
"requires": {
"fs-monkey": "1.0.3"
"fs-monkey": "^1.0.4"
}
},
"memoize-one": {
@@ -32329,9 +32329,9 @@
}
},
"react-native-webview": {
"version": "13.5.1",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.5.1.tgz",
"integrity": "sha512-SZQJqFUMxYYj1xYWy1Z48WcHpqOGvbXKS5R1cnaLQY/JxefS+1NOVMqWxy1Zwmc128vqtRklES8l9Jus8tKSLg==",
"version": "13.8.7",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.8.7.tgz",
"integrity": "sha512-r8y+qlyh2+mtuPhOvgPB9Ccn2itDXacvaVFuB61AYTfisz1YhJ57iFEE8j32DfDpvl6NhaaFIzzHsumwJk92bQ==",
"requires": {
"escape-string-regexp": "2.0.0",
"invariant": "2.2.4"
@@ -34260,13 +34260,13 @@
}
},
"webpack-dev-middleware": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz",
"integrity": "sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg==",
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz",
"integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==",
"dev": true,
"requires": {
"colorette": "^2.0.10",
"memfs": "^3.4.1",
"memfs": "^3.4.3",
"mime-types": "^2.1.31",
"range-parser": "^1.2.1",
"schema-utils": "^4.0.0"

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/v1812.0.0+2eddb859/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1823.0.0+ec98b020/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -107,7 +107,7 @@
"react-native-video": "6.0.0-alpha.11",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "118.0.7",
"react-native-webview": "13.5.1",
"react-native-webview": "13.8.7",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
"react-textarea-autosize": "8.3.0",

View File

@@ -95,7 +95,7 @@ https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native
## Using JWT tokens
- If you are planning to use tokens or another domain you can do that by updating the following props, as shown below.
- If you are planning to use tokens or another domain, you can do that by updating the following props, as shown below.
- For example:
```javascript
<JitsiMeeting
@@ -105,7 +105,7 @@ https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native
```
## Using custom overflow menu buttons
- If you are planning to use tokens or another domain you can do that by updating the following props, as shown below.
- If you are planning to add custom overflow menu buttons, you can do that by updating the ```config``` prop, as shown below.
- For example:
```javascript
<JitsiMeeting

View File

@@ -331,15 +331,17 @@ export function createNetworkInfoEvent({ isOnline, networkType, details }:
/**
* Creates a "not allowed error" event.
*
* @param {string} type - The type of the error.
* @param {string} reason - The reason for the error.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createNotAllowedErrorEvent(reason: string) {
export function createNotAllowedErrorEvent(type: string, reason: string) {
return {
action: 'not.allowed.error',
attributes: {
reason
reason,
type
}
};
}

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

@@ -1,5 +1,3 @@
// @ts-expect-error
import UIEvents from '../../../../service/UI/UIEvents';
import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { IReduxState, IStore } from '../../app/types';
@@ -1066,41 +1064,41 @@ export function redirect(vnode: string, focusJid: string, username: string) {
// conference without tracks.
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
if (typeof APP !== 'undefined') {
if (!vnode) {
const state = getState();
const { enableMediaOnPromote = {} } = state['features/base/config'].visitors ?? {};
const { audio = false, video = false } = enableMediaOnPromote;
if (!vnode) {
const state = getState();
const { enableMediaOnPromote = {} } = state['features/base/config'].visitors ?? {};
const { audio = false, video = false } = enableMediaOnPromote;
if (audio) {
const { available, muted, unmuteBlocked } = state['features/base/media'].audio;
const { startSilent } = state['features/base/config'];
if (audio) {
const { available, muted, unmuteBlocked } = state['features/base/media'].audio;
const { startSilent } = state['features/base/config'];
// do not unmute the user if he was muted before (on the prejoin, the config
// or URL param, etc.)
if (!unmuteBlocked && !muted && !startSilent && available) {
dispatch(setAudioMuted(false, true));
// do not unmute the user if he was muted before (on the prejoin, the config
// or URL param, etc.)
if (!unmuteBlocked && !muted && !startSilent && available) {
dispatch(setAudioMuted(false, true));
// // FIXME: The old conference logic still relies on this event being emitted.
typeof APP === 'undefined' || APP.UI.emitEvent(UIEvents.AUDIO_MUTED, false);
}
}
if (video) {
const { muted, unmuteBlocked } = state['features/base/media'].video;
// do not unmute the user if he was muted before (on the prejoin, the config, URL param or
// audo only, etc)
if (!unmuteBlocked && !muted && hasAvailableDevices(state, 'videoInput')) {
dispatch(setVideoMuted(false, VIDEO_MUTISM_AUTHORITY.USER, true));
// // FIXME: The old conference logic still relies on this event being emitted.
typeof APP === 'undefined' || APP.UI.emitEvent(UIEvents.VIDEO_MUTED, false);
}
// // 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

@@ -165,6 +165,10 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
const result = next(action);
const { enableForcedReload } = getState()['features/base/config'];
if (LocalRecordingManager.isRecordingLocally()) {
dispatch(stopLocalVideoRecording());
}
// Handle specific failure reasons.
switch (error.name) {
case JitsiConferenceErrors.CONFERENCE_RESTARTED: {
@@ -220,9 +224,30 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
break;
}
case JitsiConferenceErrors.NOT_ALLOWED_ERROR: {
const [ msg ] = error.params;
const [ type, msg ] = error.params;
let descriptionKey;
let titleKey = 'dialog.tokenAuthFailed';
if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.NO_MAIN_PARTICIPANTS) {
descriptionKey = 'visitors.notification.noMainParticipantsDescription';
titleKey = 'visitors.notification.noMainParticipantsTitle';
} else if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.NO_VISITORS_LOBBY) {
descriptionKey = 'visitors.notification.noVisitorLobby';
} else if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.PROMOTION_NOT_ALLOWED) {
descriptionKey = 'visitors.notification.notAllowedPromotion';
} else if (type === JitsiConferenceErrors.AUTH_ERROR_TYPES.ROOM_CREATION_RESTRICTION) {
descriptionKey = 'dialog.errorRoomCreationRestriction';
}
APP.store.dispatch(showErrorNotification({
descriptionKey,
hideErrorSupportLink: true,
titleKey
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
sendAnalytics(createNotAllowedErrorEvent(type, msg));
sendAnalytics(createNotAllowedErrorEvent(msg));
break;
}
case JitsiConferenceErrors.OFFER_ANSWER_FAILED:

View File

@@ -18,6 +18,8 @@ import {
CONFERENCE_TIMESTAMP_CHANGED,
CONFERENCE_WILL_JOIN,
CONFERENCE_WILL_LEAVE,
DATA_CHANNEL_CLOSED,
DATA_CHANNEL_OPENED,
LOCK_STATE_CHANGED,
P2P_STATUS_CHANGED,
SET_ASSUMED_BANDWIDTH_BPS,
@@ -35,6 +37,7 @@ import { isRoomValid } from './functions';
const DEFAULT_STATE = {
assumedBandwidthBps: undefined,
conference: undefined,
dataChannelOpen: undefined,
e2eeSupported: undefined,
joining: undefined,
leaving: undefined,
@@ -146,6 +149,7 @@ export interface IConferenceState {
authRequired?: IJitsiConference;
conference?: IJitsiConference;
conferenceTimestamp?: number;
dataChannelOpen?: boolean;
e2eeSupported?: boolean;
error?: Error;
followMeEnabled?: boolean;
@@ -219,6 +223,12 @@ ReducerRegistry.register<IConferenceState>('features/base/conference',
case CONNECTION_WILL_CONNECT:
return set(state, 'authRequired', undefined);
case DATA_CHANNEL_CLOSED:
return set(state, 'dataChannelOpen', false);
case DATA_CHANNEL_OPENED:
return set(state, 'dataChannelOpen', true);
case LOCK_STATE_CHANGED:
return _lockStateChanged(state, action);

View File

@@ -81,6 +81,7 @@ export default [
'debug',
'debugAudioLevels',
'deeplinking.disabled',
'deeplinking.desktop.enabled',
'defaultLocalDisplayName',
'defaultRemoteDisplayName',
'deploymentUrls',

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

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

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

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

@@ -9,6 +9,7 @@ import {
close as closeParticipantsPane,
open as openParticipantsPane
} from '../../../participants-pane/actions.web';
import { closeOverflowMenuIfOpen } from '../../../toolbox/actions.web';
import { isParticipantsPaneEnabled } from '../../functions';
import ParticipantsCounter from './ParticipantsCounter';
@@ -61,6 +62,7 @@ class ParticipantsPaneButton extends AbstractButton<IProps> {
_handleClick() {
const { dispatch, _isOpen } = this.props;
dispatch(closeOverflowMenuIfOpen());
if (_isOpen) {
dispatch(closeParticipantsPane());
} else {

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,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

@@ -63,7 +63,8 @@ module:hook('muc-occupant-pre-join', function (event)
if host == local_domain then
if room._main_room_lobby_enabled then
origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Visitors not allowed while lobby is on!'));
origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Visitors not allowed while lobby is on!')
:tag('no-visitors-lobby', { xmlns = 'jitsi:visitors' }));
return true;
else
occupant.role = 'visitor';

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,18 @@ 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')
:tag('promotion-not-allowed', { xmlns = 'jitsi:visitors' }));
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')
:tag('no-main-participants', { xmlns = 'jitsi:visitors' }));
end
end
end, 7); -- after muc_meeting_id, the logic for not joining before jicofo

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'
};

View File

@@ -1,15 +0,0 @@
<html>
<head>
<!--#include virtual="/base.html" -->
<link rel="stylesheet" href="css/all.css"/>
<!--#include virtual="/title.html" -->
</head>
<body>
<div class="redirectPageMessage">
Sorry! You are not allowed to be here :(
<div class="forbidden-msg">
<p>You might be missing the JWT or using an incompatible one.</p>
</div>
</div>
</body>
</html>