Compare commits

..

10 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
fbdd1d2f38 settings: fix loading disableCallIntegration 2019-11-08 12:22:41 +01:00
Saúl Ibarra Corretgé
53ebab33b3 android: fix selecting the Bluetooth route
Samsung devices (of course) seem to stick with the earpiece if we first select
Bluetooth but then set speaker to false. Reverse the order to make everyone
happy.

This only applies to the generic and legacy handlers.
2019-11-08 12:22:41 +01:00
Saúl Ibarra Corretgé
a36965e908 android: fix initializing audio device handler modules too early
When ConnectionService is used (the default) we were attaching the handlers too
early, and since attaching them requires that the RNConnectionService module is
loaded, it silently failed. Instead, use the initialize() method, which gets
called after all the Catalyst (aka native) modules have been loaded.
2019-11-08 12:22:41 +01:00
Saúl Ibarra Corretgé
9bf859ee50 android: log a warning if listeners could not be attached 2019-11-08 12:22:41 +01:00
Saúl Ibarra Corretgé
24ff6f58a5 android: make code a bit more readable 2019-11-08 12:22:41 +01:00
Saúl Ibarra Corretgé
6338624095 android,hack: send custom UA to fool spartan nginx config on meet.jit.si 2019-11-07 15:28:04 +01:00
Saúl Ibarra Corretgé
9634d58d4e Merge branch 'master' into mobile-19.4 2019-11-07 08:40:06 +01:00
Saúl Ibarra Corretgé
95a6f1355f uri: avoid using String.prototype.normalize
It crashes on Android. Well, on the JSC version React Native uses on Android.

While we could use this fallback only on Android, we have decided to use it
on all mobile platforms for consistency.
2019-11-06 15:37:52 +01:00
Saúl Ibarra Corretgé
fd3fb7ef55 Merge branch 'master' into mobile-19.4 2019-10-31 16:45:37 +01:00
Saúl Ibarra Corretgé
6909c6a0f4 android: fix SDK release script for new dependency syntax
Skip the first character, since it's now like ^123456.0.0
2019-10-21 11:13:13 +02:00
79 changed files with 577 additions and 8022 deletions

View File

