mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-03-24 20:40:23 +00:00
Compare commits
37 Commits
saghul-pat
...
android-sd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8648a5a998 | ||
|
|
c83b30558d | ||
|
|
057dfc8194 | ||
|
|
5eae0b58e9 | ||
|
|
1538107e93 | ||
|
|
cd1862a2d3 | ||
|
|
147a076f5d | ||
|
|
9bdaea4069 | ||
|
|
547d1547bb | ||
|
|
39853e048b | ||
|
|
8b454b5196 | ||
|
|
e2788e0fb2 | ||
|
|
b1d1599a1c | ||
|
|
cef98f457f | ||
|
|
6b0e8aab11 | ||
|
|
086889ed70 | ||
|
|
516e5af118 | ||
|
|
afe1b4b0f9 | ||
|
|
8790ad6013 | ||
|
|
8bbc04d4db | ||
|
|
4fda428be1 | ||
|
|
f972ebfe9e | ||
|
|
3dfcc8d80e | ||
|
|
33ebd241a9 | ||
|
|
cb6fbb0f03 | ||
|
|
0077ee29c5 | ||
|
|
551db30cc7 | ||
|
|
1bd930a3cb | ||
|
|
e0563a743f | ||
|
|
a4ab7ea95f | ||
|
|
b50d6e43d0 | ||
|
|
f9fcb46036 | ||
|
|
4824c8714a | ||
|
|
be56521267 | ||
|
|
b7eda8df7a | ||
|
|
d632b6e13e | ||
|
|
dff7d661ca |
10
.github/ISSUE_TEMPLATE/2-help.md
vendored
10
.github/ISSUE_TEMPLATE/2-help.md
vendored
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Need help with Jitsi Meet?
|
||||
about: Please ask it in our community at https://community.jitsi.org
|
||||
|
||||
---
|
||||
|
||||
If you have a question about Jitsi Meet that is not a bug report or feature
|
||||
request, please post it in https://community.jitsi.org
|
||||
|
||||
Questions posted to this repository will be closed.
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Need help with Jitsi Meet?
|
||||
url: https://community.jitsi.org
|
||||
about: Please ask it in our community.
|
||||
20
README.md
20
README.md
@@ -1,6 +1,6 @@
|
||||
# Jitsi Meet - Secure, Simple and Scalable Video Conferences
|
||||
|
||||
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, [secure](#security) and scalable video conferences. Jitsi Meet in action can be seen at [here at the session #482 of the VoIP Users Conference](http://youtu.be/7vFUVClsNh0).
|
||||
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, [secure](https://jitsi.org/security) and scalable video conferences. Jitsi Meet in action can be seen at [here at the session #482 of the VoIP Users Conference](http://youtu.be/7vFUVClsNh0).
|
||||
|
||||
The Jitsi Meet client runs in your browser, without installing anything else on your computer. You can try it out at https://meet.jit.si.
|
||||
|
||||
@@ -61,25 +61,13 @@ Jitsi Meet provides a very flexible way of embedding in external applications by
|
||||
|
||||
## Security
|
||||
|
||||
WebRTC does not (yet) provide a way of conducting multi-party conversations with end-to-end encryption.
|
||||
Unless you consistently compare DTLS fingerprints with your peers vocally, the same goes for one-to-one calls.
|
||||
As a result, your stream is encrypted on the network but decrypted on the machine that hosts the bridge when using Jitsi Meet.
|
||||
The security section here was starting to feel a bit too succinct for the complexity of the topic, so we created a post that covers the topic much more broadly here: https://jitsi.org/security
|
||||
|
||||
The Jitsi Meet architecture allows you to deploy your own version, including
|
||||
all server components. In that case, your security guarantees will be roughly
|
||||
equivalent to a direct one-to-one WebRTC call. This is the uniqueness of
|
||||
Jitsi Meet in terms of security.
|
||||
|
||||
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
|
||||
at [8x8](https://8x8.com).
|
||||
The section on end-to-end encryption in that document is likely going to be one of the key points of interest: https://jitsi.org/security/#e2ee
|
||||
|
||||
## Security issues
|
||||
|
||||
We take security very seriously and develop all Jitsi projects to be secure and safe.
|
||||
|
||||
If you find (or simply suspect) a security issue in any of the Jitsi projects, please send us an email to security@jitsi.org.
|
||||
|
||||
**We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.**
|
||||
For information on reporting security vulnerabilities in Jitsi Meet, see [SECURITY.md](./SECURITY.md).
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
---
|
||||
name: Security issues
|
||||
about: Please email security@jitsi.org
|
||||
# Security
|
||||
|
||||
---
|
||||
## Reporting security issuess
|
||||
|
||||
We take security very seriously and develop all Jitsi projects to be secure and safe.
|
||||
|
||||
If you find (or simply suspect) a security issue in any of the Jitsi projects, please send us an email to security@jitsi.org.
|
||||
|
||||
We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.
|
||||
**We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.**
|
||||
@@ -20,5 +20,5 @@
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
appVersion=20.2.0
|
||||
appVersion=20.3.0
|
||||
sdkVersion=2.8.0
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
|
||||
@@ -40,6 +42,8 @@ class AmplitudeModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public static final String NAME = "Amplitude";
|
||||
public static final String JITSI_PREFERENCES = "jitsi-preferences";
|
||||
public static final String AMPLITUDE_DEVICE_ID_KEY = "amplitudeDeviceId";
|
||||
|
||||
public AmplitudeModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
@@ -58,8 +62,8 @@ class AmplitudeModule
|
||||
Amplitude.getInstance(instanceName).initialize(getCurrentActivity(), apiKey);
|
||||
|
||||
// Set the device ID to something consistent.
|
||||
String android_id
|
||||
= Settings.Secure.getString(getReactApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
SharedPreferences sharedPreferences = getReactApplicationContext().getSharedPreferences(JITSI_PREFERENCES, Context.MODE_PRIVATE);
|
||||
String android_id = sharedPreferences.getString(AMPLITUDE_DEVICE_ID_KEY, "");
|
||||
if (!TextUtils.isEmpty(android_id)) {
|
||||
Amplitude.getInstance(instanceName).setDeviceId(android_id);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
createDeviceChangedEvent,
|
||||
createStartSilentEvent,
|
||||
createScreenSharingEvent,
|
||||
createStreamSwitchDelayEvent,
|
||||
createTrackMutedEvent,
|
||||
sendAnalytics
|
||||
} from './react/features/analytics';
|
||||
@@ -113,6 +112,7 @@ import {
|
||||
import { getJitsiMeetGlobalNS } from './react/features/base/util';
|
||||
import { showDesktopPicker } from './react/features/desktop-picker';
|
||||
import { appendSuffix } from './react/features/display-name';
|
||||
import { setE2EEKey } from './react/features/e2ee';
|
||||
import {
|
||||
maybeOpenFeedbackDialog,
|
||||
submitFeedback
|
||||
@@ -470,11 +470,6 @@ export default {
|
||||
*/
|
||||
localVideo: null,
|
||||
|
||||
/**
|
||||
* The key used for End-To-End Encryption.
|
||||
*/
|
||||
e2eeKey: undefined,
|
||||
|
||||
/**
|
||||
* Creates local media tracks and connects to a room. Will show error
|
||||
* dialogs in case accessing the local microphone and/or camera failed. Will
|
||||
@@ -1202,11 +1197,14 @@ export default {
|
||||
items[key] = param[1];
|
||||
}
|
||||
|
||||
this.e2eeKey = items.e2eekey;
|
||||
if (typeof items.e2eekey !== undefined) {
|
||||
APP.store.dispatch(setE2EEKey(items.e2eekey));
|
||||
|
||||
logger.debug(`New E2EE key: ${this.e2eeKey}`);
|
||||
// Clean URL in browser history.
|
||||
const cleanUrl = window.location.href.split('#')[0];
|
||||
|
||||
this._room.setE2EEKey(this.e2eeKey);
|
||||
history.replaceState(history.state, document.title, cleanUrl);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1818,7 +1816,7 @@ export default {
|
||||
const desktopVideoStream = streams.find(stream => stream.getType() === MEDIA_TYPE.VIDEO);
|
||||
|
||||
if (desktopVideoStream) {
|
||||
this.useVideoStream(desktopVideoStream);
|
||||
await this.useVideoStream(desktopVideoStream);
|
||||
}
|
||||
|
||||
this._desktopAudioStream = streams.find(stream => stream.getType() === MEDIA_TYPE.AUDIO);
|
||||
@@ -2264,18 +2262,6 @@ export default {
|
||||
});
|
||||
});
|
||||
|
||||
/* eslint-disable max-params */
|
||||
APP.UI.addListener(
|
||||
UIEvents.RESOLUTION_CHANGED,
|
||||
(id, oldResolution, newResolution, delay) => {
|
||||
sendAnalytics(createStreamSwitchDelayEvent(
|
||||
{
|
||||
'old_resolution': oldResolution,
|
||||
'new_resolution': newResolution,
|
||||
value: delay
|
||||
}));
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.AUTH_CLICKED, () => {
|
||||
AuthHandler.authenticate(room);
|
||||
});
|
||||
|
||||
11
config.js
11
config.js
@@ -44,9 +44,6 @@ var config = {
|
||||
//
|
||||
|
||||
testing: {
|
||||
// Enables experimental simulcast support on Firefox.
|
||||
enableFirefoxSimulcast: false,
|
||||
|
||||
// P2P test mode disables automatic switching to P2P when there are 2
|
||||
// participants in the conference.
|
||||
p2pTestMode: false
|
||||
@@ -451,6 +448,14 @@ var config = {
|
||||
// the menu has option to flip the locally seen video for local presentations
|
||||
// disableLocalVideoFlip: false,
|
||||
|
||||
// Mainly privacy related settings
|
||||
|
||||
// Disables all invite functions from the app (share, invite, dial out...etc)
|
||||
// disableInviteFunctions: true,
|
||||
|
||||
// Disables storing the room name to the recents list
|
||||
// doNotStoreRoom: true,
|
||||
|
||||
// Deployment specific URLs.
|
||||
// deploymentUrls: {
|
||||
// // If specified a 'Help' button will be displayed in the overflow menu with a link to the specified URL for
|
||||
|
||||
@@ -20,18 +20,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-foreign {
|
||||
align-items: center;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
font-size: 40pt;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.avatar-svg {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@@ -63,4 +51,4 @@
|
||||
@include avatarBadge;
|
||||
background-color: $presence-idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,14 +85,15 @@ const options = {
|
||||
const api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
You can set the userInfo(email) for the call:
|
||||
You can set the userInfo(email, display name) for the call:
|
||||
|
||||
```javascript
|
||||
var domain = "meet.jit.si";
|
||||
var options = {
|
||||
...
|
||||
userInfo: {
|
||||
email: 'email@jitsiexamplemail.com'
|
||||
email: 'email@jitsiexamplemail.com',
|
||||
displayName: 'John Doe'
|
||||
}
|
||||
}
|
||||
var api = new JitsiMeetExternalAPI(domain, options);
|
||||
|
||||
@@ -69,7 +69,7 @@ Use it at the CLI, type
|
||||
make dev
|
||||
```
|
||||
|
||||
By default the backend deployment used is `beta.meet.jit.si`. You can point the Jitsi-Meet app at a different backend by using a proxy server. To do this, set the WEBPACK_DEV_SERVER_PROXY_TARGET variable:
|
||||
By default the backend deployment used is `alpha.jitsi.net`. You can point the Jitsi-Meet app at a different backend by using a proxy server. To do this, set the WEBPACK_DEV_SERVER_PROXY_TARGET variable:
|
||||
```
|
||||
export WEBPACK_DEV_SERVER_PROXY_TARGET=https://your-example-server.com
|
||||
make dev
|
||||
|
||||
@@ -7,8 +7,10 @@ Jitsi Meet can be built as a standalone app for Android or iOS. It uses the
|
||||
|
||||
First make sure the [React Native dependencies] are installed.
|
||||
|
||||
**NOTE**: This document assumes the app is being built on a macOS system.
|
||||
**NOTE**: Node 10.X and npm 6.X are recommended for building.
|
||||
**NOTE**: This document assumes the app is being built on a macOS system. GNU/Linux is also
|
||||
supported for building the Android app and Windows **is not supported at alll**.
|
||||
|
||||
**NOTE**: Node 12.X and npm 6.X are recommended for building.
|
||||
|
||||
|
||||
## iOS
|
||||
|
||||
@@ -107,7 +107,7 @@ Open to world:
|
||||
Open to the videobridges only
|
||||
|
||||
* 5222 TCP (for Prosody)
|
||||
* 5437 TCP (for Jicofo)
|
||||
* 5347 TCP (for Jicofo)
|
||||
|
||||
|
||||
#### NGINX
|
||||
|
||||
@@ -51,7 +51,8 @@ var interfaceConfig = {
|
||||
'fodeviceselection', 'hangup', 'profile', 'info', 'chat', 'recording',
|
||||
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
||||
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone'
|
||||
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone',
|
||||
'e2ee'
|
||||
],
|
||||
|
||||
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
|
||||
|
||||
@@ -586,4 +586,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: f615794fb9184757b00cd16e534824ba6ee2fc98
|
||||
|
||||
COCOAPODS: 1.8.4
|
||||
COCOAPODS: 1.9.1
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>20.2.0</string>
|
||||
<string>20.3.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>20.2.0</string>
|
||||
<string>20.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>20.2.0</string>
|
||||
<string>20.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"none": "Keine Audiogeräte verfügbar"
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "Nur Audio"
|
||||
"audioOnly": "Geringe Bandbreite"
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Meeting-Link hinzufügen",
|
||||
@@ -69,7 +69,7 @@
|
||||
},
|
||||
"connection": {
|
||||
"ATTACHED": "Angehängt",
|
||||
"AUTHENTICATING": "Anmeldung läuft",
|
||||
"AUTHENTICATING": "Authentifizierung läuft",
|
||||
"AUTHFAIL": "Authentifizierung fehlgeschlagen",
|
||||
"CONNECTED": "Verbunden",
|
||||
"CONNECTING": "Verbindung wird hergestellt",
|
||||
@@ -268,7 +268,7 @@
|
||||
"stopRecording": "Aufnahme stoppen",
|
||||
"stopRecordingWarning": "Sind Sie sicher, dass Sie die Aufnahme stoppen möchten?",
|
||||
"stopStreamingWarning": "Sind Sie sicher, dass Sie das Livestreaming stoppen möchten?",
|
||||
"streamKey": "Name/Schlüssel für den Stream",
|
||||
"streamKey": "Streamschlüssel",
|
||||
"Submit": "OK",
|
||||
"thankYou": "Danke für die Verwendung von {{appName}}!",
|
||||
"token": "Token",
|
||||
@@ -352,7 +352,7 @@
|
||||
"keyboardShortcuts": {
|
||||
"focusLocal": "Lokales Video fokussieren",
|
||||
"focusRemote": "Auf das Video eines anderen Teilnehmers fokussieren",
|
||||
"fullScreen": "Vollbildmodus aktivieren / deaktivieren",
|
||||
"fullScreen": "Vollbildmodus aktivieren oder deaktivieren",
|
||||
"keyboardShortcuts": "Tastenkürzel",
|
||||
"localRecording": "Lokale Aufzeichnungssteuerelemente ein- oder ausblenden",
|
||||
"mute": "Stummschaltung aktivieren oder deaktivieren",
|
||||
@@ -372,7 +372,7 @@
|
||||
"changeSignIn": "Konten wechseln.",
|
||||
"choose": "Livestream auswählen",
|
||||
"chooseCTA": "Streaming-Option auswählen. Sie sind aktuell als {{email}} angemeldet.",
|
||||
"enterStreamKey": "Name/Schlüssel für den YouTube-Livestream hier eingeben.",
|
||||
"enterStreamKey": "Streamschlüssel für den YouTube-Livestream hier eingeben.",
|
||||
"error": "Das Livestreaming ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"errorAPI": "Beim Abrufen der YouTube-Livestreams ist ein Fehler aufgetreten. Bitte versuchen Sie, sich erneut anzumelden.",
|
||||
"errorLiveStreamNotEnabled": "Livestreaming ist für {{email}} nicht aktiviert. Aktivieren Sie das Livestreaming oder melden Sie sich bei einem Konto mit aktiviertem Livestreaming an.",
|
||||
@@ -390,7 +390,7 @@
|
||||
"serviceName": "Livestreaming-Dienst",
|
||||
"signedInAs": "Sie sind derzeit angemeldet als:",
|
||||
"signIn": "Mit Google anmelden",
|
||||
"signInCTA": "Anmelden oder den Namen/Schlüssel des YouTube-Livestreams eingeben.",
|
||||
"signInCTA": "Anmelden oder den Streamschlüssel des YouTube-Livestreams eingeben.",
|
||||
"signOut": "Abmelden",
|
||||
"start": "Einen Livestream starten",
|
||||
"streamIdHelp": "Was ist das?",
|
||||
|
||||
@@ -82,10 +82,10 @@
|
||||
"connectionindicator": {
|
||||
"address": "Dirección:",
|
||||
"bandwidth": "Ancho de banda estimado:",
|
||||
"bitrate": "Tasa de bits:",
|
||||
"bitrate": "Tasa de transferencia:",
|
||||
"bridgeCount": "Contador del servidor: ",
|
||||
"connectedTo": "Conectado a:",
|
||||
"framerate": "Tasa de cuadros:",
|
||||
"framerate": "Fotogramas por segundo:",
|
||||
"less": "Mostrar menos",
|
||||
"localaddress": "Dirección local:",
|
||||
"localaddress_plural": "Direcciones locales:",
|
||||
@@ -96,8 +96,8 @@
|
||||
"quality": {
|
||||
"good": "Bueno",
|
||||
"inactive": "Inactivo",
|
||||
"lost": "Perdida",
|
||||
"nonoptimal": "No óptima",
|
||||
"lost": "Perdido",
|
||||
"nonoptimal": "No óptimo",
|
||||
"poor": "Pobre"
|
||||
},
|
||||
"remoteaddress": "Dirección remota:",
|
||||
@@ -105,7 +105,7 @@
|
||||
"remoteport": "Puerto remoto:",
|
||||
"remoteport_plural": "Puertos remotos:",
|
||||
"resolution": "Resolución:",
|
||||
"status": "Conexión:",
|
||||
"status": "Estado:",
|
||||
"transport": "Transporte:",
|
||||
"transport_plural": "Transportes:"
|
||||
},
|
||||
@@ -322,7 +322,7 @@
|
||||
"numbers": "Números de marcado",
|
||||
"password": "$t(lockRoomPasswordUppercase):",
|
||||
"title": "Compartir",
|
||||
"tooltip": "Compartir el enlace y la información de acceso telefónico para esta reunión",
|
||||
"tooltip": "Compartir el enlace y acceso telefónico para esta reunión",
|
||||
"label": "Información de la reunión"
|
||||
},
|
||||
"inviteDialog": {
|
||||
@@ -345,10 +345,10 @@
|
||||
"fullScreen": "Ver o salir de pantalla completa",
|
||||
"keyboardShortcuts": "Atajos de teclado",
|
||||
"localRecording": "Mostrar u ocultar controles de grabación locales",
|
||||
"mute": "Activar o desactivar micrófono",
|
||||
"mute": "Activar o silenciar el micrófono",
|
||||
"pushToTalk": "Presione para hablar",
|
||||
"raiseHand": "Levantar o bajar la mano",
|
||||
"showSpeakerStats": "Mostrar estadísticas del locutor",
|
||||
"showSpeakerStats": "Estadísticas de participantes",
|
||||
"toggleChat": "Abrir o cerrar panel de chat",
|
||||
"toggleFilmstrip": "Mostrar/Ocultar miniaturas de video",
|
||||
"toggleScreensharing": "Cambiar entre cámara y compartir pantalla",
|
||||
@@ -522,8 +522,8 @@
|
||||
"selectAudioOutput": "Salida de audio",
|
||||
"selectCamera": "Cámara",
|
||||
"selectMic": "Micrófono",
|
||||
"startAudioMuted": "Todos inician en silencio",
|
||||
"startVideoMuted": "Todos inician ocultos",
|
||||
"startAudioMuted": "Todos inician silenciados",
|
||||
"startVideoMuted": "Todos inician con cámara desactivada",
|
||||
"title": "Ajustes"
|
||||
},
|
||||
"settingsView": {
|
||||
@@ -549,14 +549,14 @@
|
||||
"dialInfoText": "\n\n===== \\ n\n¿Solo quieres marcar en tu teléfono? \\ N\n{{defaultDialInNumber}} Haga clic en este enlace para ver el marcado en los números de teléfono de esta reunión\n{{dialInfoPageUrl}}",
|
||||
"mainText": "Presione en el siguiente enlace para unirse a la reunión:\n{{roomUrl}}"
|
||||
},
|
||||
"speaker": "Orador",
|
||||
"speaker": "Participante",
|
||||
"speakerStats": {
|
||||
"hours": "{{count}}h",
|
||||
"minutes": "{{count}}m",
|
||||
"name": "Nombre",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "Estadísticas del locutor",
|
||||
"speakerTime": "Tiempo del locutor"
|
||||
"speakerStats": "Estadísticas de participantes",
|
||||
"speakerTime": "Tiempo hablado"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"bitrate": "Tasa de transferencia:",
|
||||
"bridgeCount": "Contador del servidor: ",
|
||||
"connectedTo": "Conectado a:",
|
||||
"framerate": "Cuadros por segundo:",
|
||||
"framerate": "Fotogramas por segundo:",
|
||||
"less": "Mostrar menos",
|
||||
"localaddress": "Dirección local:",
|
||||
"localaddress_plural": "Direcciones locales:",
|
||||
@@ -109,7 +109,7 @@
|
||||
"remoteport": "Puerto remoto:",
|
||||
"remoteport_plural": "Puertos remotos:",
|
||||
"resolution": "Resolución:",
|
||||
"status": "Conexión:",
|
||||
"status": "Estado:",
|
||||
"transport": "Transporte:",
|
||||
"transport_plural": "Transportes:"
|
||||
},
|
||||
@@ -332,8 +332,8 @@
|
||||
"numbers": "Números de marcación desde afuera",
|
||||
"password": "$t(lockRoomPasswordUppercase):",
|
||||
"title": "Compartir",
|
||||
"tooltip": "Compartir el enlace y la información de marcación desde afuera para esta reunión",
|
||||
"label": "Información de reunión"
|
||||
"tooltip": "Compartir el enlace y acceso telefónico para esta reunión",
|
||||
"label": "Información de la reunión"
|
||||
},
|
||||
"inviteDialog": {
|
||||
"alertText": "No se pudieron invitar a algunos participantes.",
|
||||
@@ -532,8 +532,8 @@
|
||||
"selectAudioOutput": "Salida de audio",
|
||||
"selectCamera": "Cámara",
|
||||
"selectMic": "Micrófono",
|
||||
"startAudioMuted": "Todos comienzan con el silencio activado",
|
||||
"startVideoMuted": "Todos comienzan ocultos",
|
||||
"startAudioMuted": "Todos comienzan con silenciados",
|
||||
"startVideoMuted": "Todos comienzan con cámara desactivada",
|
||||
"title": "Configuración"
|
||||
},
|
||||
"settingsView": {
|
||||
@@ -559,14 +559,14 @@
|
||||
"dialInfoText": "\n\n=====\n\n¿Desea llamar desde su teléfono?\n\n{{defaultDialInNumber}}La lista de números disponibles para la reunión está disponible aquí : \n{{dialInfoPageUrl}}",
|
||||
"mainText": "Haz clic en el enlace siguiente para unirte a la reunión:\n{{roomUrl}}"
|
||||
},
|
||||
"speaker": "Altavoz",
|
||||
"speaker": "Participante",
|
||||
"speakerStats": {
|
||||
"hours": "{{count}} h",
|
||||
"minutes": "{{count}} min",
|
||||
"name": "Nombre",
|
||||
"seconds": "{{count}} s",
|
||||
"speakerStats": "Estadísticas del altavoz",
|
||||
"speakerTime": "Hora del altavoz"
|
||||
"speakerStats": "Estadísticas de participantes",
|
||||
"speakerTime": "Tiempo hablado"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
@@ -641,7 +641,7 @@
|
||||
"lowerYourHand": "Bajar la mano",
|
||||
"moreActions": "Más acciones",
|
||||
"moreOptions": "Más opciones",
|
||||
"mute": "Silenciar/anular silencio",
|
||||
"mute": "Activar o silenciar el micrófono",
|
||||
"muteEveryone": "Silenciar a todos",
|
||||
"noAudioSignalTitle": "¡No hay ninguna entrada que provenga de su micrófono!",
|
||||
"noAudioSignalDesc": "Si no lo silenció intencionalmente desde la configuración del sistema o el hardware, considere la posibilidad de cambiar el dispositivo.",
|
||||
@@ -660,7 +660,7 @@
|
||||
"sharedvideo": "Compartir un video de YouTube",
|
||||
"shareRoom": "Invitar a alguien",
|
||||
"shortcuts": "Ver accesos directos",
|
||||
"speakerStats": "Estadísticas del altavoz",
|
||||
"speakerStats": "Estadísticas de participantes",
|
||||
"startScreenSharing": "Iniciar uso compartido de pantalla",
|
||||
"startSubtitles": "Iniciar subtítulos",
|
||||
"stopScreenSharing": "Detener uso compartido de pantalla",
|
||||
|
||||
@@ -175,6 +175,10 @@
|
||||
"dismiss": "Dismiss",
|
||||
"displayNameRequired": "Hi! What’s your name?",
|
||||
"done": "Done",
|
||||
"e2eeDescription": "<p>End-to-End Encryption is currently <strong>EXPERIMENTAL</strong>. Please see <a href='https://jitsi.org/blog/e2ee/' target='_blank'>this post</a> for details.</p><br/><p>Please keep in mind that turning on end-to-end encryption will effectively disable server-side provided services such as: recording, live streaming and phone participation. Also keep in mind that the meeting will only work for people joining from browsers with support for insertable streams.</p>",
|
||||
"e2eeLabel": "Key",
|
||||
"e2eeTitle": "End-to-End Encryption",
|
||||
"e2eeWarning": "<br /><p><strong>WARNING:</strong> Not all participants in this meeting seem to have support for End-to-End encryption. If you enable it they won't be able to see nor hear you.</p>",
|
||||
"enterDisplayName": "Please enter your name here",
|
||||
"error": "Error",
|
||||
"externalInstallationMsg": "You need to install our desktop sharing extension.",
|
||||
@@ -595,6 +599,7 @@
|
||||
"chat": "Toggle chat window",
|
||||
"document": "Toggle shared document",
|
||||
"download": "Download our apps",
|
||||
"e2ee": "End-to-End Encryption",
|
||||
"feedback": "Leave feedback",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hangup": "Leave the call",
|
||||
@@ -638,6 +643,7 @@
|
||||
"documentClose": "Close shared document",
|
||||
"documentOpen": "Open shared document",
|
||||
"download": "Download our apps",
|
||||
"e2ee": "End-to-End Encryption",
|
||||
"enterFullScreen": "View full screen",
|
||||
"enterTileView": "Enter tile view",
|
||||
"exitFullScreen": "Exit full screen",
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
setSubject
|
||||
} from '../../react/features/base/conference';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import { setE2EEKey } from '../../react/features/e2ee';
|
||||
import { invite } from '../../react/features/invite';
|
||||
import { toggleTileView } from '../../react/features/video-layout';
|
||||
import { getJitsiMeetTransport } from '../transport';
|
||||
@@ -166,6 +167,10 @@ function initCommands() {
|
||||
} catch (err) {
|
||||
logger.error('Failed sending endpoint text message', err);
|
||||
}
|
||||
},
|
||||
'e2ee-key': key => {
|
||||
logger.debug('Set E2EE key command received');
|
||||
APP.store.dispatch(setE2EEKey(key));
|
||||
}
|
||||
};
|
||||
transport.on('event', ({ data, name }) => {
|
||||
|
||||
15
modules/API/external/external_api.js
vendored
15
modules/API/external/external_api.js
vendored
@@ -29,6 +29,7 @@ const ALWAYS_ON_TOP_FILENAMES = [
|
||||
const commands = {
|
||||
avatarUrl: 'avatar-url',
|
||||
displayName: 'display-name',
|
||||
e2eeKey: 'e2ee-key',
|
||||
email: 'email',
|
||||
hangup: 'video-hangup',
|
||||
password: 'password',
|
||||
@@ -236,6 +237,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* information about the initial devices that will be used in the call.
|
||||
* @param {Object} [options.userInfo] - Object containing information about
|
||||
* the participant opening the meeting.
|
||||
* @param {string} [options.e2eeKey] - The key used for End-to-End encryption.
|
||||
* THIS IS EXPERIMENTAL.
|
||||
*/
|
||||
constructor(domain, ...args) {
|
||||
super();
|
||||
@@ -251,7 +254,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
onload = undefined,
|
||||
invitees,
|
||||
devices,
|
||||
userInfo
|
||||
userInfo,
|
||||
e2eeKey
|
||||
} = parseArguments(args);
|
||||
|
||||
this._parentNode = parentNode;
|
||||
@@ -276,6 +280,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
if (Array.isArray(invitees) && invitees.length > 0) {
|
||||
this.invite(invitees);
|
||||
}
|
||||
this._tmpE2EEKey = e2eeKey;
|
||||
this._isLargeVideoVisible = true;
|
||||
this._numberOfParticipants = 0;
|
||||
this._participants = {};
|
||||
@@ -429,11 +434,17 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
const userID = data.id;
|
||||
|
||||
switch (name) {
|
||||
case 'video-conference-joined':
|
||||
case 'video-conference-joined': {
|
||||
if (typeof this._tmpE2EEKey !== 'undefined') {
|
||||
this.executeCommand(commands.e2eeKey, this._tmpE2EEKey);
|
||||
this._tmpE2EEKey = undefined;
|
||||
}
|
||||
|
||||
this._myUserID = userID;
|
||||
this._participants[userID] = {
|
||||
avatarURL: data.avatarURL
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'participant-joined': {
|
||||
|
||||
@@ -322,15 +322,6 @@ UI.inputDisplayNameHandler = function(newDisplayName) {
|
||||
eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newDisplayName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the type of the remote video.
|
||||
* @param jid the jid for the remote video
|
||||
* @returns the video type video or screen.
|
||||
*/
|
||||
UI.getRemoteVideoType = function(jid) {
|
||||
return VideoLayout.getRemoteVideoType(jid);
|
||||
};
|
||||
|
||||
// FIXME check if someone user this
|
||||
UI.showLoginPopup = function(callback) {
|
||||
logger.log('password is required');
|
||||
|
||||
@@ -19,7 +19,6 @@ export default class SharedVideoThumb extends SmallVideo {
|
||||
this.id = participant.id;
|
||||
this.isLocal = false;
|
||||
this.url = participant.id;
|
||||
this.setVideoType(videoType);
|
||||
this.videoSpanId = 'sharedVideoContainer';
|
||||
this.container = this.createContainer(this.videoSpanId);
|
||||
this.$container = $(this.container);
|
||||
|
||||
@@ -27,12 +27,15 @@ const UIUtil = {
|
||||
*/
|
||||
prependChild(container, newChild) {
|
||||
const firstChild = container.childNodes[0];
|
||||
let result;
|
||||
|
||||
if (firstChild) {
|
||||
container.insertBefore(newChild, firstChild);
|
||||
result = container.insertBefore(newChild, firstChild);
|
||||
} else {
|
||||
container.appendChild(newChild);
|
||||
result = container.appendChild(newChild);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -90,13 +90,13 @@ export default class RemoteVideo extends SmallVideo {
|
||||
this._isRemoteControlSessionActive = false;
|
||||
|
||||
/**
|
||||
* The flag is set to <tt>true</tt> after the 'onplay' event has been
|
||||
* The flag is set to <tt>true</tt> after the 'canplay' event has been
|
||||
* triggered on the current video element. It goes back to <tt>false</tt>
|
||||
* when the stream is removed. It is used to determine whether the video
|
||||
* playback has ever started.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.wasVideoPlayed = false;
|
||||
this._canPlayEventReceived = false;
|
||||
|
||||
/**
|
||||
* The flag is set to <tt>true</tt> if remote participant's video gets muted
|
||||
@@ -366,7 +366,7 @@ export default class RemoteVideo extends SmallVideo {
|
||||
|
||||
select.remove();
|
||||
if (isVideo) {
|
||||
this.wasVideoPlayed = false;
|
||||
this._canPlayEventReceived = false;
|
||||
}
|
||||
|
||||
logger.info(`${isVideo ? 'Video' : 'Audio'} removed ${this.id}`, select);
|
||||
@@ -390,13 +390,10 @@ export default class RemoteVideo extends SmallVideo {
|
||||
}
|
||||
|
||||
/**
|
||||
* The remote video is considered "playable" once the stream has started
|
||||
* according to the {@link #hasVideoStarted} result.
|
||||
* It will be allowed to display video also in
|
||||
* {@link JitsiParticipantConnectionStatus.INTERRUPTED} if the video was ever
|
||||
* played and was not muted while not in ACTIVE state. This basically means
|
||||
* that there is stalled video image cached that could be displayed. It's used
|
||||
* to show "grey video image" in user's thumbnail when there are connectivity
|
||||
* The remote video is considered "playable" once the can play event has been received. It will be allowed to
|
||||
* display video also in {@link JitsiParticipantConnectionStatus.INTERRUPTED} if the video has received the canplay
|
||||
* event and was not muted while not in ACTIVE state. This basically means that there is stalled video image cached
|
||||
* that could be displayed. It's used to show "grey video image" in user's thumbnail when there are connectivity
|
||||
* issues.
|
||||
*
|
||||
* @inheritdoc
|
||||
@@ -406,7 +403,7 @@ export default class RemoteVideo extends SmallVideo {
|
||||
const connectionState = APP.conference.getParticipantConnectionStatus(this.id);
|
||||
|
||||
return super.isVideoPlayable()
|
||||
&& this.hasVideoStarted()
|
||||
&& this._canPlayEventReceived
|
||||
&& (connectionState === JitsiParticipantConnectionStatus.ACTIVE
|
||||
|| (connectionState === JitsiParticipantConnectionStatus.INTERRUPTED && !this.mutedWhileDisconnected));
|
||||
}
|
||||
@@ -459,26 +456,16 @@ export default class RemoteVideo extends SmallVideo {
|
||||
return;
|
||||
}
|
||||
|
||||
streamElement.onplaying = () => {
|
||||
this.wasVideoPlayed = true;
|
||||
streamElement.oncanplay = () => {
|
||||
this._canPlayEventReceived = true;
|
||||
this.VideoLayout.remoteVideoActive(streamElement, this.id);
|
||||
streamElement.onplaying = null;
|
||||
streamElement.oncanplay = undefined;
|
||||
|
||||
// Refresh to show the video
|
||||
this.updateView();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the video stream has started for this RemoteVideo instance.
|
||||
*
|
||||
* @returns {boolean} true if this RemoteVideo has a video stream for which
|
||||
* the playback has been started.
|
||||
*/
|
||||
hasVideoStarted() {
|
||||
return this.wasVideoPlayed;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} stream
|
||||
@@ -494,20 +481,16 @@ export default class RemoteVideo extends SmallVideo {
|
||||
|
||||
isVideo ? this.videoStream = stream : this.audioStream = stream;
|
||||
|
||||
if (isVideo) {
|
||||
this.setVideoType(stream.videoType);
|
||||
}
|
||||
|
||||
if (!stream.getOriginalStream()) {
|
||||
logger.debug('Remote video stream has no original stream');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const streamElement = SmallVideo.createStreamElement(stream);
|
||||
let streamElement = SmallVideo.createStreamElement(stream);
|
||||
|
||||
// Put new stream element always in front
|
||||
UIUtils.prependChild(this.container, streamElement);
|
||||
streamElement = UIUtils.prependChild(this.container, streamElement);
|
||||
|
||||
$(streamElement).hide();
|
||||
|
||||
|
||||
@@ -32,8 +32,6 @@ import {
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
|
||||
/**
|
||||
* Display mode constant used when video is being displayed on the small video.
|
||||
* @type {number}
|
||||
@@ -158,28 +156,6 @@ export default class SmallVideo {
|
||||
return this.$container.is(':visible');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of the video displayed by this instance.
|
||||
* Note that this is a string without clearly defined or checked values, and
|
||||
* it is NOT one of the strings defined in service/RTC/VideoType in
|
||||
* lib-jitsi-meet.
|
||||
* @param videoType 'camera' or 'desktop', or 'sharedvideo'.
|
||||
*/
|
||||
setVideoType(videoType) {
|
||||
this.videoType = videoType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the video displayed by this instance.
|
||||
* Note that this is a string without clearly defined or checked values, and
|
||||
* it is NOT one of the strings defined in service/RTC/VideoType in
|
||||
* lib-jitsi-meet.
|
||||
* @returns {String} 'camera', 'screen', 'sharedvideo', or undefined.
|
||||
*/
|
||||
getVideoType() {
|
||||
return this.videoType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an audio or video element for a particular MediaStream.
|
||||
*/
|
||||
@@ -189,6 +165,7 @@ export default class SmallVideo {
|
||||
|
||||
if (isVideo) {
|
||||
element.setAttribute('muted', 'true');
|
||||
element.setAttribute('playsInline', 'true'); /* for Safari on iOS to work */
|
||||
} else if (config.startSilent) {
|
||||
element.muted = true;
|
||||
}
|
||||
@@ -451,7 +428,7 @@ export default class SmallVideo {
|
||||
* or <tt>false</tt> otherwise.
|
||||
*/
|
||||
isCurrentlyOnLargeVideo() {
|
||||
return this.VideoLayout.isCurrentlyOnLarge(this.id);
|
||||
return APP.store.getState()['features/large-video']?.participantId === this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -499,7 +476,7 @@ export default class SmallVideo {
|
||||
hasVideo: Boolean(this.selectVideoElement().length),
|
||||
connectionStatus: APP.conference.getParticipantConnectionStatus(this.id),
|
||||
mutedWhileDisconnected: this.mutedWhileDisconnected,
|
||||
wasVideoPlayed: this.wasVideoPlayed,
|
||||
canPlayEventReceived: this._canPlayEventReceived,
|
||||
videoStream: Boolean(this.videoStream),
|
||||
isVideoMuted: this.isVideoMuted,
|
||||
videoStreamMuted: this.videoStream ? this.videoStream.isMuted() : 'no stream'
|
||||
@@ -639,39 +616,6 @@ export default class SmallVideo {
|
||||
this.updateIndicators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for onresize events for this video, which will monitor for
|
||||
* resolution changes, will calculate the delay since the moment the listened
|
||||
* is added, and will fire a RESOLUTION_CHANGED event.
|
||||
*/
|
||||
waitForResolutionChange() {
|
||||
const beforeChange = window.performance.now();
|
||||
const videos = this.selectVideoElement();
|
||||
|
||||
if (!videos || !videos.length || videos.length <= 0) {
|
||||
return;
|
||||
}
|
||||
const video = videos[0];
|
||||
const oldWidth = video.videoWidth;
|
||||
const oldHeight = video.videoHeight;
|
||||
|
||||
video.onresize = () => {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (video.videoWidth != oldWidth || video.videoHeight != oldHeight) {
|
||||
// Only run once.
|
||||
video.onresize = null;
|
||||
|
||||
const delay = window.performance.now() - beforeChange;
|
||||
const emitter = this.VideoLayout.getEventEmitter();
|
||||
|
||||
if (emitter) {
|
||||
emitter.emit(UIEvents.RESOLUTION_CHANGED, this.getId(), `${oldWidth}x${oldHeight}`,
|
||||
`${video.videoWidth}x${video.videoHeight}`, delay);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initalizes any browser specific properties. Currently sets the overflow
|
||||
* property for Qt browsers on Windows to hidden, thus fixing the following
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
/* global APP, $, interfaceConfig */
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
import { VIDEO_TYPE } from '../../../react/features/base/media';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../../../react/features/base/media';
|
||||
import {
|
||||
getLocalParticipant as getLocalParticipantFromStore,
|
||||
getPinnedParticipant,
|
||||
getParticipantById,
|
||||
pinParticipant
|
||||
} from '../../../react/features/base/participants';
|
||||
import { getTrackByMediaTypeAndParticipant } from '../../../react/features/base/tracks';
|
||||
import { SHARED_VIDEO_CONTAINER_TYPE } from '../shared_video/SharedVideo';
|
||||
import SharedVideoThumb from '../shared_video/SharedVideoThumb';
|
||||
|
||||
@@ -73,9 +75,6 @@ const VideoLayout = {
|
||||
emitter,
|
||||
this._updateLargeVideoIfDisplayed.bind(this));
|
||||
|
||||
// sets default video type of local video
|
||||
// FIXME container type is totally different thing from the video type
|
||||
localVideoThumbnail.setVideoType(VIDEO_CONTAINER_TYPE);
|
||||
this.registerListeners();
|
||||
},
|
||||
|
||||
@@ -221,10 +220,16 @@ const VideoLayout = {
|
||||
* @returns {String} the video type video or screen.
|
||||
*/
|
||||
getRemoteVideoType(id) {
|
||||
const smallVideo = VideoLayout.getSmallVideo(id);
|
||||
const state = APP.store.getState();
|
||||
const participant = getParticipantById(state, id);
|
||||
|
||||
if (participant?.isFakeParticipant) {
|
||||
return SHARED_VIDEO_CONTAINER_TYPE;
|
||||
}
|
||||
|
||||
return smallVideo ? smallVideo.getVideoType() : null;
|
||||
const videoTrack = getTrackByMediaTypeAndParticipant(state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
|
||||
|
||||
return videoTrack?.videoType;
|
||||
},
|
||||
|
||||
isPinned(id) {
|
||||
@@ -308,12 +313,6 @@ const VideoLayout = {
|
||||
addRemoteVideoContainer(id, remoteVideo) {
|
||||
remoteVideos[id] = remoteVideo;
|
||||
|
||||
if (!remoteVideo.getVideoType()) {
|
||||
// make video type the default one (camera)
|
||||
// FIXME container type is not a video type
|
||||
remoteVideo.setVideoType(VIDEO_CONTAINER_TYPE);
|
||||
}
|
||||
|
||||
// Initialize the view
|
||||
remoteVideo.updateView();
|
||||
},
|
||||
@@ -491,22 +490,6 @@ const VideoLayout = {
|
||||
|
||||
logger.info('Peer video type changed: ', id, newVideoType);
|
||||
|
||||
let smallVideo;
|
||||
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
if (!localVideoThumbnail) {
|
||||
logger.warn('Local video not ready yet');
|
||||
|
||||
return;
|
||||
}
|
||||
smallVideo = localVideoThumbnail;
|
||||
} else if (remoteVideos[id]) {
|
||||
smallVideo = remoteVideos[id];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
smallVideo.setVideoType(newVideoType);
|
||||
|
||||
this._updateLargeVideoIfDisplayed(id, true);
|
||||
},
|
||||
|
||||
@@ -584,17 +567,16 @@ const VideoLayout = {
|
||||
}
|
||||
const currentContainer = largeVideo.getCurrentContainer();
|
||||
const currentContainerType = largeVideo.getCurrentContainerType();
|
||||
const currentId = largeVideo.id;
|
||||
const isOnLarge = this.isCurrentlyOnLarge(id);
|
||||
const smallVideo = this.getSmallVideo(id);
|
||||
const state = APP.store.getState();
|
||||
const videoTrack = getTrackByMediaTypeAndParticipant(state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
|
||||
const videoStream = videoTrack?.jitsiTrack;
|
||||
|
||||
if (isOnLarge && !forceUpdate
|
||||
&& LargeVideoManager.isVideoContainer(currentContainerType)
|
||||
&& smallVideo) {
|
||||
&& videoStream) {
|
||||
const currentStreamId = currentContainer.getStreamID();
|
||||
const newStreamId
|
||||
= smallVideo.videoStream
|
||||
? smallVideo.videoStream.getId() : null;
|
||||
const newStreamId = videoStream?.getId() || null;
|
||||
|
||||
// FIXME it might be possible to get rid of 'forceUpdate' argument
|
||||
if (currentStreamId !== newStreamId) {
|
||||
@@ -603,42 +585,17 @@ const VideoLayout = {
|
||||
}
|
||||
}
|
||||
|
||||
if ((!isOnLarge || forceUpdate) && smallVideo) {
|
||||
if (!isOnLarge || forceUpdate) {
|
||||
const videoType = this.getRemoteVideoType(id);
|
||||
|
||||
// FIXME video type is not the same thing as container type
|
||||
|
||||
if (id !== currentId && videoType === VIDEO_CONTAINER_TYPE) {
|
||||
APP.API.notifyOnStageParticipantChanged(id);
|
||||
}
|
||||
|
||||
let oldSmallVideo;
|
||||
|
||||
if (currentId) {
|
||||
oldSmallVideo = this.getSmallVideo(currentId);
|
||||
}
|
||||
|
||||
smallVideo.waitForResolutionChange();
|
||||
if (oldSmallVideo) {
|
||||
oldSmallVideo.waitForResolutionChange();
|
||||
}
|
||||
|
||||
largeVideo.updateLargeVideo(
|
||||
id,
|
||||
smallVideo.videoStream,
|
||||
videoStream,
|
||||
videoType || VIDEO_TYPE.CAMERA
|
||||
).then(() => {
|
||||
// update current small video and the old one
|
||||
smallVideo.updateView();
|
||||
oldSmallVideo && oldSmallVideo.updateView();
|
||||
}, () => {
|
||||
// use clicked other video during update, nothing to do.
|
||||
).catch(() => {
|
||||
// do nothing
|
||||
});
|
||||
|
||||
} else if (currentId) {
|
||||
const currentSmallVideo = this.getSmallVideo(currentId);
|
||||
|
||||
currentSmallVideo && currentSmallVideo.updateView();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -723,10 +680,6 @@ const VideoLayout = {
|
||||
this.localFlipX = val;
|
||||
},
|
||||
|
||||
getEventEmitter() {
|
||||
return eventEmitter;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles user's features changes.
|
||||
*/
|
||||
|
||||
34
package-lock.json
generated
34
package-lock.json
generated
@@ -2833,9 +2833,9 @@
|
||||
}
|
||||
},
|
||||
"@jitsi/sdp-interop": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/sdp-interop/-/sdp-interop-1.0.1.tgz",
|
||||
"integrity": "sha512-OJm8IYsJKCYJBlC0geRHm2VHi8ow2k/3wOZ7n0lEOBfV6RWghZQzQWHdT/qrkbXB2EHcm40Oy91a5Bfz2m6ydA==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/sdp-interop/-/sdp-interop-1.0.2.tgz",
|
||||
"integrity": "sha512-3Ivl0iuGZ+R97GzgzKneuY45cVCmpYWaOc37uyg58GJK3IiY3+/kf9fdfOweyeZeB6wrNkjI8dfn+I3XQPxbkQ==",
|
||||
"requires": {
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"sdp-transform": "2.3.0"
|
||||
@@ -10654,8 +10654,8 @@
|
||||
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
|
||||
},
|
||||
"js-utils": {
|
||||
"version": "github:jitsi/js-utils#0b2cef90613a74777fefd98d4ee3eda3879809ab",
|
||||
"from": "github:jitsi/js-utils#0b2cef90613a74777fefd98d4ee3eda3879809ab",
|
||||
"version": "github:jitsi/js-utils#df68966e3c65b5c57fcd2670da1326a2c77518d1",
|
||||
"from": "github:jitsi/js-utils#df68966e3c65b5c57fcd2670da1326a2c77518d1",
|
||||
"requires": {
|
||||
"bowser": "2.7.0",
|
||||
"js-md5": "0.7.3",
|
||||
@@ -10884,37 +10884,21 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#0d058e50633d62c7b26114418e234f4a8ae9a78e",
|
||||
"from": "github:jitsi/lib-jitsi-meet#0d058e50633d62c7b26114418e234f4a8ae9a78e",
|
||||
"version": "github:jitsi/lib-jitsi-meet#f97c37d0140a0f12644ae29f4dd93757b8b8610f",
|
||||
"from": "github:jitsi/lib-jitsi-meet#f97c37d0140a0f12644ae29f4dd93757b8b8610f",
|
||||
"requires": {
|
||||
"@jitsi/sdp-interop": "1.0.1",
|
||||
"@jitsi/sdp-interop": "1.0.2",
|
||||
"@jitsi/sdp-simulcast": "0.3.0",
|
||||
"async": "0.9.0",
|
||||
"current-executing-script": "0.1.3",
|
||||
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b",
|
||||
"js-utils": "github:jitsi/js-utils#8567f86ec2774ae1d1c47b22e3435928cf5d9771",
|
||||
"js-utils": "github:jitsi/js-utils#df68966e3c65b5c57fcd2670da1326a2c77518d1",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"sdp-transform": "2.3.0",
|
||||
"strophe.js": "1.3.4",
|
||||
"strophejs-plugin-disco": "0.0.2",
|
||||
"strophejs-plugin-stream-management": "github:jitsi/strophejs-plugin-stream-management#e719a02b4f83856c1530882084a4b048ee622d45",
|
||||
"webrtc-adapter": "7.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"js-md5": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
|
||||
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
|
||||
},
|
||||
"js-utils": {
|
||||
"version": "github:jitsi/js-utils#8567f86ec2774ae1d1c47b22e3435928cf5d9771",
|
||||
"from": "github:jitsi/js-utils#8567f86ec2774ae1d1c47b22e3435928cf5d9771",
|
||||
"requires": {
|
||||
"bowser": "2.7.0",
|
||||
"js-md5": "0.7.3",
|
||||
"postis": "2.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libflacjs": {
|
||||
|
||||
@@ -53,10 +53,10 @@
|
||||
"jquery-contextmenu": "2.4.5",
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"js-utils": "github:jitsi/js-utils#0b2cef90613a74777fefd98d4ee3eda3879809ab",
|
||||
"js-utils": "github:jitsi/js-utils#df68966e3c65b5c57fcd2670da1326a2c77518d1",
|
||||
"jsrsasign": "8.0.12",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0d058e50633d62c7b26114418e234f4a8ae9a78e",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#f97c37d0140a0f12644ae29f4dd93757b8b8610f",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.13",
|
||||
"moment": "2.19.4",
|
||||
|
||||
@@ -257,6 +257,20 @@ export function createDeviceChangedEvent(mediaType, deviceType) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event indicating that an action related to E2EE occurred.
|
||||
*
|
||||
* @param {string} action - The action which occurred.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createE2EEEvent(action) {
|
||||
return {
|
||||
action,
|
||||
actionSubject: 'e2ee'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which specifies that the feedback dialog has been opened.
|
||||
*
|
||||
@@ -674,21 +688,6 @@ export function createStartMutedConfigurationEvent(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which indicates the delay for switching between simulcast
|
||||
* streams.
|
||||
*
|
||||
* @param {Object} attributes - Attributes to attach to the event.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createStreamSwitchDelayEvent(attributes) {
|
||||
return {
|
||||
action: 'stream.switch.delay',
|
||||
attributes
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically changing the mute state of a media track in order to match
|
||||
* the current stored state in redux.
|
||||
|
||||
@@ -79,14 +79,15 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
||||
viewBox = '0 0 100 100'
|
||||
xmlns = 'http://www.w3.org/2000/svg'
|
||||
xmlnsXlink = 'http://www.w3.org/1999/xlink'>
|
||||
<foreignObject
|
||||
height = '100%'
|
||||
width = '100%'>
|
||||
<span
|
||||
className = 'avatar-foreign'>
|
||||
{ initials }
|
||||
</span>
|
||||
</foreignObject>
|
||||
<text
|
||||
dominantBaseline = 'central'
|
||||
fill = 'rgba(255,255,255,.6)'
|
||||
fontSize = '40pt'
|
||||
textAnchor = 'middle'
|
||||
x = '50'
|
||||
y = '50'>
|
||||
{ initials }
|
||||
</text>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -31,6 +31,7 @@ import { isRoomValid } from './functions';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
conference: undefined,
|
||||
e2eeSupported: undefined,
|
||||
joining: undefined,
|
||||
leaving: undefined,
|
||||
locked: undefined,
|
||||
@@ -175,6 +176,7 @@ function _conferenceFailed(state, { conference, error }) {
|
||||
return assign(state, {
|
||||
authRequired,
|
||||
conference: undefined,
|
||||
e2eeSupported: undefined,
|
||||
error,
|
||||
joining: undefined,
|
||||
leaving: undefined,
|
||||
@@ -226,6 +228,9 @@ function _conferenceJoined(state, { conference }) {
|
||||
* @type {JitsiConference}
|
||||
*/
|
||||
conference,
|
||||
|
||||
e2eeSupported: conference.isE2EESupported(),
|
||||
|
||||
joining: undefined,
|
||||
leaving: undefined,
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import {
|
||||
areDeviceLabelsInitialized,
|
||||
getDeviceIdByLabel,
|
||||
getDeviceLabelById,
|
||||
getDevicesFromURL,
|
||||
setAudioOutputDeviceId
|
||||
} from './functions';
|
||||
@@ -216,6 +217,25 @@ export function setAudioInputDevice(deviceId) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the audio input device id and updates the settings
|
||||
* so they are persisted across sessions.
|
||||
*
|
||||
* @param {string} deviceId - The id of the new audio input device.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setAudioInputDeviceAndUpdateSettings(deviceId) {
|
||||
return function(dispatch, getState) {
|
||||
const deviceLabel = getDeviceLabelById(getState(), deviceId, 'audioInput');
|
||||
|
||||
dispatch(setAudioInputDevice(deviceId));
|
||||
dispatch(updateSettings({
|
||||
userSelectedMicDeviceId: deviceId,
|
||||
userSelectedMicDeviceLabel: deviceLabel
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the output device id.
|
||||
*
|
||||
@@ -244,6 +264,25 @@ export function setVideoInputDevice(deviceId) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the video input device id and updates the settings
|
||||
* so they are persisted across sessions.
|
||||
*
|
||||
* @param {string} deviceId - The id of the new video input device.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setVideoInputDeviceAndUpdateSettings(deviceId) {
|
||||
return function(dispatch, getState) {
|
||||
const deviceLabel = getDeviceLabelById(getState(), deviceId, 'videoInput');
|
||||
|
||||
dispatch(setVideoInputDevice(deviceId));
|
||||
dispatch(updateSettings({
|
||||
userSelectedCameraDeviceId: deviceId,
|
||||
userSelectedCameraDeviceLabel: deviceLabel
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to update the list of known audio and video devices.
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
@@ -53,7 +54,8 @@ class BaseDialog<P: Props, S: State> extends AbstractDialog<P, S> {
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback>
|
||||
<View
|
||||
<KeyboardAvoidingView
|
||||
behavior = 'height'
|
||||
style = { [
|
||||
styles.overlay,
|
||||
style
|
||||
@@ -73,7 +75,7 @@ class BaseDialog<P: Props, S: State> extends AbstractDialog<P, S> {
|
||||
</TouchableOpacity>
|
||||
{ this._renderContent() }
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ class JitsiModal extends PureComponent<Props> {
|
||||
position = { position }
|
||||
show = { _show }>
|
||||
<KeyboardAvoidingView
|
||||
behavior = 'padding'
|
||||
behavior = 'height'
|
||||
style = { [
|
||||
_headerStyles.page,
|
||||
_styles.page,
|
||||
|
||||
@@ -196,6 +196,13 @@ StateListenerRegistry.register(
|
||||
JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
|
||||
(participant, propertyName, oldValue, newValue) => {
|
||||
switch (propertyName) {
|
||||
case 'features_e2ee':
|
||||
store.dispatch(participantUpdated({
|
||||
conference,
|
||||
id: participant.getId(),
|
||||
e2eeSupported: newValue
|
||||
}));
|
||||
break;
|
||||
case 'features_jigasi':
|
||||
store.dispatch(participantUpdated({
|
||||
conference,
|
||||
|
||||
@@ -129,6 +129,8 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
// TODO Remove the following calls to APP.UI once components interested
|
||||
// in track mute changes are moved into React and/or redux.
|
||||
if (typeof APP !== 'undefined') {
|
||||
const result = next(action);
|
||||
|
||||
const { jitsiTrack } = action.track;
|
||||
const muted = jitsiTrack.isMuted();
|
||||
const participantID = jitsiTrack.getParticipantId();
|
||||
@@ -151,6 +153,8 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
} else {
|
||||
APP.UI.setAudioMuted(participantID, muted);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
JitsiConferenceErrors,
|
||||
JitsiConferenceEvents
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import { setActiveModalId } from '../base/modal';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
@@ -22,7 +23,13 @@ import { isButtonEnabled, showToolbox } from '../toolbox';
|
||||
import { SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes';
|
||||
import { addMessage, clearMessages, toggleChat } from './actions';
|
||||
import { ChatPrivacyDialog } from './components';
|
||||
import { INCOMING_MSG_SOUND_ID, MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from './constants';
|
||||
import {
|
||||
CHAT_VIEW_MODAL_ID,
|
||||
INCOMING_MSG_SOUND_ID,
|
||||
MESSAGE_TYPE_ERROR,
|
||||
MESSAGE_TYPE_LOCAL,
|
||||
MESSAGE_TYPE_REMOTE
|
||||
} from './constants';
|
||||
import { INCOMING_MSG_SOUND_FILE } from './sounds';
|
||||
|
||||
declare var APP: Object;
|
||||
@@ -94,6 +101,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
|
||||
case SET_PRIVATE_MESSAGE_RECIPIENT: {
|
||||
Boolean(action.participant) && dispatch(setActiveModalId(CHAT_VIEW_MODAL_ID));
|
||||
_maybeFocusField();
|
||||
break;
|
||||
}
|
||||
|
||||
8
react/features/e2ee/actionTypes.js
Normal file
8
react/features/e2ee/actionTypes.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* The type of the action which signals the E2EE key has changed.
|
||||
*
|
||||
* {
|
||||
* type: SET_E2EE_KEY
|
||||
* }
|
||||
*/
|
||||
export const SET_E2EE_KEY = 'SET_E2EE_KEY';
|
||||
16
react/features/e2ee/actions.js
Normal file
16
react/features/e2ee/actions.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// @flow
|
||||
|
||||
import { SET_E2EE_KEY } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Dispatches an action to set the E2EE key.
|
||||
*
|
||||
* @param {string|undefined} key - The new key to be used for E2EE.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setE2EEKey(key: ?string) {
|
||||
return {
|
||||
type: SET_E2EE_KEY,
|
||||
key
|
||||
};
|
||||
}
|
||||
76
react/features/e2ee/components/E2EEButton.js
Normal file
76
react/features/e2ee/components/E2EEButton.js
Normal file
@@ -0,0 +1,76 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { createE2EEEvent, sendAnalytics } from '../../analytics';
|
||||
import { openDialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { IconRoomUnlock } from '../../base/icons';
|
||||
import { connect } from '../../base/redux';
|
||||
import { AbstractButton, BetaTag } from '../../base/toolbox';
|
||||
import type { AbstractButtonProps } from '../../base/toolbox';
|
||||
|
||||
import E2EEDialog from './E2EEDialog';
|
||||
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Function
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Button that open a dialog to set the E2EE key.
|
||||
*/
|
||||
class E2EEButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.e2ee';
|
||||
icon = IconRoomUnlock;
|
||||
label = 'toolbar.e2ee';
|
||||
tooltip = 'toolbar.e2ee';
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which returns
|
||||
* a React Element to display (a beta tag) at the end of the button.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_getElementAfter() {
|
||||
return <BetaTag />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the E2EE dialog.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
sendAnalytics(createE2EEEvent('dialog.open'));
|
||||
this.props.dispatch(openDialog(E2EEDialog));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Props} ownProps - The own props of the Component.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function mapStateToProps(state: Object, ownProps: Props) {
|
||||
const { e2eeSupported } = state['features/base/conference'];
|
||||
const { visible = Boolean(e2eeSupported) } = ownProps;
|
||||
|
||||
return {
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export default translate(connect(mapStateToProps)(E2EEButton));
|
||||
156
react/features/e2ee/components/E2EEDialog.js
Normal file
156
react/features/e2ee/components/E2EEDialog.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
import { FieldTextStateless as TextField } from '@atlaskit/field-text';
|
||||
|
||||
import { createE2EEEvent, sendAnalytics } from '../../analytics';
|
||||
import { Dialog } from '../../base/dialog';
|
||||
import { translate, translateToHTML } from '../../base/i18n';
|
||||
import { getParticipants } from '../../base/participants';
|
||||
import { connect } from '../../base/redux';
|
||||
|
||||
import { setE2EEKey } from '../actions';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Indicates whether all participants in the conference currently support E2EE.
|
||||
*/
|
||||
_everyoneSupportsE2EE: boolean,
|
||||
|
||||
/**
|
||||
* The current E2EE key.
|
||||
*/
|
||||
_key: string,
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The current E2EE key.
|
||||
*/
|
||||
key: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@code Component} for displaying a dialog with a field
|
||||
* for setting the E2EE key.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class E2EEDialog extends Component<Props, State> {
|
||||
/**
|
||||
* Initializes a new {@code E2EEDialog } instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
key: this.props._key
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onKeyChange = this._onKeyChange.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _everyoneSupportsE2EE, t } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isModal = { false }
|
||||
onSubmit = { this._onSubmit }
|
||||
titleKey = 'dialog.e2eeTitle'
|
||||
width = 'small'>
|
||||
<div className = 'e2ee-destription'>
|
||||
{ translateToHTML(t, 'dialog.e2eeDescription') }
|
||||
</div>
|
||||
{
|
||||
!_everyoneSupportsE2EE
|
||||
&& <div className = 'e2ee-warn'>
|
||||
{ translateToHTML(t, 'dialog.e2eeWarning') }
|
||||
</div>
|
||||
}
|
||||
<TextField
|
||||
autoFocus = { true }
|
||||
compact = { true }
|
||||
label = { t('dialog.e2eeLabel') }
|
||||
name = 'e2eeKey'
|
||||
onChange = { this._onKeyChange }
|
||||
shouldFitContainer = { true }
|
||||
type = 'password'
|
||||
value = { this.state.key } />
|
||||
</Dialog>);
|
||||
}
|
||||
|
||||
_onKeyChange: (Object) => void;
|
||||
|
||||
/**
|
||||
* Updates the entered key.
|
||||
*
|
||||
* @param {Object} event - The DOM event triggered from the entered value having changed.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyChange(event) {
|
||||
this.setState({ key: event.target.value.trim() });
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
|
||||
/**
|
||||
* Dispatches an action to update the E2EE key.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onSubmit() {
|
||||
const { key } = this.state;
|
||||
|
||||
sendAnalytics(createE2EEEvent(`key.${key ? 'set' : 'unset'}`));
|
||||
this.props.dispatch(setE2EEKey(key));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
const { e2eeKey } = state['features/e2ee'];
|
||||
const participants = getParticipants(state).filter(p => !p.local);
|
||||
|
||||
return {
|
||||
_everyoneSupportsE2EE: participants.every(p => Boolean(p.e2eeSupported)),
|
||||
_key: e2eeKey || ''
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(E2EEDialog));
|
||||
2
react/features/e2ee/components/index.js
Normal file
2
react/features/e2ee/components/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as E2EEButton } from './E2EEButton';
|
||||
export { default as E2EEDialog } from './E2EEDialog';
|
||||
6
react/features/e2ee/index.js
Normal file
6
react/features/e2ee/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
5
react/features/e2ee/logger.js
Normal file
5
react/features/e2ee/logger.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/e2ee');
|
||||
43
react/features/e2ee/middleware.js
Normal file
43
react/features/e2ee/middleware.js
Normal file
@@ -0,0 +1,43 @@
|
||||
// @flow
|
||||
|
||||
import { getCurrentConference } from '../base/conference';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
||||
|
||||
import { SET_E2EE_KEY } from './actionTypes';
|
||||
import { setE2EEKey } from './actions';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Middleware that captures actions related to E2EE.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(({ getState }) => next => action => {
|
||||
switch (action.type) {
|
||||
case SET_E2EE_KEY: {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
if (conference) {
|
||||
logger.debug(`New E2EE key: ${action.key}`);
|
||||
conference.setE2EEKey(action.key);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Set up state change listener to perform maintenance tasks when the conference
|
||||
* is left or failed.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
state => getCurrentConference(state),
|
||||
(conference, { dispatch }, previousConference) => {
|
||||
if (previousConference) {
|
||||
dispatch(setE2EEKey(undefined));
|
||||
}
|
||||
});
|
||||
29
react/features/e2ee/reducer.js
Normal file
29
react/features/e2ee/reducer.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// @flow
|
||||
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import { SET_E2EE_KEY } from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
|
||||
/**
|
||||
* E2EE key.
|
||||
*/
|
||||
e2eeKey: undefined
|
||||
};
|
||||
|
||||
/**
|
||||
* Reduces the Redux actions of the feature features/e2ee.
|
||||
*/
|
||||
ReducerRegistry.register('features/e2ee', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case SET_E2EE_KEY:
|
||||
return {
|
||||
...state,
|
||||
e2eeKey: action.key
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
||||
@@ -1,7 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../base/media';
|
||||
import { getLocalParticipant } from '../base/participants';
|
||||
import { StateListenerRegistry } from '../base/redux';
|
||||
import { getTrackByMediaTypeAndParticipant } from '../base/tracks';
|
||||
import { appendSuffix } from '../display-name';
|
||||
import { shouldDisplayTileView } from '../video-layout';
|
||||
|
||||
@@ -37,3 +39,18 @@ StateListenerRegistry.register(
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates the on stage participant value.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/large-video'].participantId,
|
||||
/* listener */ (participantId, store) => {
|
||||
const videoTrack = getTrackByMediaTypeAndParticipant(
|
||||
store.getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, participantId);
|
||||
|
||||
if (videoTrack && videoTrack.videoType === VIDEO_TYPE.CAMERA) {
|
||||
APP.API.notifyOnStageParticipantChanged(participantId);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { StateListenerRegistry, equals } from '../base/redux';
|
||||
import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
|
||||
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
|
||||
import { getCurrentLayout, getTileViewGridDimensions, shouldDisplayTileView, LAYOUTS } from '../video-layout';
|
||||
|
||||
import { setHorizontalViewDimensions, setTileViewDimensions } from './actions';
|
||||
@@ -56,3 +57,22 @@ StateListenerRegistry.register(
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles on stage participant updates.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/large-video'].participantId,
|
||||
/* listener */ (participantId, store, oldParticipantId) => {
|
||||
const newThumbnail = VideoLayout.getSmallVideo(participantId);
|
||||
const oldThumbnail = VideoLayout.getSmallVideo(oldParticipantId);
|
||||
|
||||
if (newThumbnail) {
|
||||
newThumbnail.updateView();
|
||||
}
|
||||
|
||||
if (oldThumbnail) {
|
||||
oldThumbnail.updateView();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -63,7 +63,8 @@ class LargeVideo extends Component<Props> {
|
||||
<video
|
||||
autoPlay = { !this.props._noAutoPlayVideo }
|
||||
id = 'largeVideo'
|
||||
muted = { true } />
|
||||
muted = { true }
|
||||
playsInline = { true } /* for Safari on iOS to work */ />
|
||||
</div>
|
||||
</div>
|
||||
{ interfaceConfig.DISABLE_TRANSCRIPTION_SUBTITLES
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconMenuDown } from '../../../base/icons';
|
||||
@@ -62,8 +64,17 @@ class PictureInPictureButton extends AbstractButton<Props, *> {
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
const flag = Boolean(getFeatureFlag(state, PIP_ENABLED));
|
||||
let enabled = flag;
|
||||
|
||||
// Override flag for Android < 26, PiP was introduced in Oreo.
|
||||
// https://developer.android.com/guide/topics/ui/picture-in-picture
|
||||
if (Platform.OS === 'android' && Platform.Version < 26) {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
return {
|
||||
_enabled: Boolean(getFeatureFlag(state, PIP_ENABLED))
|
||||
_enabled: enabled
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { toggleAudioSettings } from '../../../actions';
|
||||
import {
|
||||
getAudioInputDeviceData,
|
||||
getAudioOutputDeviceData,
|
||||
setAudioInputDevice as setAudioInputDeviceAction,
|
||||
setAudioInputDeviceAndUpdateSettings,
|
||||
setAudioOutputDevice as setAudioOutputDeviceAction
|
||||
} from '../../../../base/devices';
|
||||
import { connect } from '../../../../base/redux';
|
||||
@@ -90,7 +90,7 @@ function mapStateToProps(state) {
|
||||
|
||||
const mapDispatchToProps = {
|
||||
onClose: toggleAudioSettings,
|
||||
setAudioInputDevice: setAudioInputDeviceAction,
|
||||
setAudioInputDevice: setAudioInputDeviceAndUpdateSettings,
|
||||
setAudioOutputDevice: setAudioOutputDeviceAction
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import InlineDialog from '@atlaskit/inline-dialog';
|
||||
import { toggleVideoSettings } from '../../../actions';
|
||||
import {
|
||||
getVideoDeviceIds,
|
||||
setVideoInputDevice as setVideoInputDeviceAction
|
||||
setVideoInputDeviceAndUpdateSettings
|
||||
} from '../../../../base/devices';
|
||||
import { getVideoSettingsVisibility } from '../../../functions';
|
||||
import { connect } from '../../../../base/redux';
|
||||
@@ -79,7 +79,7 @@ function mapStateToProps(state) {
|
||||
|
||||
const mapDispatchToProps = {
|
||||
onClose: toggleVideoSettings,
|
||||
setVideoInputDevice: setVideoInputDeviceAction
|
||||
setVideoInputDevice: setVideoInputDeviceAndUpdateSettings
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(VideoSettingsPopup);
|
||||
|
||||
@@ -33,6 +33,7 @@ import { OverflowMenuItem } from '../../../base/toolbox';
|
||||
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
|
||||
import { VideoBlurButton } from '../../../blur';
|
||||
import { ChatCounter, toggleChat } from '../../../chat';
|
||||
import { E2EEButton } from '../../../e2ee';
|
||||
import { SharedDocumentButton } from '../../../etherpad';
|
||||
import { openFeedbackDialog } from '../../../feedback';
|
||||
import {
|
||||
@@ -1012,6 +1013,10 @@ class Toolbox extends Component<Props, State> {
|
||||
key = 'stats'
|
||||
onClick = { this._onToolbarOpenSpeakerStats }
|
||||
text = { t('toolbar.speakerStats') } />,
|
||||
this._shouldShowButton('e2ee')
|
||||
&& <E2EEButton
|
||||
key = 'e2ee'
|
||||
showLabel = { true } />,
|
||||
this._shouldShowButton('feedback')
|
||||
&& _feedbackConfigured
|
||||
&& <OverflowMenuItem
|
||||
|
||||
@@ -50,13 +50,18 @@ if [ -f /etc/nginx/sites-enabled/$DOMAIN.conf ] ; then
|
||||
cp /usr/share/jitsi-meet-turnserver/coturn-certbot-deploy.sh $TURN_HOOK
|
||||
chmod u+x $TURN_HOOK
|
||||
sed -i "s/jitsi-meet.example.com/$DOMAIN/g" $TURN_HOOK
|
||||
fi
|
||||
|
||||
./certbot-auto certonly --noninteractive \
|
||||
--webroot --webroot-path /usr/share/jitsi-meet \
|
||||
-d $DOMAIN \
|
||||
--agree-tos --email $EMAIL \
|
||||
--deploy-hook $TURN_HOOK
|
||||
./certbot-auto certonly --noninteractive \
|
||||
--webroot --webroot-path /usr/share/jitsi-meet \
|
||||
-d $DOMAIN \
|
||||
--agree-tos --email $EMAIL \
|
||||
--deploy-hook $TURN_HOOK
|
||||
else
|
||||
./certbot-auto certonly --noninteractive \
|
||||
--webroot --webroot-path /usr/share/jitsi-meet \
|
||||
-d $DOMAIN \
|
||||
--agree-tos --email $EMAIL
|
||||
fi
|
||||
|
||||
echo "Configuring nginx"
|
||||
|
||||
|
||||
@@ -53,10 +53,6 @@ export default {
|
||||
*/
|
||||
LOCAL_FLIPX_CHANGED: 'UI.local_flipx_changed',
|
||||
|
||||
// An event which indicates that the resolution of a remote video has
|
||||
// changed.
|
||||
RESOLUTION_CHANGED: 'UI.resolution_changed',
|
||||
|
||||
/**
|
||||
* Notifies that the button "Cancel" is pressed on the dialog for
|
||||
* external extension installation.
|
||||
|
||||
Reference in New Issue
Block a user