Compare commits

..

1 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
d3fea0cd25 doc: update supported platforms for building mobile apps 2020-04-12 20:18:54 +02:00
51 changed files with 285 additions and 600 deletions

10
.github/ISSUE_TEMPLATE/2-help.md vendored Normal file
View File

@@ -0,0 +1,10 @@
---
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.

View File

@@ -1,9 +1,11 @@
# Security
---
name: Security issues
about: Please email security@jitsi.org
## 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.

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Need help with Jitsi Meet?
url: https://community.jitsi.org
about: Please ask it in our community.

View File

@@ -75,7 +75,11 @@ at [8x8](https://8x8.com).
## Security issues
For information on reporting security vulnerabilities in Jitsi Meet, see [SECURITY.md](./SECURITY.md).
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.**
## Acknowledgements

View File

@@ -20,5 +20,5 @@
android.useAndroidX=true
android.enableJetifier=true
appVersion=20.3.0
appVersion=20.2.0
sdkVersion=2.8.0

View File

@@ -17,8 +17,6 @@
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;
@@ -42,8 +40,6 @@ 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);
@@ -62,8 +58,8 @@ class AmplitudeModule
Amplitude.getInstance(instanceName).initialize(getCurrentActivity(), apiKey);
// Set the device ID to something consistent.
SharedPreferences sharedPreferences = getReactApplicationContext().getSharedPreferences(JITSI_PREFERENCES, Context.MODE_PRIVATE);
String android_id = sharedPreferences.getString(AMPLITUDE_DEVICE_ID_KEY, "");
String android_id
= Settings.Secure.getString(getReactApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
if (!TextUtils.isEmpty(android_id)) {
Amplitude.getInstance(instanceName).setDeviceId(android_id);
}

View File

@@ -19,6 +19,7 @@ import {
createDeviceChangedEvent,
createStartSilentEvent,
createScreenSharingEvent,
createStreamSwitchDelayEvent,
createTrackMutedEvent,
sendAnalytics
} from './react/features/analytics';
@@ -112,7 +113,6 @@ 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,6 +470,11 @@ 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
@@ -1197,14 +1202,11 @@ export default {
items[key] = param[1];
}
if (typeof items.e2eekey !== undefined) {
APP.store.dispatch(setE2EEKey(items.e2eekey));
this.e2eeKey = items.e2eekey;
// Clean URL in browser history.
const cleanUrl = window.location.href.split('#')[0];
logger.debug(`New E2EE key: ${this.e2eeKey}`);
history.replaceState(history.state, document.title, cleanUrl);
}
this._room.setE2EEKey(this.e2eeKey);
},
/**
@@ -1816,7 +1818,7 @@ export default {
const desktopVideoStream = streams.find(stream => stream.getType() === MEDIA_TYPE.VIDEO);
if (desktopVideoStream) {
await this.useVideoStream(desktopVideoStream);
this.useVideoStream(desktopVideoStream);
}
this._desktopAudioStream = streams.find(stream => stream.getType() === MEDIA_TYPE.AUDIO);
@@ -2262,6 +2264,18 @@ 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);
});

View File

@@ -44,6 +44,9 @@ 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
@@ -448,14 +451,6 @@ 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

View File

@@ -85,15 +85,14 @@ const options = {
const api = new JitsiMeetExternalAPI(domain, options);
```
You can set the userInfo(email, display name) for the call:
You can set the userInfo(email) for the call:
```javascript
var domain = "meet.jit.si";
var options = {
...
userInfo: {
email: 'email@jitsiexamplemail.com',
displayName: 'John Doe'
email: 'email@jitsiexamplemail.com'
}
}
var api = new JitsiMeetExternalAPI(domain, options);

View File

@@ -69,7 +69,7 @@ Use it at the CLI, type
make dev
```
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:
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:
```
export WEBPACK_DEV_SERVER_PROXY_TARGET=https://your-example-server.com
make dev

View File

@@ -51,8 +51,7 @@ 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',
'e2ee'
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone'
],
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],

View File

@@ -586,4 +586,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: f615794fb9184757b00cd16e534824ba6ee2fc98
COCOAPODS: 1.9.1
COCOAPODS: 1.8.4

View File

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

View File

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

View File

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

View File

@@ -175,10 +175,6 @@
"dismiss": "Dismiss",
"displayNameRequired": "Hi! Whats 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.",
@@ -599,7 +595,6 @@
"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",
@@ -643,7 +638,6 @@
"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",

View File

@@ -11,7 +11,6 @@ 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';
@@ -167,10 +166,6 @@ 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 }) => {

View File

@@ -29,7 +29,6 @@ const ALWAYS_ON_TOP_FILENAMES = [
const commands = {
avatarUrl: 'avatar-url',
displayName: 'display-name',
e2eeKey: 'e2ee-key',
email: 'email',
hangup: 'video-hangup',
password: 'password',
@@ -237,8 +236,6 @@ 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();
@@ -254,8 +251,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
onload = undefined,
invitees,
devices,
userInfo,
e2eeKey
userInfo
} = parseArguments(args);
this._parentNode = parentNode;
@@ -280,7 +276,6 @@ 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 = {};
@@ -434,17 +429,11 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
const userID = data.id;
switch (name) {
case 'video-conference-joined': {
if (typeof this._tmpE2EEKey !== 'undefined') {
this.executeCommand(commands.e2eeKey, this._tmpE2EEKey);
this._tmpE2EEKey = undefined;
}
case 'video-conference-joined':
this._myUserID = userID;
this._participants[userID] = {
avatarURL: data.avatarURL
};
}
// eslint-disable-next-line no-fallthrough
case 'participant-joined': {

View File

@@ -322,6 +322,15 @@ 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');

View File

@@ -19,6 +19,7 @@ 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);

View File

@@ -27,15 +27,12 @@ const UIUtil = {
*/
prependChild(container, newChild) {
const firstChild = container.childNodes[0];
let result;
if (firstChild) {
result = container.insertBefore(newChild, firstChild);
container.insertBefore(newChild, firstChild);
} else {
result = container.appendChild(newChild);
container.appendChild(newChild);
}
return result;
},
/**

View File

@@ -90,13 +90,13 @@ export default class RemoteVideo extends SmallVideo {
this._isRemoteControlSessionActive = false;
/**
* The flag is set to <tt>true</tt> after the 'canplay' event has been
* The flag is set to <tt>true</tt> after the 'onplay' 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._canPlayEventReceived = false;
this.wasVideoPlayed = 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._canPlayEventReceived = false;
this.wasVideoPlayed = false;
}
logger.info(`${isVideo ? 'Video' : 'Audio'} removed ${this.id}`, select);
@@ -390,10 +390,13 @@ export default class RemoteVideo extends SmallVideo {
}
/**
* 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
* 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
* issues.
*
* @inheritdoc
@@ -403,7 +406,7 @@ export default class RemoteVideo extends SmallVideo {
const connectionState = APP.conference.getParticipantConnectionStatus(this.id);
return super.isVideoPlayable()
&& this._canPlayEventReceived
&& this.hasVideoStarted()
&& (connectionState === JitsiParticipantConnectionStatus.ACTIVE
|| (connectionState === JitsiParticipantConnectionStatus.INTERRUPTED && !this.mutedWhileDisconnected));
}
@@ -456,16 +459,26 @@ export default class RemoteVideo extends SmallVideo {
return;
}
streamElement.oncanplay = () => {
this._canPlayEventReceived = true;
streamElement.onplaying = () => {
this.wasVideoPlayed = true;
this.VideoLayout.remoteVideoActive(streamElement, this.id);
streamElement.oncanplay = undefined;
streamElement.onplaying = null;
// 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
@@ -481,16 +494,20 @@ 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;
}
let streamElement = SmallVideo.createStreamElement(stream);
const streamElement = SmallVideo.createStreamElement(stream);
// Put new stream element always in front
streamElement = UIUtils.prependChild(this.container, streamElement);
UIUtils.prependChild(this.container, streamElement);
$(streamElement).hide();

View File

@@ -32,6 +32,8 @@ 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}
@@ -156,6 +158,28 @@ 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.
*/
@@ -165,7 +189,6 @@ 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;
}
@@ -428,7 +451,7 @@ export default class SmallVideo {
* or <tt>false</tt> otherwise.
*/
isCurrentlyOnLargeVideo() {
return APP.store.getState()['features/large-video']?.participantId === this.id;
return this.VideoLayout.isCurrentlyOnLarge(this.id);
}
/**
@@ -476,7 +499,7 @@ export default class SmallVideo {
hasVideo: Boolean(this.selectVideoElement().length),
connectionStatus: APP.conference.getParticipantConnectionStatus(this.id),
mutedWhileDisconnected: this.mutedWhileDisconnected,
canPlayEventReceived: this._canPlayEventReceived,
wasVideoPlayed: this.wasVideoPlayed,
videoStream: Boolean(this.videoStream),
isVideoMuted: this.isVideoMuted,
videoStreamMuted: this.videoStream ? this.videoStream.isMuted() : 'no stream'
@@ -616,6 +639,39 @@ 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

View File

@@ -1,14 +1,12 @@
/* global APP, $, interfaceConfig */
const logger = require('jitsi-meet-logger').getLogger(__filename);
import { MEDIA_TYPE, VIDEO_TYPE } from '../../../react/features/base/media';
import { 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';
@@ -75,6 +73,9 @@ 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();
},
@@ -220,16 +221,10 @@ const VideoLayout = {
* @returns {String} the video type video or screen.
*/
getRemoteVideoType(id) {
const state = APP.store.getState();
const participant = getParticipantById(state, id);
const smallVideo = VideoLayout.getSmallVideo(id);
if (participant?.isFakeParticipant) {
return SHARED_VIDEO_CONTAINER_TYPE;
}
const videoTrack = getTrackByMediaTypeAndParticipant(state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
return videoTrack?.videoType;
return smallVideo ? smallVideo.getVideoType() : null;
},
isPinned(id) {
@@ -313,6 +308,12 @@ 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();
},
@@ -490,6 +491,22 @@ 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);
},
@@ -567,16 +584,17 @@ const VideoLayout = {
}
const currentContainer = largeVideo.getCurrentContainer();
const currentContainerType = largeVideo.getCurrentContainerType();
const currentId = largeVideo.id;
const isOnLarge = this.isCurrentlyOnLarge(id);
const state = APP.store.getState();
const videoTrack = getTrackByMediaTypeAndParticipant(state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
const videoStream = videoTrack?.jitsiTrack;
const smallVideo = this.getSmallVideo(id);
if (isOnLarge && !forceUpdate
&& LargeVideoManager.isVideoContainer(currentContainerType)
&& videoStream) {
&& smallVideo) {
const currentStreamId = currentContainer.getStreamID();
const newStreamId = videoStream?.getId() || null;
const newStreamId
= smallVideo.videoStream
? smallVideo.videoStream.getId() : null;
// FIXME it might be possible to get rid of 'forceUpdate' argument
if (currentStreamId !== newStreamId) {
@@ -585,17 +603,42 @@ const VideoLayout = {
}
}
if (!isOnLarge || forceUpdate) {
if ((!isOnLarge || forceUpdate) && smallVideo) {
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,
videoStream,
smallVideo.videoStream,
videoType || VIDEO_TYPE.CAMERA
).catch(() => {
// do nothing
).then(() => {
// update current small video and the old one
smallVideo.updateView();
oldSmallVideo && oldSmallVideo.updateView();
}, () => {
// use clicked other video during update, nothing to do.
});
} else if (currentId) {
const currentSmallVideo = this.getSmallVideo(currentId);
currentSmallVideo && currentSmallVideo.updateView();
}
},
@@ -680,6 +723,10 @@ const VideoLayout = {
this.localFlipX = val;
},
getEventEmitter() {
return eventEmitter;
},
/**
* Handles user's features changes.
*/

34
package-lock.json generated
View File

@@ -2833,9 +2833,9 @@
}
},
"@jitsi/sdp-interop": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/sdp-interop/-/sdp-interop-1.0.2.tgz",
"integrity": "sha512-3Ivl0iuGZ+R97GzgzKneuY45cVCmpYWaOc37uyg58GJK3IiY3+/kf9fdfOweyeZeB6wrNkjI8dfn+I3XQPxbkQ==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@jitsi/sdp-interop/-/sdp-interop-1.0.1.tgz",
"integrity": "sha512-OJm8IYsJKCYJBlC0geRHm2VHi8ow2k/3wOZ7n0lEOBfV6RWghZQzQWHdT/qrkbXB2EHcm40Oy91a5Bfz2m6ydA==",
"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#df68966e3c65b5c57fcd2670da1326a2c77518d1",
"from": "github:jitsi/js-utils#df68966e3c65b5c57fcd2670da1326a2c77518d1",
"version": "github:jitsi/js-utils#0b2cef90613a74777fefd98d4ee3eda3879809ab",
"from": "github:jitsi/js-utils#0b2cef90613a74777fefd98d4ee3eda3879809ab",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3",
@@ -10884,21 +10884,37 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#f97c37d0140a0f12644ae29f4dd93757b8b8610f",
"from": "github:jitsi/lib-jitsi-meet#f97c37d0140a0f12644ae29f4dd93757b8b8610f",
"version": "github:jitsi/lib-jitsi-meet#0d058e50633d62c7b26114418e234f4a8ae9a78e",
"from": "github:jitsi/lib-jitsi-meet#0d058e50633d62c7b26114418e234f4a8ae9a78e",
"requires": {
"@jitsi/sdp-interop": "1.0.2",
"@jitsi/sdp-interop": "1.0.1",
"@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#df68966e3c65b5c57fcd2670da1326a2c77518d1",
"js-utils": "github:jitsi/js-utils#8567f86ec2774ae1d1c47b22e3435928cf5d9771",
"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": {

View File

@@ -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#df68966e3c65b5c57fcd2670da1326a2c77518d1",
"js-utils": "github:jitsi/js-utils#0b2cef90613a74777fefd98d4ee3eda3879809ab",
"jsrsasign": "8.0.12",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#f97c37d0140a0f12644ae29f4dd93757b8b8610f",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0d058e50633d62c7b26114418e234f4a8ae9a78e",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.13",
"moment": "2.19.4",

View File

@@ -257,20 +257,6 @@ 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.
*
@@ -688,6 +674,21 @@ 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.

View File

@@ -31,7 +31,6 @@ import { isRoomValid } from './functions';
const DEFAULT_STATE = {
conference: undefined,
e2eeSupported: undefined,
joining: undefined,
leaving: undefined,
locked: undefined,
@@ -176,7 +175,6 @@ function _conferenceFailed(state, { conference, error }) {
return assign(state, {
authRequired,
conference: undefined,
e2eeSupported: undefined,
error,
joining: undefined,
leaving: undefined,
@@ -228,9 +226,6 @@ function _conferenceJoined(state, { conference }) {
* @type {JitsiConference}
*/
conference,
e2eeSupported: conference.isE2EESupported(),
joining: undefined,
leaving: undefined,

View File

@@ -17,7 +17,6 @@ import {
import {
areDeviceLabelsInitialized,
getDeviceIdByLabel,
getDeviceLabelById,
getDevicesFromURL,
setAudioOutputDeviceId
} from './functions';
@@ -217,25 +216,6 @@ 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.
*
@@ -264,25 +244,6 @@ 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.
*

View File

@@ -107,7 +107,7 @@ class JitsiModal extends PureComponent<Props> {
position = { position }
show = { _show }>
<KeyboardAvoidingView
behavior = 'height'
behavior = 'padding'
style = { [
_headerStyles.page,
_styles.page,

View File

@@ -196,13 +196,6 @@ 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,

View File

@@ -129,8 +129,6 @@ 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();
@@ -153,8 +151,6 @@ MiddlewareRegistry.register(store => next => action => {
} else {
APP.UI.setAudioMuted(participantID, muted);
}
return result;
}
}

View File

@@ -1,8 +0,0 @@
/**
* The type of the action which signals the E2EE key has changed.
*
* {
* type: SET_E2EE_KEY
* }
*/
export const SET_E2EE_KEY = 'SET_E2EE_KEY';

View File

@@ -1,16 +0,0 @@
// @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
};
}

View File

@@ -1,76 +0,0 @@
// @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));

View File

@@ -1,156 +0,0 @@
/* @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));

View File

@@ -1,2 +0,0 @@
export { default as E2EEButton } from './E2EEButton';
export { default as E2EEDialog } from './E2EEDialog';

View File

@@ -1,6 +0,0 @@
export * from './actions';
export * from './actionTypes';
export * from './components';
import './middleware';
import './reducer';

View File

@@ -1,5 +0,0 @@
// @flow
import { getLogger } from '../base/logging/functions';
export default getLogger('features/e2ee');

View File

@@ -1,43 +0,0 @@
// @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));
}
});

View File

@@ -1,29 +0,0 @@
// @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;
}
});

View File

@@ -1,9 +1,7 @@
// @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';
@@ -39,18 +37,3 @@ 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);
}
}
);

View File

@@ -2,7 +2,6 @@
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';
@@ -57,22 +56,3 @@ 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();
}
}
);

View File

@@ -63,8 +63,7 @@ class LargeVideo extends Component<Props> {
<video
autoPlay = { !this.props._noAutoPlayVideo }
id = 'largeVideo'
muted = { true }
playsInline = { true } /* for Safari on iOS to work */ />
muted = { true } />
</div>
</div>
{ interfaceConfig.DISABLE_TRANSCRIPTION_SUBTITLES

View File

@@ -1,7 +1,5 @@
// @flow
import { Platform } from 'react-native';
import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
import { translate } from '../../../base/i18n';
import { IconMenuDown } from '../../../base/icons';
@@ -64,17 +62,8 @@ 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: enabled
_enabled: Boolean(getFeatureFlag(state, PIP_ENABLED))
};
}

View File

@@ -8,7 +8,7 @@ import { toggleAudioSettings } from '../../../actions';
import {
getAudioInputDeviceData,
getAudioOutputDeviceData,
setAudioInputDeviceAndUpdateSettings,
setAudioInputDevice as setAudioInputDeviceAction,
setAudioOutputDevice as setAudioOutputDeviceAction
} from '../../../../base/devices';
import { connect } from '../../../../base/redux';
@@ -90,7 +90,7 @@ function mapStateToProps(state) {
const mapDispatchToProps = {
onClose: toggleAudioSettings,
setAudioInputDevice: setAudioInputDeviceAndUpdateSettings,
setAudioInputDevice: setAudioInputDeviceAction,
setAudioOutputDevice: setAudioOutputDeviceAction
};

View File

@@ -6,7 +6,7 @@ import InlineDialog from '@atlaskit/inline-dialog';
import { toggleVideoSettings } from '../../../actions';
import {
getVideoDeviceIds,
setVideoInputDeviceAndUpdateSettings
setVideoInputDevice as setVideoInputDeviceAction
} from '../../../../base/devices';
import { getVideoSettingsVisibility } from '../../../functions';
import { connect } from '../../../../base/redux';
@@ -79,7 +79,7 @@ function mapStateToProps(state) {
const mapDispatchToProps = {
onClose: toggleVideoSettings,
setVideoInputDevice: setVideoInputDeviceAndUpdateSettings
setVideoInputDevice: setVideoInputDeviceAction
};
export default connect(mapStateToProps, mapDispatchToProps)(VideoSettingsPopup);

View File

@@ -33,7 +33,6 @@ 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 {
@@ -1013,10 +1012,6 @@ 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

View File

@@ -50,19 +50,14 @@ 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
./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
./certbot-auto certonly --noninteractive \
--webroot --webroot-path /usr/share/jitsi-meet \
-d $DOMAIN \
--agree-tos --email $EMAIL \
--deploy-hook $TURN_HOOK
echo "Configuring nginx"
CONF_FILE="/etc/nginx/sites-available/$DOMAIN.conf"

View File

@@ -53,6 +53,10 @@ 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.