@@ -27,18 +27,10 @@ You can download Debian/Ubuntu binaries:
You can download source archives (produced by ```make source-package```):
* [source builds](https://download.jitsi.org/jitsi-meet/src/)
### Mobile apps
You can get our mobile versions from here:
* [Android](https://play.google.com/store/apps/details?id=org.jitsi.meet)
* [iOS](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
You can also sign up for our open beta testing here:
* [Android](https://play.google.com/apps/testing/org.jitsi.meet)
* [iOS](https://testflight.apple.com/join/isy6ja7S)
## Development
For web development see [here](doc/development.md), and for mobile see [here](doc/mobile.md).

View File

@@ -24,7 +24,7 @@ platform :android do
# Upload built artifact to the Closed Beta track
upload_to_play_store(
track: "beta",
track: "Closed Beta",
json_key: ENV["JITSI_JSON_KEY_FILE"],
skip_upload_metadata: true,
skip_upload_images: true,

View File

@@ -20,5 +20,5 @@
android.useAndroidX=true
android.enableJetifier=true
appVersion=19.5.0
sdkVersion=2.5.0
appVersion=19.4.0
sdkVersion=2.4.0

View File

@@ -349,7 +349,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
urlProps.putString("jwt", token);
}
if (userInfo != null) {
if (token == null && userInfo != null) {
props.putBundle("userInfo", userInfo.asBundle());
}

View File

View File

@@ -1205,16 +1205,12 @@ export default {
_getConferenceOptions() {
const options = config;
const { email, name: nick } = getLocalParticipant(APP.store.getState());
const nick = APP.store.getState()['features/base/settings'].displayName;
const { locationURL } = APP.store.getState()['features/base/connection'];
if (options.enableDisplayNameInStats && nick) {
options.statisticsDisplayName = nick;
}
if (options.enableEmailInStats && email) {
options.statisticsId = email;
if (nick) {
options.displayName = nick;
}
options.applicationName = interfaceConfig.APP_NAME;

View File

@@ -292,11 +292,13 @@ var config = {
// callStatsID: '',
// callStatsSecret: '',
// enables callstatsUsername to be reported as statsId and used
// by callstats as repoted remote id
// enableStatsID: false
// enables sending participants display name to callstats
// enableDisplayNameInStats: false
// enables sending participants email if available to callstats and other analytics
// enableEmailInStats: false
// Privacy
//

View File

@@ -205,10 +205,6 @@
border-radius: 6px 0px 6px 6px;
}
.usermessage {
white-space: pre-wrap;
}
&.error {
border-radius: 0px;
@@ -230,8 +226,6 @@
.messagecontent {
margin: 5px 10px;
max-width: 100%;
overflow: hidden;
}
}

View File

@@ -489,8 +489,6 @@
height: 300px;
margin: auto;
position: relative;
top: 50%;
transform: translateY(-50%);
}
#mixedstream {

View File

@@ -30,7 +30,6 @@ Its constructor gets a number of options:
* **onload**: (optional) handler for the iframe onload event.
* **invitees**: (optional) Array of objects containing information about new participants that will be invited in the call.
* **devices**: (optional) A map containing information about the initial devices that will be used in the call.
* **userInfo**: (optional) JS object containing information about the participant opening the meeting, such as `email`.
Example:
@@ -85,19 +84,6 @@ const options = {
const api = new JitsiMeetExternalAPI(domain, options);
```
You can set the userInfo(email) for the call:
```javascript
var domain = "meet.jit.si";
var options = {
...
userInfo: {
email: 'email@jitsiexamplemail.com'
}
}
var api = new JitsiMeetExternalAPI(domain, options);
```
### Controlling the embedded Jitsi Meet Conference
Device management `JitsiMeetExternalAPI` methods:

View File

@@ -45,7 +45,7 @@ server {
proxy_set_header Host $http_host;
}
location ~ ^/([^/?&:'"]+)$ {
location ~ ^/([^?&:’“]+)$ {
try_files $uri @root_path;
}

View File

View File

@@ -1,6 +1,5 @@
<html itemscope itemtype="http://schema.org/Product" prefix="og: http://ogp.me/ns#" xmlns="http://www.w3.org/1999/html">
<head>
<!--#include virtual="head.html" -->
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -152,7 +151,6 @@
<!--#include virtual="static/settingsToolbarAdditionalContent.html" -->
</head>
<body>
<!--#include virtual="body.html" -->
<div id="react"></div>
</body>
</html>

View File

@@ -1,4 +1,4 @@
platform :ios, '11.0'
platform :ios, '10.0'
workspace 'jitsi-meet'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
@@ -82,7 +82,7 @@ post_install do |installer|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'YES'
config.build_settings['SUPPORTS_MACCATALYST'] = 'NO'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0'
end
end
end

View File

@@ -274,7 +274,7 @@ PODS:
- React
- react-native-netinfo (4.1.5):
- React
- react-native-webrtc (1.75.2):
- react-native-webrtc (1.75.0):
- React
- react-native-webview (7.4.1):
- React
@@ -534,7 +534,7 @@ SPEC CHECKSUMS:
react-native-calendar-events: 2fe35a9294af05de0ed819d3a1b5dac048d2c010
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
react-native-webrtc: f6783727706d8bec5fb302b76eda60c33dfe3191
react-native-webrtc: c5e3d631179a933548a8e49bddbd8fad02586095
react-native-webview: 4dbc1d2a4a6b9c5e9e723c62651917aa2b5e579e
React-RCTActionSheet: 94671eef55b01a93be735605822ef712d5ea208e
React-RCTAnimation: 524ae33e73de9c0fe6501a7a4bda8e01d26499d9
@@ -553,6 +553,6 @@ SPEC CHECKSUMS:
RNWatch: 09738b339eceb66e4d80a2371633ca5fb380fa42
Yoga: 02036f6383c0008edb7ef0773a0e6beb6ce82bd1
PODFILE CHECKSUM: 0fdfa45ae809c9460c80be3e0d4bbb822fccc418
PODFILE CHECKSUM: 63c90b1d33cd96709fb72bad6be440ae9c3deecb
COCOAPODS: 1.8.1

View File

@@ -747,6 +747,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
INFOPLIST_FILE = src/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = (
@@ -757,6 +758,7 @@
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet;
PRODUCT_NAME = "jitsi-meet";
PROVISIONING_PROFILE_SPECIFIER = "";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -781,6 +783,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
INFOPLIST_FILE = src/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = (
@@ -791,6 +794,7 @@
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet;
PRODUCT_NAME = "jitsi-meet";
PROVISIONING_PROFILE_SPECIFIER = "";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@@ -846,11 +850,10 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -899,11 +902,10 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>19.5.0</string>
<string>19.4.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>19.5.0</string>
<string>19.4.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>19.5.0</string>
<string>19.4.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CLKComplicationPrincipalClass</key>

View File

@@ -61,21 +61,7 @@ platform :ios do
)
# Upload the build to TestFlight (but don't distribute it)
upload_to_testflight(
beta_app_description: ENV["JITSI_CHANGELOG"],
beta_app_feedback_email: ENV["JITSI_REVIEW_EMAIL"],
beta_app_review_info: {
contact_email: ENV["JITSI_REVIEW_EMAIL"],
demo_account_name: ENV["JITSI_DEMO_ACCOUNT"],
demo_account_password: ENV["JITSI_DEMO_PASSWORD"],
},
changelog: ENV["JITSI_CHANGELOG"],
demo_account_required: false,
distribute_external: true,
groups: ENV["JITSI_BETA_TESTING_GROUPS"],
reject_build_waiting_for_review: true,
uses_non_exempt_encryption: false
)
upload_to_testflight(skip_submission: true, skip_waiting_for_build_processing: true)
# Cleanup
clean_build_artifacts

View File

@@ -25,7 +25,7 @@ popd
# Build the SDK
pushd ${PROJECT_REPO}
rm -rf ios/sdk/JitsiMeet.framework
xcodebuild -workspace ios/jitsi-meet.xcworkspace -scheme JitsiMeet -destination='generic/platform=iOS' -configuration Release ENABLE_BITCODE=NO clean archive
xcodebuild -workspace ios/jitsi-meet.xcworkspace -scheme JitsiMeet -destination='generic/platform=iOS' -configuration Release archive
if [[ $DO_GIT_TAG == 1 ]]; then
git tag ios-sdk-${SDK_VERSION}
fi

View File

@@ -555,7 +555,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -611,7 +611,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -639,6 +639,7 @@
ENABLE_BITCODE = NO;
INFOPLIST_FILE = src/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -649,6 +650,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -669,6 +671,7 @@
ENABLE_BITCODE = YES;
INFOPLIST_FILE = src/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -678,6 +681,7 @@
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};

View File

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

View File

@@ -225,7 +225,7 @@ static NSString *const WelcomePageEnabledFeatureFlag = @"welcomepage.enabled";
urlProps[@"jwt"] = _token;
}
if (_userInfo != nil) {
if (_token == nil && _userInfo != nil) {
props[@"userInfo"] = [self.userInfo asDict];
}

View File

@@ -47,10 +47,8 @@
},
"chat": {
"error": "Error: your message was not sent. Reason: {{error}}",
"fieldPlaceHolder": "Type your message here",
"messagebox": "Type a message",
"messageTo": "Private message to {{recipient}}",
"noMessagesMessage": "There are no messages in the meeting yet. Start a conversation here!",
"nickname": {
"popover": "Choose a nickname",
"title": "Enter a nickname to use chat"

View File

@@ -232,8 +232,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* information about new participants that will be invited in the call.
* @param {Array<Object>} [options.devices] - Array of objects containing
* 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.
*/
constructor(domain, ...args) {
super();
@@ -248,8 +246,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
jwt = undefined,
onload = undefined,
invitees,
devices,
userInfo
devices
} = parseArguments(args);
this._parentNode = parentNode;
@@ -259,8 +256,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
jwt,
noSSL,
roomName,
devices,
userInfo
devices
});
this._createIFrame(height, width, onload);
this._transport = new Transport({

View File

@@ -683,8 +683,8 @@ export default class LargeVideoManager {
}
/**
* Dispatches an action to update the known resolution state of the large video and adjusts container sizes when the
* resolution changes.
* Dispatches an action to update the known resolution state of the
* large video and adjusts container sizes when the resolution changes.
*
* @private
* @returns {void}
@@ -697,7 +697,7 @@ export default class LargeVideoManager {
APP.store.dispatch(updateKnownLargeVideoResolution(height));
}
const currentAspectRatio = height === 0 ? 0 : width / height;
const currentAspectRatio = width / height;
if (this._videoAspectRatio !== currentAspectRatio) {
this._videoAspectRatio = currentAspectRatio;

View File

@@ -5,8 +5,10 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { browser } from '../../../react/features/base/lib-jitsi-meet';
import { ORIENTATION, LargeVideoBackground } from '../../../react/features/large-video';
import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
import {
ORIENTATION,
LargeVideoBackground
} from '../../../react/features/large-video';
/* eslint-enable no-unused-vars */
import Filmstrip from './Filmstrip';
@@ -53,12 +55,8 @@ function computeDesktopVideoSize( // eslint-disable-line max-params
videoHeight,
videoSpaceWidth,
videoSpaceHeight) {
if (videoWidth === 0 || videoHeight === 0 || videoSpaceWidth === 0 || videoSpaceHeight === 0) {
// Avoid NaN values caused by devision by 0.
return [ 0, 0 ];
}
const aspectRatio = videoWidth / videoHeight;
let availableWidth = Math.max(videoWidth, videoSpaceWidth);
let availableHeight = Math.max(videoHeight, videoSpaceHeight);
@@ -101,11 +99,6 @@ function computeCameraVideoSize( // eslint-disable-line max-params
videoSpaceWidth,
videoSpaceHeight,
videoLayoutFit) {
if (videoWidth === 0 || videoHeight === 0 || videoSpaceWidth === 0 || videoSpaceHeight === 0) {
// Avoid NaN values caused by devision by 0.
return [ 0, 0 ];
}
const aspectRatio = videoWidth / videoHeight;
switch (videoLayoutFit) {
@@ -329,7 +322,7 @@ export class VideoContainer extends LargeContainer {
* @param {number} containerHeight container height
* @returns {{availableWidth, availableHeight}}
*/
_getVideoSize(containerWidth, containerHeight) {
getVideoSize(containerWidth, containerHeight) {
const { width, height } = this.getStreamSize();
if (this.stream && this.isScreenSharing()) {
@@ -421,29 +414,13 @@ export class VideoContainer extends LargeContainer {
if (this.$video.length === 0) {
return;
}
const currentLayout = getCurrentLayout(APP.store.getState());
if (currentLayout === LAYOUTS.TILE_VIEW) {
// We don't need to resize the large video since it won't be displayed and we'll resize when returning back
// to stage view.
return;
}
this.positionRemoteStatusMessages();
const [ width, height ] = this._getVideoSize(containerWidth, containerHeight);
if (width === 0 || height === 0) {
// We don't need to set 0 for width or height since the visibility is controled by the visibility css prop
// on the largeVideoElementsContainer. Also if the width/height of the video element is 0 the attached
// stream won't be played. Normally if we attach a new stream we won't resize the video element until the
// stream has been played. But setting width/height to 0 will prevent the video from playing.
return;
}
const [ width, height ]
= this.getVideoSize(containerWidth, containerHeight);
if ((containerWidth > width) || (containerHeight > height)) {
this._backgroundOrientation = containerWidth > width ? ORIENTATION.LANDSCAPE : ORIENTATION.PORTRAIT;
this._backgroundOrientation = containerWidth > width
? ORIENTATION.LANDSCAPE : ORIENTATION.PORTRAIT;
this._hideBackground = false;
} else {
this._hideBackground = true;
@@ -452,7 +429,15 @@ export class VideoContainer extends LargeContainer {
this._updateBackground();
const { horizontalIndent, verticalIndent }
= this.getVideoPosition(width, height, containerWidth, containerHeight);
= this.getVideoPosition(width, height,
containerWidth, containerHeight);
// update avatar position
const top = (containerHeight / 2) - (this.avatarHeight / 4 * 3);
this.$avatar.css('top', top);
this.positionRemoteStatusMessages();
this.$wrapper.animate({
width,

View File

@@ -22,6 +22,7 @@ import SharedVideoThumb from '../shared_video/SharedVideoThumb';
import Filmstrip from './Filmstrip';
import UIEvents from '../../../service/UI/UIEvents';
import UIUtil from '../util/UIUtil';
import RemoteVideo from './RemoteVideo';
import LargeVideoManager from './LargeVideoManager';
@@ -662,6 +663,14 @@ const VideoLayout = {
largeVideo.updateContainerSize();
largeVideo.resize(animate);
}
// Calculate available width and height.
const availableHeight = window.innerHeight;
const availableWidth = UIUtil.getAvailableVideoWidth();
if (availableWidth < 0 || availableHeight < 0) {
return;
}
},
getSmallVideo(id) {

View File

@@ -9,6 +9,43 @@ const DEFAULT_POSTIS_OPTIONS = {
window: window.opener || window.parent
};
/**
* The list of methods of incoming postis messages that we have to support for
* backward compatibility for the users that are directly sending messages to
* Jitsi Meet (without using external_api.js)
*
* @type {string[]}
*/
const LEGACY_INCOMING_METHODS = [
'avatar-url',
'display-name',
'email',
'toggle-audio',
'toggle-chat',
'toggle-film-strip',
'toggle-share-screen',
'toggle-video',
'video-hangup'
];
/**
* The list of methods of outgoing postis messages that we have to support for
* backward compatibility for the users that are directly listening to the
* postis messages send by Jitsi Meet(without using external_api.js).
*
* @type {string[]}
*/
const LEGACY_OUTGOING_METHODS = [
'display-name-change',
'incoming-message',
'outgoing-message',
'participant-joined',
'participant-left',
'video-conference-joined',
'video-conference-left',
'video-ready-to-close'
];
/**
* The postis method used for all messages.
*
@@ -26,13 +63,34 @@ export default class PostMessageTransportBackend {
* @param {Object} options - Optional parameters for configuration of the
* transport.
*/
constructor({ postisOptions } = {}) {
constructor({ enableLegacyFormat, postisOptions } = {}) {
// eslint-disable-next-line new-cap
this.postis = Postis({
...DEFAULT_POSTIS_OPTIONS,
...postisOptions
});
/**
* If true PostMessageTransportBackend will process and send messages
* using the legacy format and in the same time the current format.
* Otherwise all messages (outgoing and incoming) that are using the
* legacy format will be ignored.
*
* @type {boolean}
*/
this._enableLegacyFormat = enableLegacyFormat;
if (this._enableLegacyFormat) {
// backward compatibility
LEGACY_INCOMING_METHODS.forEach(method =>
this.postis.listen(
method,
params =>
this._legacyMessageReceivedCallback(method, params)
)
);
}
this._receiveCallback = () => {
// Do nothing until a callback is set by the consumer of
// PostMessageTransportBackend via setReceiveCallback.
@@ -43,6 +101,37 @@ export default class PostMessageTransportBackend {
message => this._receiveCallback(message));
}
/**
* Handles incoming legacy postis messages.
*
* @param {string} method - The method property from the postis message.
* @param {Any} params - The params property from the postis message.
* @returns {void}
*/
_legacyMessageReceivedCallback(method, params = {}) {
this._receiveCallback({
data: {
name: method,
data: params
}
});
}
/**
* Sends the passed message via postis using the old format.
*
* @param {Object} legacyMessage - The message to be sent.
* @returns {void}
*/
_sendLegacyMessage({ name, ...data }) {
if (name && LEGACY_OUTGOING_METHODS.indexOf(name) !== -1) {
this.postis.send({
method: name,
params: data
});
}
}
/**
* Disposes the allocated resources.
*
@@ -63,6 +152,14 @@ export default class PostMessageTransportBackend {
method: POSTIS_METHOD_NAME,
params: message
});
if (this._enableLegacyFormat) {
// For the legacy use case we don't need any new fields defined in
// Transport class. That's why we are passing only the original
// object passed by the consumer of the Transport class which is
// message.data.
this._sendLegacyMessage(message.data || {});
}
}
/**

View File

@@ -36,7 +36,12 @@ let transport;
*/
export function getJitsiMeetTransport() {
if (!transport) {
transport = new Transport({ backend: new PostMessageTransportBackend({ postisOptions }) });
transport = new Transport({
backend: new PostMessageTransportBackend({
enableLegacyFormat: true,
postisOptions
})
});
}
return transport;

9
package-lock.json generated
View File

@@ -10931,8 +10931,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#dd31f0aff0a38b3cfd8e808e457a2e3a0f966514",
"from": "github:jitsi/lib-jitsi-meet#dd31f0aff0a38b3cfd8e808e457a2e3a0f966514",
"version": "github:jitsi/lib-jitsi-meet#1de69abe22aa632c9a4255ee9f6ae48dab9be756",
"from": "github:jitsi/lib-jitsi-meet#1de69abe22aa632c9a4255ee9f6ae48dab9be756",
"requires": {
"@jitsi/sdp-interop": "0.1.14",
"@jitsi/sdp-simulcast": "0.2.2",
@@ -14887,9 +14887,8 @@
"integrity": "sha512-l3Quzbb+qa4in2U5RSt/lT0/pHrIpEChT1NnqrVAAXNrjkXjVOsxduaaEDdDhTzNJQEm/PcAcoyrFmgvGOohxw=="
},
"react-native-webrtc": {
"version": "1.75.2",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.75.2.tgz",
"integrity": "sha512-mdEukmHNhiyVIiwdooxk4kVXWG83OOENFV9YIkC7dtGU/sOdL81vDzynqd6Af9YbGMeOr0xdpFuEGsc1OFnKZg==",
"version": "github:react-native-webrtc/react-native-webrtc#a12a6cdfdefe53d03b388394e4cf10966bd99fca",
"from": "github:react-native-webrtc/react-native-webrtc#a12a6cdfdefe53d03b388394e4cf10966bd99fca",
"requires": {
"base64-js": "^1.1.2",
"event-target-shim": "^1.0.5",

View File

@@ -57,7 +57,7 @@
"js-utils": "github:jitsi/js-utils#192b1c996e8c05530eb1f19e82a31069c3021e31",
"jsrsasign": "8.0.12",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#dd31f0aff0a38b3cfd8e808e457a2e3a0f966514",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#1de69abe22aa632c9a4255ee9f6ae48dab9be756",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.13",
"moment": "2.19.4",
@@ -80,7 +80,7 @@
"react-native-svg-transformer": "0.13.0",
"react-native-swipeout": "2.3.6",
"react-native-watch-connectivity": "0.2.0",
"react-native-webrtc": "1.75.2",
"react-native-webrtc": "github:react-native-webrtc/react-native-webrtc#a12a6cdfdefe53d03b388394e4cf10966bd99fca",
"react-native-webview": "7.4.1",
"react-redux": "7.1.0",
"react-textarea-autosize": "7.1.0",

View File

@@ -9,7 +9,10 @@ import { DialogContainer } from '../../base/dialog';
import { CALL_INTEGRATION_ENABLED, updateFlags } from '../../base/flags';
import '../../base/jwt';
import { Platform } from '../../base/react';
import '../../base/responsive-ui';
import {
AspectRatioDetector,
ReducedUIDetector
} from '../../base/responsive-ui';
import { updateSettings } from '../../base/settings';
import '../../google-api';
import '../../mobile/audio-mode';
@@ -107,6 +110,22 @@ export class App extends AbstractApp {
});
}
/**
* Injects {@link AspectRatioDetector} in order to detect the aspect ratio
* of this {@code App}'s user interface and afford {@link AspectRatioAware}.
*
* @override
*/
_createMainElement(component, props) {
return (
<AspectRatioDetector>
<ReducedUIDetector>
{ super._createMainElement(component, props) }
</ReducedUIDetector>
</AspectRatioDetector>
);
}
/**
* Attempts to disable the use of React Native
* {@link ExceptionsManager#handleException} on platforms and in

View File

@@ -5,6 +5,7 @@ import React from 'react';
import { DialogContainer } from '../../base/dialog';
import '../../base/user-interaction';
import '../../base/responsive-ui';
import '../../chat';
import '../../external-api';
import '../../power-monitor';

View File

@@ -14,7 +14,6 @@ import { JitsiConferenceEvents } from '../lib-jitsi-meet';
import { setAudioMuted, setVideoMuted } from '../media';
import {
dominantSpeakerChanged,
getLocalParticipant,
getNormalizedDisplayName,
participantConnectionStatusChanged,
participantKicked,
@@ -394,18 +393,14 @@ export function createConference() {
throw new Error('Cannot join a conference without a room name!');
}
const config = state['features/base/config'];
const { email, name: nick } = getLocalParticipant(state);
const conference
= connection.initJitsiConference(
getBackendSafeRoomName(room), {
...config,
...state['features/base/config'],
applicationName: getName(),
getWiFiStatsMethod: getJitsiMeetGlobalNS().getWiFiStats,
confID: `${locationURL.host}${locationURL.pathname}`,
statisticsDisplayName: config.enableDisplayNameInStats ? nick : undefined,
statisticsId: config.enableEmailInStats ? email : undefined
confID: `${locationURL.host}${locationURL.pathname}`
});
connection[JITSI_CONNECTION_CONFERENCE_KEY] = conference;

View File

@@ -11,7 +11,6 @@ import {
participantLeft
} from '../participants';
import { toState } from '../redux';
import { safeDecodeURIComponent } from '../util';
import {
AVATAR_ID_COMMAND,
@@ -164,7 +163,7 @@ export function getConferenceName(stateful: Function | Object): string {
|| subject
|| callDisplayName
|| (callee && callee.name)
|| _.startCase(safeDecodeURIComponent(room));
|| _.startCase(decodeURIComponent(room));
}
/**
@@ -178,15 +177,13 @@ export function getConferenceName(stateful: Function | Object): string {
* @returns {JitsiConference|undefined}
*/
export function getCurrentConference(stateful: Function | Object) {
const { conference, joining, leaving, passwordRequired }
const { conference, joining, leaving }
= toState(stateful)['features/base/conference'];
// There is a precendence
if (conference) {
return conference === leaving ? undefined : conference;
}
return joining || passwordRequired;
return (
conference
? conference === leaving ? undefined : conference
: joining);
}
/**

View File

@@ -15,6 +15,10 @@ export default [
'autoRecord',
'autoRecordToken',
'avgRtpStatsN',
'callFlowsEnabled',
'callStatsConfIDNamespace',
'callStatsID',
'callStatsSecret',
/**
* The display name of the CallKit call representing the conference/meeting
@@ -30,7 +34,6 @@ export default [
* @type string
*/
'callDisplayName',
'callFlowsEnabled',
/**
* The handle
@@ -45,9 +48,6 @@ export default [
* @type string
*/
'callHandle',
'callStatsConfIDNamespace',
'callStatsID',
'callStatsSecret',
/**
* The UUID of the CallKit call representing the conference/meeting
@@ -73,8 +73,8 @@ export default [
'desktopSharingChromeExtId',
'desktopSharingChromeMinExtVersion',
'desktopSharingChromeSources',
'desktopSharingFirefoxDisabled',
'desktopSharingFrameRate',
'desktopSharingFirefoxDisabled',
'desktopSharingSources',
'disable1On1Mode',
'disableAEC',
@@ -84,7 +84,6 @@ export default [
'disableDeepLinking',
'disableH264',
'disableHPF',
'disableLocalVideoFlip',
'disableNS',
'disableRemoteControl',
'disableRtx',
@@ -92,10 +91,11 @@ export default [
'displayJids',
'e2eping',
'enableDisplayNameInStats',
'enableEmailInStats',
'enableLayerSuspension',
'enableLipSync',
'disableLocalVideoFlip',
'enableRemb',
'enableStatsID',
'enableTalkWhileMuted',
'enableTcc',
'etherpad_base',
@@ -123,12 +123,11 @@ export default [
'startAudioMuted',
'startAudioOnly',
'startBitrate',
'startScreenSharing',
'startSilent',
'startScreenSharing',
'startVideoMuted',
'startWithAudioMuted',
'startWithVideoMuted',
'stereo',
'subject',
'testing',
'useIPv6',

View File

@@ -22,7 +22,6 @@ export default [
'DEFAULT_REMOTE_DISPLAY_NAME',
'DISABLE_DOMINANT_SPEAKER_INDICATOR',
'DISABLE_FOCUS_INDICATOR',
'DISABLE_PRIVATE_MESSAGES',
'DISABLE_RINGING',
'DISABLE_TRANSCRIPTION_SUBTITLES',
'DISABLE_VIDEO_BACKGROUND',

View File

@@ -2,6 +2,7 @@
import _ from 'lodash';
import Platform from '../react/Platform';
import { equals, ReducerRegistry, set } from '../redux';
import { _UPDATE_CONFIG, CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes';
@@ -20,6 +21,15 @@ import { _cleanupConfig } from './functions';
const INITIAL_NON_RN_STATE = {
};
/**
* When we should enable H.264 on mobile. iOS 10 crashes so we disable it there.
* See: https://bugs.chromium.org/p/webrtc/issues/detail?id=11002
* Note that this is only used for P2P calls.
*
* @type {boolean}
*/
const RN_ENABLE_H264 = navigator.product === 'ReactNative' && !(Platform.OS === 'ios' && Platform.Version === 10);
/**
* The initial state of the feature base/config when executing in a React Native
* environment. The mandatory configuration to be passed to JitsiMeetJS#init().
@@ -40,9 +50,11 @@ const INITIAL_RN_STATE = {
// fastest to merely disable them.
disableAudioLevels: true,
disableH264: !RN_ENABLE_H264,
p2p: {
disableH264: false,
preferH264: true
disableH264: !RN_ENABLE_H264,
preferH264: RN_ENABLE_H264
}
};

View File

@@ -1,7 +1,7 @@
// @flow
import React, { PureComponent, type Node } from 'react';
import { SafeAreaView, ScrollView, View } from 'react-native';
import { Platform, SafeAreaView, ScrollView, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../color-scheme';
import { SlidingView } from '../../../react';
@@ -61,18 +61,41 @@ class BottomSheet extends PureComponent<Props> {
styles.sheetItemContainer,
_styles.sheet
] }>
<SafeAreaView>
<ScrollView
bounces = { false }
showsVerticalScrollIndicator = { false } >
{ this.props.children }
</ScrollView>
</SafeAreaView>
{ this._getWrappedContent() }
</View>
</View>
</SlidingView>
);
}
/**
* Wraps the content when needed (iOS 11 and above), or just returns the original content.
*
* @returns {React$Element}
*/
_getWrappedContent() {
const content = (
<ScrollView
bounces = { false }
showsVerticalScrollIndicator = { false } >
{ this.props.children }
</ScrollView>
);
if (Platform.OS === 'ios') {
const majorVersionIOS = parseInt(Platform.Version, 10);
if (majorVersionIOS > 10) {
return (
<SafeAreaView>
{ content }
</SafeAreaView>
);
}
}
return content;
}
}
/**

View File

@@ -1,14 +1,18 @@
// @flow
import React, { PureComponent, type Node } from 'react';
import { SafeAreaView, StatusBar, View } from 'react-native';
import React, { Component, type Node } from 'react';
import { Platform, SafeAreaView, StatusBar, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../color-scheme';
import { connect } from '../../../redux';
import { isDarkColor } from '../../../styles';
// Register style
import './headerstyles';
import { HEADER_PADDING } from './headerstyles';
/**
* Compatibility header padding size for iOS 10 (and older) devices.
*/
const IOS10_PADDING = 20;
/**
* Constanst for the (currently) supported statusbar colors.
@@ -40,7 +44,19 @@ type Props = {
/**
* A generic screen header component.
*/
class Header extends PureComponent<Props> {
class Header extends Component<Props> {
/**
* Initializes a new {@code Header} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._getIOS10CompatiblePadding
= this._getIOS10CompatiblePadding.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
@@ -50,7 +66,11 @@ class Header extends PureComponent<Props> {
const { _styles } = this.props;
return (
<View style = { _styles.headerOverlay }>
<View
style = { [
_styles.headerOverlay,
this._getIOS10CompatiblePadding()
] } >
<StatusBar
backgroundColor = { _styles.statusBar }
barStyle = { this._getStatusBarContentColor() }
@@ -70,6 +90,32 @@ class Header extends PureComponent<Props> {
);
}
_getIOS10CompatiblePadding: () => Object;
/**
* Adds a padding for iOS 10 (and older) devices to avoid clipping with the
* status bar.
* Note: This is a workaround for iOS 10 (and older) devices only to fix
* usability, but it doesn't take orientation into account, so unnecessary
* padding is rendered in some cases.
*
* @private
* @returns {Object}
*/
_getIOS10CompatiblePadding() {
if (Platform.OS === 'ios') {
const majorVersionIOS = parseInt(Platform.Version, 10);
if (majorVersionIOS <= 10) {
return {
paddingTop: HEADER_PADDING + IOS10_PADDING
};
}
}
return null;
}
/**
* Calculates the color of the statusbar content (light or dark) based on
* certain criterias.

View File

@@ -7,7 +7,8 @@ import { BoxModel } from '../../../styles';
const HEADER_FONT_SIZE = 18;
const HEADER_HEIGHT = 48;
const HEADER_PADDING = BoxModel.padding / 2;
export const HEADER_PADDING = BoxModel.padding / 2;
ColorSchemeRegistry.register('Header', {

View File

@@ -12,7 +12,9 @@ import type { Dispatch } from 'redux';
* very brittle because it's completely disconnected from the UI which wants to
* be rendered and, naturally, it broke on iPad where even the secondary Toolbar
* didn't fit in the height. We do need to measure the actual UI at runtime and
* determine whether and how to render it.
* determine whether and how to render it. I'm bumping from 240 to 300 because I
* don't have the time now to refactor {@code ReducedUIDetector} or rip it out
* completely.
*/
const REDUCED_UI_THRESHOLD = 300;

View File

@@ -0,0 +1,73 @@
// @flow
import React, { Component, type Node } from 'react';
import { type Dispatch } from 'redux';
import { connect } from '../../redux';
import { setAspectRatio } from '../actions';
import DimensionsDetector from './DimensionsDetector';
/**
* AspectRatioDetector component's property types.
*/
type Props = {
/**
* The "onDimensionsHandler" handler.
*/
_onDimensionsChanged: Function,
/**
* Any nested components.
*/
children: Node
};
/**
* A root {@link View} which captures the 'onLayout' event and figures out
* the aspect ratio of the app.
*/
class AspectRatioDetector extends Component<Props> {
/**
* Renders the root view and it's children.
*
* @returns {Component}
*/
render() {
return (
<DimensionsDetector
onDimensionsChanged = { this.props._onDimensionsChanged } >
{ this.props.children }
</DimensionsDetector>
);
}
}
/**
* Maps dispatching of the aspect ratio actions to React component props.
*
* @param {Function} dispatch - Redux action dispatcher.
* @private
* @returns {{
* _onDimensionsChanged: Function
* }}
*/
function _mapDispatchToProps(dispatch: Dispatch<any>) {
return {
/**
* Handles the "on dimensions changed" event and dispatches aspect ratio
* changed action.
*
* @param {number} width - The new width for the component.
* @param {number} height - The new height for the component.
* @private
* @returns {void}
*/
_onDimensionsChanged(width: number, height: number) {
dispatch(setAspectRatio(width, height));
}
};
}
export default connect(undefined, _mapDispatchToProps)(AspectRatioDetector);

View File

@@ -1,6 +1,6 @@
// @flow
import React, { PureComponent } from 'react';
import React, { Component } from 'react';
import { View } from 'react-native';
import styles from './styles';
@@ -25,7 +25,7 @@ type Props = {
* A {@link View} which captures the 'onLayout' event and calls a prop with the
* component size.
*/
export default class DimensionsDetector extends PureComponent<Props> {
export default class DimensionsDetector extends Component<Props> {
/**
* Initializes a new DimensionsDetector instance.
*

View File

@@ -0,0 +1,74 @@
// @flow
import React, { Component, type Node } from 'react';
import { type Dispatch } from 'redux';
import { connect } from '../../redux';
import { setReducedUI } from '../actions';
import DimensionsDetector from './DimensionsDetector';
/**
* ReducedUIDetector component's property types.
*/
type Props = {
/**
* The "onDimensionsHandler" handler.
*/
_onDimensionsChanged: Function,
/**
* Any nested components.
*/
children: Node
};
/**
* A root {@link View} which captures the 'onLayout' event and figures out
* if the UI is reduced.
*/
class ReducedUIDetector extends Component<Props> {
/**
* Renders the root view and it's children.
*
* @returns {Component}
*/
render() {
return (
<DimensionsDetector
onDimensionsChanged = { this.props._onDimensionsChanged } >
{ this.props.children }
</DimensionsDetector>
);
}
}
/**
* Maps dispatching of the reduced UI actions to React component props.
*
* @param {Function} dispatch - Redux action dispatcher.
* @private
* @returns {{
* _onDimensionsChanged: Function
* }}
*/
function _mapDispatchToProps(dispatch: Dispatch<any>) {
return {
/**
* Handles the "on dimensions changed" event and dispatches the
* reduced UI action.
*
* @param {number} width - The new width for the component.
* @param {number} height - The new height for the component.
* @private
* @returns {void}
*/
_onDimensionsChanged(width: number, height: number) {
dispatch(setReducedUI(width, height));
}
};
}
export default connect(undefined, _mapDispatchToProps)(ReducedUIDetector);

View File

@@ -1,2 +1,4 @@
export * from './AspectRatioAware';
export { default as AspectRatioDetector } from './AspectRatioDetector';
export { default as DimensionsDetector } from './DimensionsDetector';
export { default as ReducedUIDetector } from './ReducedUIDetector';

View File

@@ -3,5 +3,4 @@ export * from './actionTypes';
export * from './components';
export * from './constants';
import './middleware';
import './reducer';

View File

@@ -1,83 +0,0 @@
// @flow
import { Dimensions } from 'react-native';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
import { MiddlewareRegistry } from '../../base/redux';
import { setAspectRatio, setReducedUI } from './actions';
/**
* Dimensions change handler.
*/
let handler;
/**
* Middleware that handles widnow dimension changes and updates the aspect ratio and
* reduced UI modes accordingly.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
const result = next(action);
switch (action.type) {
case APP_WILL_UNMOUNT: {
_appWillUnmount();
break;
}
case APP_WILL_MOUNT:
_appWillMount(store);
break;
}
return result;
});
/**
* Notifies this feature that the action {@link APP_WILL_MOUNT} is being
* dispatched within a specific redux {@code store}.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @private
* @returns {void}
*/
function _appWillMount(store) {
handler = dim => {
_onDimensionsChange(dim, store);
};
Dimensions.addEventListener('change', handler);
}
/**
* Notifies this feature that the action {@link APP_WILL_UNMOUNT} is being
* dispatched within a specific redux {@code store}.
*
* @private
* @returns {void}
*/
function _appWillUnmount() {
Dimensions.removeEventListener('change', handler);
handler = undefined;
}
/**
* Handles window dimension changes.
*
* @param {Object} dimensions - The new dimensions.
* @param {Store} store - The redux store.
* @private
* @returns {void}
*/
function _onDimensionsChange(dimensions, store) {
const { width, height } = dimensions.window;
const { dispatch } = store;
dispatch(setAspectRatio(width, height));
dispatch(setReducedUI(width, height));
}

View File

@@ -13,14 +13,16 @@ const DEFAULT_STATE = {
reducedUI: false
};
ReducerRegistry.register('features/base/responsive-ui', (state = DEFAULT_STATE, action) => {
switch (action.type) {
case SET_ASPECT_RATIO:
return set(state, 'aspectRatio', action.aspectRatio);
ReducerRegistry.register(
'features/base/responsive-ui',
(state = DEFAULT_STATE, action) => {
switch (action.type) {
case SET_ASPECT_RATIO:
return set(state, 'aspectRatio', action.aspectRatio);
case SET_REDUCED_UI:
return set(state, 'reducedUI', action.reducedUI);
}
case SET_REDUCED_UI:
return set(state, 'reducedUI', action.reducedUI);
}
return state;
});
return state;
});

View File

@@ -1,10 +1,7 @@
// @flow
import _ from 'lodash';
import { APP_WILL_MOUNT } from '../app';
import { setAudioOnly } from '../audio-only';
import parseURLParams from '../config/parseURLParams'; // minimize imports to avoid circular imports
import { SET_LOCATION_URL } from '../connection/actionTypes'; // minimize imports to avoid circular imports
import { getLocalParticipant, participantUpdated } from '../participants';
import { MiddlewareRegistry } from '../redux';
@@ -31,9 +28,6 @@ MiddlewareRegistry.register(store => next => action => {
_maybeSetAudioOnly(store, action);
_updateLocalParticipant(store, action);
break;
case SET_LOCATION_URL:
_updateLocalParticipantFromUrl(store);
break;
}
return result;
@@ -124,30 +118,3 @@ function _updateLocalParticipant({ dispatch, getState }, action) {
dispatch(participantUpdated(newLocalParticipant));
}
/**
* Returns the userInfo set in the URL.
*
* @param {Store} store - The redux store.
* @private
* @returns {void}
*/
function _updateLocalParticipantFromUrl({ dispatch, getState }) {
const urlParams
= parseURLParams(getState()['features/base/connection'].locationURL);
const urlEmail = urlParams['userInfo.email'];
if (!urlEmail) {
return;
}
const localParticipant = getLocalParticipant(getState());
if (localParticipant) {
dispatch(participantUpdated({
...localParticipant,
email: _.escape(urlEmail)
}));
}
}

View File

@@ -4,8 +4,6 @@ import md5 from 'js-md5';
import logger from './logger';
declare var __DEV__;
/**
* The name of the {@code localStorage} store where the app persists its values.
*/
@@ -89,9 +87,7 @@ class PersistenceRegistry {
// Initialize the checksum.
this._checksum = this._calculateChecksum(filteredPersistedState);
if (typeof __DEV__ !== 'undefined' && __DEV__) {
logger.info('redux state rehydrated as', filteredPersistedState);
}
logger.info('redux state rehydrated as', filteredPersistedState);
return filteredPersistedState;
}
@@ -117,6 +113,7 @@ class PersistenceRegistry {
logger.error(
'Error persisting redux subtree',
subtreeName,
filteredState[subtreeName],
error);
}
}
@@ -156,7 +153,7 @@ class PersistenceRegistry {
try {
return md5.hex(JSON.stringify(state) || '');
} catch (error) {
logger.error('Error calculating checksum for state', error);
logger.error('Error calculating checksum for state', state, error);
return '';
}

View File

@@ -117,8 +117,8 @@ class TestConnectionInfo extends Component<Props, State> {
this.setState({
stats: {
bitrate: {
download: stats.bitrate?.download || 0,
upload: stats.bitrate?.upload || 0
download: stats.bitrate.download,
upload: stats.bitrate.upload
}
}
});

View File

@@ -373,23 +373,6 @@ function _standardURIToString(thiz: ?Object) {
return str;
}
/**
* Sometimes we receive strings that we don't know if already percent-encoded, or not, due to the
* various sources we get URLs or room names. This function encapsulates the decoding in a safe way.
*
* @param {string} text - The text to decode.
* @returns {string}
*/
export function safeDecodeURIComponent(text: string) {
try {
return decodeURIComponent(text);
} catch (e) {
// The text wasn't encoded.
}
return text;
}
/**
* Attempts to return a {@code String} representation of a specific
* {@code Object} which is supposed to represent a URL. Obviously, if a
@@ -527,7 +510,7 @@ export function urlObjectToString(o: Object): ?string {
let { hash } = url;
for (const urlPrefix of [ 'config', 'interfaceConfig', 'devices', 'userInfo' ]) {
for (const urlPrefix of [ 'config', 'interfaceConfig', 'devices' ]) {
const urlParamsArray
= _objectToURLParamsArray(
o[`${urlPrefix}Overwrite`]

View File

@@ -15,7 +15,7 @@ export type Props = {
*
* @extends PureComponent
*/
export default class AbstractMessageContainer<P: Props> extends PureComponent<P> {
export default class AbstractMessageContainer extends PureComponent<Props> {
static defaultProps = {
messages: []
};
@@ -46,7 +46,7 @@ export default class AbstractMessageContainer<P: Props> extends PureComponent<P>
}
}
currentGrouping.length && groups.push(currentGrouping);
groups.push(currentGrouping);
return groups;
}

View File

@@ -3,7 +3,6 @@
import React, { Component } from 'react';
import { TextInput, TouchableOpacity, View } from 'react-native';
import { translate } from '../../../base/i18n';
import { Icon, IconChatSend } from '../../../base/icons';
import { Platform } from '../../../base/react';
@@ -14,12 +13,7 @@ type Props = {
/**
* Callback to invoke on message send.
*/
onSend: Function,
/**
* Function to be used to translate i18n labels.
*/
t: Function
onSend: Function
};
type State = {
@@ -43,7 +37,7 @@ type State = {
/**
* Implements the chat input bar with text field and action(s).
*/
class ChatInputBar extends Component<Props, State> {
export default class ChatInputBar extends Component<Props, State> {
/**
* Instantiates a new instance of the component.
*
@@ -59,7 +53,6 @@ class ChatInputBar extends Component<Props, State> {
};
this._onChangeText = this._onChangeText.bind(this);
this._onFieldReferenceAvailable = this._onFieldReferenceAvailable.bind(this);
this._onFocused = this._onFocused.bind(this);
this._onSubmit = this._onSubmit.bind(this);
}
@@ -83,8 +76,6 @@ class ChatInputBar extends Component<Props, State> {
onChangeText = { this._onChangeText }
onFocus = { this._onFocused(true) }
onSubmitEditing = { this._onSubmit }
placeholder = { this.props.t('chat.fieldPlaceHolder') }
ref = { this._onFieldReferenceAvailable }
returnKeyType = 'send'
style = { styles.inputField }
value = { this.state.message } />
@@ -114,18 +105,6 @@ class ChatInputBar extends Component<Props, State> {
});
}
_onFieldReferenceAvailable: Object => void;
/**
* Callback to be invoked when the field reference is available.
*
* @param {Object} field - The reference to the field.
* @returns {void}
*/
_onFieldReferenceAvailable(field) {
field && field.focus();
}
_onFocused: boolean => Function;
/**
@@ -159,5 +138,3 @@ class ChatInputBar extends Component<Props, State> {
});
}
}
export default translate(ChatInputBar);

View File

@@ -1,36 +1,18 @@
// @flow
import React from 'react';
import { FlatList, Text, View } from 'react-native';
import { FlatList } from 'react-native';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
import AbstractMessageContainer, { type Props as AbstractProps }
import AbstractMessageContainer, { type Props }
from '../AbstractMessageContainer';
import ChatMessageGroup from './ChatMessageGroup';
import styles from './styles';
type Props = AbstractProps & {
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType,
/**
* Function to be used to translate i18n labels.
*/
t: Function
};
/**
* Implements a container to render all the chat messages in a conference.
*/
class MessageContainer extends AbstractMessageContainer<Props> {
export default class MessageContainer extends AbstractMessageContainer {
/**
* Instantiates a new instance of the component.
*
@@ -40,7 +22,6 @@ class MessageContainer extends AbstractMessageContainer<Props> {
super(props);
this._keyExtractor = this._keyExtractor.bind(this);
this._renderListEmptyComponent = this._renderListEmptyComponent.bind(this);
this._renderMessageGroup = this._renderMessageGroup.bind(this);
}
@@ -50,16 +31,10 @@ class MessageContainer extends AbstractMessageContainer<Props> {
* @inheritdoc
*/
render() {
const data = this._getMessagesGroupedBySender();
return (
<FlatList
ListEmptyComponent = { this._renderListEmptyComponent }
data = { data }
// Workaround for RN bug:
// https://github.com/facebook/react-native/issues/21196
inverted = { Boolean(data.length) }
data = { this._getMessagesGroupedBySender() }
inverted = { true }
keyExtractor = { this._keyExtractor }
keyboardShouldPersistTaps = 'always'
renderItem = { this._renderMessageGroup }
@@ -83,26 +58,7 @@ class MessageContainer extends AbstractMessageContainer<Props> {
return `key_${index}`;
}
_renderListEmptyComponent: () => React$Element<any>;
/**
* Renders a message when there are no messages in the chat yet.
*
* @returns {React$Element<any>}
*/
_renderListEmptyComponent() {
const { _styles, t } = this.props;
return (
<View style = { styles.emptyComponentWrapper }>
<Text style = { _styles.emptyComponentText }>
{ t('chat.noMessagesMessage') }
</Text>
</View>
);
}
_renderMessageGroup: Object => React$Element<any>;
_renderMessageGroup: Object => React$Element<*>;
/**
* Renders a single chat message.
@@ -114,17 +70,3 @@ class MessageContainer extends AbstractMessageContainer<Props> {
return <ChatMessageGroup messages = { messages } />;
}
}
/**
* Maps part of the redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_styles: ColorSchemeRegistry.get(state, 'Chat')
};
}
export default translate(connect(_mapStateToProps)(MessageContainer));

View File

@@ -42,13 +42,6 @@ export default {
flexDirection: 'column'
},
emptyComponentWrapper: {
alignSelf: 'center',
flex: 1,
padding: BoxModel.padding,
paddingTop: '10%'
},
/**
* A special padding to avoid issues on some devices (such as Android devices with custom suggestions bar).
*/
@@ -150,11 +143,6 @@ ColorSchemeRegistry.register('Chat', {
fontSize: 13
},
emptyComponentText: {
color: schemeColor('displayName'),
textAlign: 'center'
},
localMessageBubble: {
backgroundColor: schemeColor('localMsgBackground'),
borderTopRightRadius: 0

View File

@@ -14,7 +14,7 @@ import ChatMessageGroup from './ChatMessageGroup';
*
* @extends AbstractMessageContainer
*/
export default class MessageContainer extends AbstractMessageContainer<Props> {
export default class MessageContainer extends AbstractMessageContainer {
/**
* Whether or not chat has been scrolled to the bottom of the screen. Used
* to determine if chat should be scrolled automatically to the bottom when

View File

@@ -20,7 +20,7 @@ export function notifyKickedOut(participant: Object, _: ?Function) { // eslint-d
return (dispatch: Dispatch<any>, getState: Function) => {
const args = {
participantDisplayName:
getParticipantDisplayName(getState, participant.getId())
getParticipantDisplayName(getState, participant.getDisplayName())
};
dispatch(showNotification({

View File

@@ -1,7 +1,7 @@
// @flow
import React from 'react';
import { NativeModules, SafeAreaView, StatusBar } from 'react-native';
import { NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { appNavigate } from '../../../app';
@@ -279,7 +279,7 @@ class Conference extends AbstractConference<Props, *> {
</TintedView>
}
<SafeAreaView
<View
pointerEvents = 'box-none'
style = { styles.toolboxAndFilmstripContainer }>
@@ -320,7 +320,7 @@ class Conference extends AbstractConference<Props, *> {
*/
_shouldDisplayTileView ? undefined : <Filmstrip />
}
</SafeAreaView>
</View>
<SafeAreaView
pointerEvents = 'box-none'

View File

@@ -3,7 +3,6 @@
import React, { Component } from 'react';
import { getConferenceName } from '../../../base/conference/functions';
import { getParticipantCount } from '../../../base/participants/functions';
import { connect } from '../../../base/redux';
import { isToolboxVisible } from '../../../toolbox';
@@ -14,11 +13,6 @@ import ParticipantsCount from './ParticipantsCount';
*/
type Props = {
/**
* Whether then participant count should be shown or not.
*/
_showParticipantCount: boolean,
/**
* The subject or the of the conference.
* Falls back to conference name.
@@ -45,12 +39,12 @@ class Subject extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { _showParticipantCount, _subject, _visible } = this.props;
const { _subject, _visible } = this.props;
return (
<div className = { `subject ${_visible ? 'visible' : ''}` }>
<span className = 'subject-text'>{ _subject }</span>
{ _showParticipantCount && <ParticipantsCount /> }
<ParticipantsCount />
</div>
);
}
@@ -68,10 +62,8 @@ class Subject extends Component<Props> {
* }}
*/
function _mapStateToProps(state) {
const participantCount = getParticipantCount(state);
return {
_showParticipantCount: participantCount > 2,
_subject: getConferenceName(state),
_visible: isToolboxVisible(state)
};

View File

@@ -94,23 +94,6 @@ class DeepLinkingMobilePage extends Component<Props> {
const downloadButtonClassName
= `${_SNS}__button ${_SNS}__button_primary`;
const onOpenLinkProperties = _URLS[Platform.OS]
? {
// When opening a link to the download page, we want to let the
// OS itself handle intercepting and opening the appropriate
// app store. This avoids potential issues with browsers, such
// as iOS Chrome, not opening the store properly.
}
: {
// When falling back to another URL (Firebase) let the page be
// opened in a new window. This helps prevent the user getting
// trapped in an app-open-cycle where going back to the mobile
// browser re-triggers the app-open behavior.
target: '_blank',
rel: 'noopener noreferrer'
};
return (
<div className = { _SNS }>
<div className = 'header'>
@@ -130,18 +113,20 @@ class DeepLinkingMobilePage extends Component<Props> {
{ t(`${_TNS}.appNotInstalled`, { app: NATIVE_APP_NAME }) }
</p>
<a
{ ...onOpenLinkProperties }
href = { this._generateDownloadURL() }
onClick = { this._onDownloadApp }>
onClick = { this._onDownloadApp }
rel = 'noopener noreferrer'
target = '_blank'>
<button className = { downloadButtonClassName }>
{ t(`${_TNS}.downloadApp`) }
</button>
</a>
<a
{ ...onOpenLinkProperties }
className = { `${_SNS}__href` }
href = { generateDeepLinkingURL() }
onClick = { this._onOpenApp }>
onClick = { this._onOpenApp }
rel = 'noopener noreferrer'
target = '_blank'>
{/* <button className = { `${_SNS}__button` }> */}
{ t(`${_TNS}.openApp`) }
{/* </button> */}

View File

@@ -1,7 +1,7 @@
// @flow
import React, { PureComponent } from 'react';
import { SafeAreaView, View } from 'react-native';
import { Platform, SafeAreaView, View } from 'react-native';
import { WebView } from 'react-native-webview';
import type { Dispatch } from 'redux';
@@ -71,6 +71,13 @@ class SharedDocument extends PureComponent<Props> {
render() {
const { _documentUrl, _isOpen } = this.props;
const webViewStyles = this._getWebViewStyles();
const extraWebViewProps = {};
// FIXME HACK: workaround nginx spartan config issue on meet.jit.si
if (Platform.OS === 'android') {
// eslint-disable-next-line max-len
extraWebViewProps.userAgent = 'Mozilla/5.0 (Linux; Android 8.0.0;) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.96 Mobile Safari/537.36';
}
return (
<SlidingView
@@ -86,7 +93,8 @@ class SharedDocument extends PureComponent<Props> {
onError = { this._onError }
renderLoading = { this._renderLoading }
source = {{ uri: _documentUrl }}
startInLoadingState = { true } />
startInLoadingState = { true }
{ ...extraWebViewProps } />
</SafeAreaView>
</View>
</SlidingView>

View File

@@ -15,10 +15,10 @@ import {
CONNECTION_DISCONNECTED,
CONNECTION_FAILED,
JITSI_CONNECTION_CONFERENCE_KEY,
JITSI_CONNECTION_URL_KEY,
getURLWithoutParams
JITSI_CONNECTION_URL_KEY
} from '../../base/connection';
import { MiddlewareRegistry } from '../../base/redux';
import { toURLString } from '../../base/util';
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture';
import { sendEvent } from './functions';
@@ -82,7 +82,7 @@ MiddlewareRegistry.register(store => next => action => {
store,
CONFERENCE_TERMINATED,
/* data */ {
url: _normalizeUrl(locationURL)
url: toURLString(locationURL)
});
}
@@ -106,7 +106,7 @@ MiddlewareRegistry.register(store => next => action => {
CONFERENCE_TERMINATED,
/* data */ {
error: _toErrorString(error),
url: _normalizeUrl(locationURL)
url: toURLString(locationURL)
});
break;
}
@@ -161,20 +161,10 @@ function _maybeTriggerEarlyConferenceWillJoin(store, action) {
store,
CONFERENCE_WILL_JOIN,
/* data */ {
url: _normalizeUrl(locationURL)
url: toURLString(locationURL)
});
}
/**
* Normalizes the given URL for presentation over the external API.
*
* @param {URL} url -The URL to normalize.
* @returns {string} - The normalized URL as a string.
*/
function _normalizeUrl(url: URL) {
return getURLWithoutParams(url).href;
}
/**
* Sends an event to the native counterpart of the External API for a specific
* conference-related redux action.
@@ -196,7 +186,7 @@ function _sendConferenceEvent(
// instance. The external API cannot transport such an object so we have to
// transport an "equivalent".
if (conference) {
data.url = _normalizeUrl(conference[JITSI_CONFERENCE_URL_KEY]);
data.url = toURLString(conference[JITSI_CONFERENCE_URL_KEY]);
}
if (_swallowEvent(store, action, data)) {
@@ -243,7 +233,7 @@ function _sendConferenceFailedOnConnectionError(store, action) {
store,
CONFERENCE_TERMINATED,
/* data */ {
url: _normalizeUrl(locationURL),
url: toURLString(locationURL),
error: action.error.name
});
}

View File

@@ -3,7 +3,7 @@ import {
getLocalizedDurationFormatter
} from '../base/i18n';
import { NavigateSectionList } from '../base/react';
import { parseURIString, safeDecodeURIComponent } from '../base/util';
import { parseURIString } from '../base/util';
/**
* Creates a displayable list item of a recent list entry.
@@ -31,7 +31,7 @@ function toDisplayableItem(item, defaultServerURL, t) {
_toDurationString(item.duration),
serverName
],
title: safeDecodeURIComponent(location.room),
title: decodeURIComponent(location.room),
url: item.conference
};
}

View File

@@ -5,25 +5,10 @@ import React, { Component } from 'react';
import { translate } from '../../../base/i18n';
import { IconMessage } from '../../../base/icons';
import { connect } from '../../../base/redux';
import {
_mapDispatchToProps,
_mapStateToProps as _abstractMapStateToProps,
type Props as AbstractProps
} from '../../../chat/components/PrivateMessageButton';
import { isButtonEnabled } from '../../../toolbox';
import { _mapDispatchToProps, _mapStateToProps, type Props } from '../../../chat/components/PrivateMessageButton';
import RemoteVideoMenuButton from './RemoteVideoMenuButton';
declare var interfaceConfig: Object;
type Props = AbstractProps & {
/**
* True if the private chat functionality is disabled, hence the button is not visible.
*/
_hidden: boolean
};
/**
* A custom implementation of the PrivateMessageButton specialized for
* the web version of the remote video menu. When the web platform starts to use
@@ -49,11 +34,7 @@ class PrivateMessageMenuButton extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { participantID, t, _hidden } = this.props;
if (_hidden) {
return null;
}
const { participantID, t } = this.props;
return (
<RemoteVideoMenuButton
@@ -78,19 +59,4 @@ class PrivateMessageMenuButton extends Component<Props> {
}
}
/**
* Maps part of the Redux store to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the component.
* @returns {Props}
*/
function _mapStateToProps(state: Object, ownProps: Props): $Shape<Props> {
return {
..._abstractMapStateToProps(state, ownProps),
_hidden: typeof interfaceConfig !== 'undefined'
&& (interfaceConfig.DISABLE_PRIVATE_MESSAGES || !isButtonEnabled('chat'))
};
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(PrivateMessageMenuButton));

View File

@@ -5,7 +5,7 @@ import { Text, View } from 'react-native';
import { translate } from '../../../base/i18n';
import styles, { ANDROID_UNDERLINE_COLOR, PLACEHOLDER_COLOR } from './styles';
import styles, { ANDROID_UNDERLINE_COLOR } from './styles';
/**
* The type of the React {@code Component} props of {@link FormRow}
@@ -107,7 +107,6 @@ class FormRow extends Component<Props> {
switch (field.type.displayName) {
case 'TextInput':
return {
placeholderTextColor: PLACEHOLDER_COLOR,
style: styles.textInputField,
underlineColorAndroid: ANDROID_UNDERLINE_COLOR
};

View File

@@ -4,8 +4,6 @@ import {
} from '../../../base/styles';
export const ANDROID_UNDERLINE_COLOR = 'transparent';
export const PLACEHOLDER_COLOR = ColorPalette.lightGrey;
const TEXT_SIZE = 17;
/**
@@ -81,7 +79,6 @@ export default createStyleSheet({
* Standard text input field style.
*/
textInputField: {
color: ColorPalette.black,
flex: 1,
fontSize: TEXT_SIZE,
textAlign: 'right'

View File

@@ -1,6 +1,6 @@
// @flow
import React, { PureComponent } from 'react';
import React, { Component } from 'react';
import { View } from 'react-native';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
@@ -46,10 +46,37 @@ type Props = {
dispatch: Function
};
/**
* The type of {@link Toolbox}'s React {@code Component} state.
*/
type State = {
/**
* The detected width for this component.
*/
width: number
};
/**
* Implements the conference toolbox on React Native.
*/
class Toolbox extends PureComponent<Props> {
class Toolbox extends Component<Props, State> {
state = {
width: 0
};
/**
* Initializes a new {@code Toolbox} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onLayout = this._onLayout.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
@@ -59,6 +86,7 @@ class Toolbox extends PureComponent<Props> {
render() {
return (
<Container
onLayout = { this._onLayout }
style = { styles.toolbox }
visible = { this.props._visible }>
{ this._renderToolbar() }
@@ -97,6 +125,20 @@ class Toolbox extends PureComponent<Props> {
};
}
_onLayout: (Object) => void;
/**
* Handles the "on layout" View's event and stores the width as state.
*
* @param {Object} event - The "on layout" event object/structure passed
* by react-native.
* @private
* @returns {void}
*/
_onLayout({ nativeEvent: { layout: { width } } }) {
this.setState({ width });
}
/**
* Renders the toolbar. In order to avoid a weird visual effect in which the
* toolbar is (visually) rendered and then visibly changes its size, it is

View File

@@ -57,10 +57,7 @@ import {
} from '../../../settings';
import { toggleSharedVideo } from '../../../shared-video';
import { SpeakerStats } from '../../../speaker-stats';
import {
TileViewButton,
toggleTileView
} from '../../../video-layout';
import { TileViewButton } from '../../../video-layout';
import {
OverflowMenuVideoQualityItem,
VideoQualityDialog
@@ -125,11 +122,6 @@ type Props = {
*/
_fullScreen: boolean,
/**
* Whether or not the tile view is enabled.
*/
_tileViewEnabled: boolean,
/**
* Whether or not invite should be hidden, regardless of feature
* availability.
@@ -244,7 +236,6 @@ class Toolbox extends Component<Props, State> {
this._onToolbarToggleScreenshare = this._onToolbarToggleScreenshare.bind(this);
this._onToolbarToggleSharedVideo = this._onToolbarToggleSharedVideo.bind(this);
this._onToolbarOpenLocalRecordingInfoDialog = this._onToolbarOpenLocalRecordingInfoDialog.bind(this);
this._onShortcutToggleTileView = this._onShortcutToggleTileView.bind(this);
this.state = {
windowWidth: window.innerWidth
@@ -283,11 +274,6 @@ class Toolbox extends Component<Props, State> {
character: 'S',
exec: this._onShortcutToggleFullScreen,
helpDescription: 'keyboardShortcuts.fullScreen'
},
this._shouldShowButton('tileview') && {
character: 'W',
exec: this._onShortcutToggleTileView,
helpDescription: 'toolbar.tileViewToggle'
}
];
@@ -489,16 +475,6 @@ class Toolbox extends Component<Props, State> {
this.props.dispatch(toggleDialog(VideoQualityDialog));
}
/**
* Dispaches an action to toggle tile view.
*
* @private
* @returns {void}
*/
_doToggleTileView() {
this.props.dispatch(toggleTileView());
}
_onMouseOut: () => void;
/**
@@ -589,24 +565,6 @@ class Toolbox extends Component<Props, State> {
this._doToggleVideoQuality();
}
_onShortcutToggleTileView: () => void;
/**
* Dispatches an action for toggling the tile view.
*
* @private
* @returns {void}
*/
_onShortcutToggleTileView() {
sendAnalytics(createShortcutEvent(
'toggle.tileview',
{
enable: !this.props._tileViewEnabled
}));
this._doToggleTileView();
}
_onShortcutToggleFullScreen: () => void;
/**
@@ -1337,7 +1295,6 @@ function _mapStateToProps(state) {
iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
_isGuest: state['features/base/jwt'].isGuest,
_fullScreen: fullScreen,
_tileViewEnabled: state['features/video-layout'].tileViewEnabled,
_localParticipantID: localParticipant.id,
_localRecState: localRecordingStates,
_overflowMenuVisible: overflowMenuVisible,

View File

@@ -1,5 +1,4 @@
local get_room_from_jid = module:require "util".get_room_from_jid;
local room_jid_match_rewrite = module:require "util".room_jid_match_rewrite;
local jid_resource = require "util.jid".resource;
local ext_events = module:require "ext_events"
local st = require "util.stanza";
@@ -26,7 +25,7 @@ function on_message(event)
= event.stanza:get_child('speakerstats', 'http://jitsi.org/jitmeet');
if speakerStats then
local roomAddress = speakerStats.attr.room;
local room = get_room_from_jid(room_jid_match_rewrite(roomAddress));
local room = get_room_from_jid(roomAddress);
if not room then
log("warn", "No room found %s", roomAddress);

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script src="../../libs/lib-jitsi-meet.min.js?v=139"></script>
<script src="libs/load-test-participant.min.js" ></script>
</head>
<body>
<div>Number of participants: <span id="participants">1</span></div>
</body>
</html>

View File

@@ -1,261 +0,0 @@
/* global $, JitsiMeetJS */
import 'jquery';
import parseURLParams from '../../react/features/base/config/parseURLParams';
const params = parseURLParams(window.location, false, 'hash');
const { isHuman = false } = params;
const {
roomName = 'loadtest0',
localAudio = isHuman,
localVideo = isHuman,
remoteVideo = isHuman,
remoteAudio = isHuman
} = params;
const options = {
hosts: {
domain: 'george-perf.jitsi.net',
muc: 'conference.george-perf.jitsi.net'
},
bosh: '//george-perf.jitsi.net/http-bind',
// The name of client node advertised in XEP-0115 'c' stanza
clientNode: 'http://jitsi.org/jitsimeet'
};
const confOptions = {
openBridgeChannel: 'websocket',
testing: {
testMode: true,
noAutoPlayVideo: true
},
disableNS: true,
disableAEC: true,
gatherStats: true,
callStatsID: false
};
let connection = null;
let isJoined = false;
let room = null;
let numParticipants = 1;
let localTracks = [];
const remoteTracks = {};
window.APP = {
get room() {
return room;
},
get connection() {
return connection;
},
get numParticipants() {
return numParticipants;
},
get localTracks() {
return localTracks;
},
get remoteTracks() {
return remoteTracks;
},
get params() {
return {
roomName,
localAudio,
localVideo,
remoteVideo,
remoteAudio
};
}
};
/**
*
*/
function setNumberOfParticipants() {
$('#participants').text(numParticipants);
}
/**
* Handles local tracks.
* @param tracks Array with JitsiTrack objects
*/
function onLocalTracks(tracks = []) {
localTracks = tracks;
for (let i = 0; i < localTracks.length; i++) {
if (localTracks[i].getType() === 'video') {
$('body').append(`<video autoplay='1' id='localVideo${i}' />`);
localTracks[i].attach($(`#localVideo${i}`)[0]);
} else {
$('body').append(
`<audio autoplay='1' muted='true' id='localAudio${i}' />`);
localTracks[i].attach($(`#localAudio${i}`)[0]);
}
if (isJoined) {
room.addTrack(localTracks[i]);
}
}
}
/**
* Handles remote tracks
* @param track JitsiTrack object
*/
function onRemoteTrack(track) {
if (track.isLocal()
|| (track.getType() === 'video' && !remoteVideo) || (track.getType() === 'audio' && !remoteAudio)) {
return;
}
const participant = track.getParticipantId();
if (!remoteTracks[participant]) {
remoteTracks[participant] = [];
}
const idx = remoteTracks[participant].push(track);
const id = participant + track.getType() + idx;
if (track.getType() === 'video') {
$('body').append(`<video autoplay='1' id='${id}' />`);
} else {
$('body').append(`<audio autoplay='1' id='${id}' />`);
}
track.attach($(`#${id}`)[0]);
}
/**
* That function is executed when the conference is joined
*/
function onConferenceJoined() {
isJoined = true;
for (let i = 0; i < localTracks.length; i++) {
room.addTrack(localTracks[i]);
}
}
/**
*
* @param id
*/
function onUserLeft(id) {
numParticipants--;
setNumberOfParticipants();
if (!remoteTracks[id]) {
return;
}
const tracks = remoteTracks[id];
for (let i = 0; i < tracks.length; i++) {
const container = $(`#${id}${tracks[i].getType()}${i + 1}`)[0];
if (container) {
tracks[i].detach(container);
container.parentElement.removeChild(container);
}
}
}
/**
* That function is called when connection is established successfully
*/
function onConnectionSuccess() {
room = connection.initJitsiConference(roomName, confOptions);
room.on(JitsiMeetJS.events.conference.TRACK_ADDED, onRemoteTrack);
room.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, onConferenceJoined);
room.on(JitsiMeetJS.events.conference.USER_JOINED, id => {
numParticipants++;
setNumberOfParticipants();
remoteTracks[id] = [];
});
room.on(JitsiMeetJS.events.conference.USER_LEFT, onUserLeft);
room.join();
}
/**
* This function is called when the connection fail.
*/
function onConnectionFailed() {
console.error('Connection Failed!');
}
/**
* This function is called when we disconnect.
*/
function disconnect() {
console.log('disconnect!');
connection.removeEventListener(
JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,
onConnectionSuccess);
connection.removeEventListener(
JitsiMeetJS.events.connection.CONNECTION_FAILED,
onConnectionFailed);
connection.removeEventListener(
JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED,
disconnect);
}
/**
*
*/
function unload() {
for (let i = 0; i < localTracks.length; i++) {
localTracks[i].dispose();
}
room.leave();
connection.disconnect();
}
$(window).bind('beforeunload', unload);
$(window).bind('unload', unload);
JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
const initOptions = {
disableAudioLevels: true,
// The ID of the jidesha extension for Chrome.
desktopSharingChromeExtId: 'mbocklcggfhnbahlnepmldehdhpjfcjp',
// Whether desktop sharing should be disabled on Chrome.
desktopSharingChromeDisabled: true,
// The media sources to use when using screen sharing with the Chrome
// extension.
desktopSharingChromeSources: [ 'screen', 'window' ],
// Required version of Chrome extension
desktopSharingChromeMinExtVersion: '0.1',
// Whether desktop sharing should be disabled on Firefox.
desktopSharingFirefoxDisabled: true
};
JitsiMeetJS.init(initOptions);
connection = new JitsiMeetJS.JitsiConnection(null, null, options);
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, onConnectionSuccess);
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, onConnectionFailed);
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, disconnect);
connection.connect();
const devices = [];
if (localVideo) {
devices.push('video');
}
if (localAudio) {
devices.push('audio');
}
if (devices.length > 0) {
JitsiMeetJS.createLocalTracks({ devices })
.then(onLocalTracks)
.catch(error => {
throw error;
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,55 +0,0 @@
{
"name": "jitsi-meet-load-test",
"version": "0.0.0",
"description": "A load test participant",
"repository": {
"type": "git",
"url": "git://github.com/jitsi/jitsi-meet"
},
"keywords": [
"jingle",
"webrtc",
"xmpp",
"browser"
],
"author": "",
"readmeFilename": "../README.md",
"dependencies": {
"jquery": "3.4.0"
},
"devDependencies": {
"@babel/core": "7.5.5",
"@babel/plugin-proposal-class-properties": "7.1.0",
"@babel/plugin-proposal-export-default-from": "7.0.0",
"@babel/plugin-proposal-export-namespace-from": "7.0.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.4.4",
"@babel/plugin-proposal-optional-chaining": "7.2.0",
"@babel/plugin-transform-flow-strip-types": "7.0.0",
"@babel/preset-env": "7.1.0",
"@babel/preset-flow": "7.0.0",
"@babel/runtime": "7.5.5",
"babel-eslint": "10.0.1",
"babel-loader": "8.0.4",
"eslint": "5.6.1",
"eslint-config-jitsi": "github:jitsi/eslint-config-jitsi#1.0.1",
"eslint-plugin-flowtype": "2.50.3",
"eslint-plugin-import": "2.14.0",
"eslint-plugin-jsdoc": "3.8.0",
"expose-loader": "0.7.5",
"flow-bin": "0.104.0",
"imports-loader": "0.7.1",
"string-replace-loader": "2.1.1",
"style-loader": "0.19.0",
"webpack": "4.27.1",
"webpack-bundle-analyzer": "3.4.1",
"webpack-cli": "3.1.2"
},
"engines": {
"node": ">=8.0.0",
"npm": ">=6.0.0"
},
"license": "Apache-2.0",
"scripts": {
"build": "webpack -p"
}
}

View File

@@ -1,131 +0,0 @@
/* global __dirname */
const process = require('process');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const analyzeBundle = process.argv.indexOf('--analyze-bundle') !== -1;
const minimize
= process.argv.indexOf('-p') !== -1
|| process.argv.indexOf('--optimize-minimize') !== -1;
/**
* Build a Performance configuration object for the given size.
* See: https://webpack.js.org/configuration/performance/
*/
function getPerformanceHints(size) {
return {
hints: minimize ? 'error' : false,
maxAssetSize: size,
maxEntrypointSize: size
};
}
// The base Webpack configuration to bundle the JavaScript artifacts of
// jitsi-meet such as app.bundle.js and external_api.js.
const config = {
devtool: 'source-map',
mode: minimize ? 'production' : 'development',
module: {
rules: [ {
// Transpile ES2015 (aka ES6) to ES5. Accept the JSX syntax by React
// as well.
exclude: [
new RegExp(`${__dirname}/node_modules/(?!js-utils)`)
],
loader: 'babel-loader',
options: {
// XXX The require.resolve bellow solves failures to locate the
// presets when lib-jitsi-meet, for example, is npm linked in
// jitsi-meet.
plugins: [
require.resolve('@babel/plugin-transform-flow-strip-types'),
require.resolve('@babel/plugin-proposal-class-properties'),
require.resolve('@babel/plugin-proposal-export-default-from'),
require.resolve('@babel/plugin-proposal-export-namespace-from'),
require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'),
require.resolve('@babel/plugin-proposal-optional-chaining')
],
presets: [
[
require.resolve('@babel/preset-env'),
// Tell babel to avoid compiling imports into CommonJS
// so that webpack may do tree shaking.
{
modules: false,
// Specify our target browsers so no transpiling is
// done unnecessarily. For browsers not specified
// here, the ES2015+ profile will be used.
targets: {
chrome: 58,
electron: 2,
firefox: 54,
safari: 11
}
}
],
require.resolve('@babel/preset-flow'),
require.resolve('@babel/preset-react')
]
},
test: /\.jsx?$/
}, {
// Expose jquery as the globals $ and jQuery because it is expected
// to be available in such a form by multiple jitsi-meet
// dependencies including lib-jitsi-meet.
loader: 'expose-loader?$!expose-loader?jQuery',
test: /\/node_modules\/jquery\/.*\.js$/
}]
},
node: {
// Allow the use of the real filename of the module being executed. By
// default Webpack does not leak path-related information and provides a
// value that is a mock (/index.js).
__filename: true
},
optimization: {
concatenateModules: minimize,
minimize
},
output: {
filename: `[name]${minimize ? '.min' : ''}.js`,
path: `${__dirname}/libs`,
publicPath: 'load-test/libs/',
sourceMapFilename: `[name].${minimize ? 'min' : 'js'}.map`
},
plugins: [
analyzeBundle
&& new BundleAnalyzerPlugin({
analyzerMode: 'disabled',
generateStatsFile: true
})
].filter(Boolean),
resolve: {
alias: {
jquery: `jquery/dist/jquery${minimize ? '.min' : ''}.js`
},
aliasFields: [
'browser'
],
extensions: [
'.web.js',
// Webpack defaults:
'.js',
'.json'
]
}
};
module.exports = [
Object.assign({}, config, {
entry: {
'load-test-participant': './load-test-participant.js'
},
performance: getPerformanceHints(3 * 1024 * 1024)
})
];