mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-02-15 18:30:18 +00:00
Compare commits
46 Commits
android-sd
...
load-test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2ddf55d44 | ||
|
|
fb3a832a52 | ||
|
|
9c146c1245 | ||
|
|
792f506425 | ||
|
|
6121e9fc65 | ||
|
|
955fa1f49f | ||
|
|
2544d0a084 | ||
|
|
8f0a12016a | ||
|
|
34ccd3524f | ||
|
|
563e99ecd3 | ||
|
|
70f14be50f | ||
|
|
8bd0da886e | ||
|
|
1fd326f980 | ||
|
|
d9cc664ea6 | ||
|
|
d65e241056 | ||
|
|
fe2b1f3d9f | ||
|
|
17c1f50fc3 | ||
|
|
5c1c022291 | ||
|
|
72435dee56 | ||
|
|
42f2eff02a | ||
|
|
0b68bef0be | ||
|
|
676e943d81 | ||
|
|
2b4307dee9 | ||
|
|
f3f936c196 | ||
|
|
eb900ddbe1 | ||
|
|
c2c323347a | ||
|
|
af6642b91b | ||
|
|
ffded8d82a | ||
|
|
00b57c7983 | ||
|
|
5d40a8992a | ||
|
|
e543625295 | ||
|
|
0b25ff649e | ||
|
|
63344ac62d | ||
|
|
2e60aafebf | ||
|
|
131e8f4aea | ||
|
|
53f01a39c9 | ||
|
|
50f4796144 | ||
|
|
5bdfae377f | ||
|
|
44970648ea | ||
|
|
3cd7f0b77d | ||
|
|
4d243f9b92 | ||
|
|
6b716f8f56 | ||
|
|
5b99219f29 | ||
|
|
f0dcb51915 | ||
|
|
3ff658a13b | ||
|
|
3a46513d4b |
@@ -27,10 +27,18 @@ 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).
|
||||
|
||||
@@ -24,7 +24,7 @@ platform :android do
|
||||
|
||||
# Upload built artifact to the Closed Beta track
|
||||
upload_to_play_store(
|
||||
track: "Closed Beta",
|
||||
track: "beta",
|
||||
json_key: ENV["JITSI_JSON_KEY_FILE"],
|
||||
skip_upload_metadata: true,
|
||||
skip_upload_images: true,
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
appVersion=19.4.0
|
||||
sdkVersion=2.4.0
|
||||
appVersion=19.5.0
|
||||
sdkVersion=2.5.0
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.telecom.CallAudioState;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.HashSet;
|
||||
@@ -52,20 +53,20 @@ class AudioDeviceHandlerConnectionService implements
|
||||
*/
|
||||
private static int audioDeviceToRouteInt(String audioDevice) {
|
||||
if (audioDevice == null) {
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
return CallAudioState.ROUTE_EARPIECE;
|
||||
}
|
||||
switch (audioDevice) {
|
||||
case AudioModeModule.DEVICE_BLUETOOTH:
|
||||
return android.telecom.CallAudioState.ROUTE_BLUETOOTH;
|
||||
return CallAudioState.ROUTE_BLUETOOTH;
|
||||
case AudioModeModule.DEVICE_EARPIECE:
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
return CallAudioState.ROUTE_EARPIECE;
|
||||
case AudioModeModule.DEVICE_HEADPHONES:
|
||||
return android.telecom.CallAudioState.ROUTE_WIRED_HEADSET;
|
||||
return CallAudioState.ROUTE_WIRED_HEADSET;
|
||||
case AudioModeModule.DEVICE_SPEAKER:
|
||||
return android.telecom.CallAudioState.ROUTE_SPEAKER;
|
||||
return CallAudioState.ROUTE_SPEAKER;
|
||||
default:
|
||||
JitsiMeetLogger.e(TAG + " Unsupported device name: " + audioDevice);
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
return CallAudioState.ROUTE_EARPIECE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,20 +79,16 @@ class AudioDeviceHandlerConnectionService implements
|
||||
*/
|
||||
private static Set<String> routesToDeviceNames(int supportedRouteMask) {
|
||||
Set<String> devices = new HashSet<>();
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_EARPIECE)
|
||||
== android.telecom.CallAudioState.ROUTE_EARPIECE) {
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_EARPIECE) == CallAudioState.ROUTE_EARPIECE) {
|
||||
devices.add(AudioModeModule.DEVICE_EARPIECE);
|
||||
}
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_BLUETOOTH)
|
||||
== android.telecom.CallAudioState.ROUTE_BLUETOOTH) {
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_BLUETOOTH) == CallAudioState.ROUTE_BLUETOOTH) {
|
||||
devices.add(AudioModeModule.DEVICE_BLUETOOTH);
|
||||
}
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_SPEAKER)
|
||||
== android.telecom.CallAudioState.ROUTE_SPEAKER) {
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_SPEAKER) == CallAudioState.ROUTE_SPEAKER) {
|
||||
devices.add(AudioModeModule.DEVICE_SPEAKER);
|
||||
}
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_WIRED_HEADSET)
|
||||
== android.telecom.CallAudioState.ROUTE_WIRED_HEADSET) {
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_WIRED_HEADSET) == CallAudioState.ROUTE_WIRED_HEADSET) {
|
||||
devices.add(AudioModeModule.DEVICE_HEADPHONES);
|
||||
}
|
||||
return devices;
|
||||
@@ -109,13 +106,13 @@ class AudioDeviceHandlerConnectionService implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallAudioStateChange(final android.telecom.CallAudioState callAudioState) {
|
||||
public void onCallAudioStateChange(final CallAudioState state) {
|
||||
module.runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean audioRouteChanged
|
||||
= audioDeviceToRouteInt(module.getSelectedDevice()) != callAudioState.getRoute();
|
||||
int newSupportedRoutes = callAudioState.getSupportedRouteMask();
|
||||
= audioDeviceToRouteInt(module.getSelectedDevice()) != state.getRoute();
|
||||
int newSupportedRoutes = state.getSupportedRouteMask();
|
||||
boolean audioDevicesChanged = supportedRouteMask != newSupportedRoutes;
|
||||
if (audioDevicesChanged) {
|
||||
supportedRouteMask = newSupportedRoutes;
|
||||
@@ -140,6 +137,8 @@ class AudioDeviceHandlerConnectionService implements
|
||||
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
|
||||
if (rcs != null) {
|
||||
rcs.setCallAudioStateListener(this);
|
||||
} else {
|
||||
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +147,8 @@ class AudioDeviceHandlerConnectionService implements
|
||||
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
|
||||
if (rcs != null) {
|
||||
rcs.setCallAudioStateListener(null);
|
||||
} else {
|
||||
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -198,11 +198,11 @@ class AudioDeviceHandlerGeneric implements
|
||||
|
||||
@Override
|
||||
public void setAudioRoute(String device) {
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
|
||||
// Turn speaker on / off
|
||||
audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
|
||||
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -196,11 +196,11 @@ class AudioDeviceHandlerLegacy implements
|
||||
|
||||
@Override
|
||||
public void setAudioRoute(String device) {
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
|
||||
// Turn speaker on / off
|
||||
audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
|
||||
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -136,8 +136,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
public AudioModeModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
setAudioDeviceHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,6 +191,16 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the audio device handler module. This function is called *after* all Catalyst
|
||||
* modules have been created, and that's why we use it, because {@link AudioDeviceHandlerConnectionService}
|
||||
* needs access to another Catalyst module, so doing this in the constructor would be too early.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
setAudioDeviceHandler();
|
||||
}
|
||||
|
||||
private void setAudioDeviceHandler() {
|
||||
if (audioDeviceHandler != null) {
|
||||
audioDeviceHandler.stop();
|
||||
|
||||
@@ -349,7 +349,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
urlProps.putString("jwt", token);
|
||||
}
|
||||
|
||||
if (token == null && userInfo != null) {
|
||||
if (userInfo != null) {
|
||||
props.putBundle("userInfo", userInfo.asBundle());
|
||||
}
|
||||
|
||||
|
||||
@@ -1205,12 +1205,16 @@ 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 (nick) {
|
||||
options.displayName = nick;
|
||||
if (options.enableDisplayNameInStats && nick) {
|
||||
options.statisticsDisplayName = nick;
|
||||
}
|
||||
|
||||
if (options.enableEmailInStats && email) {
|
||||
options.statisticsId = email;
|
||||
}
|
||||
|
||||
options.applicationName = interfaceConfig.APP_NAME;
|
||||
|
||||
@@ -292,13 +292,11 @@ 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
|
||||
//
|
||||
|
||||
@@ -205,6 +205,10 @@
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
.usermessage {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-radius: 0px;
|
||||
|
||||
@@ -226,6 +230,8 @@
|
||||
|
||||
.messagecontent {
|
||||
margin: 5px 10px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -489,6 +489,8 @@
|
||||
height: 300px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#mixedstream {
|
||||
|
||||
14
doc/api.md
14
doc/api.md
@@ -30,6 +30,7 @@ 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:
|
||||
|
||||
@@ -84,6 +85,19 @@ 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:
|
||||
|
||||
@@ -45,7 +45,7 @@ server {
|
||||
proxy_set_header Host $http_host;
|
||||
}
|
||||
|
||||
location ~ ^/([^?&:’“]+)$ {
|
||||
location ~ ^/([^/?&:'"]+)$ {
|
||||
try_files $uri @root_path;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<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">
|
||||
@@ -151,6 +152,7 @@
|
||||
<!--#include virtual="static/settingsToolbarAdditionalContent.html" -->
|
||||
</head>
|
||||
<body>
|
||||
<!--#include virtual="body.html" -->
|
||||
<div id="react"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
platform :ios, '10.0'
|
||||
platform :ios, '11.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'] = '10.0'
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -274,7 +274,7 @@ PODS:
|
||||
- React
|
||||
- react-native-netinfo (4.1.5):
|
||||
- React
|
||||
- react-native-webrtc (1.75.0):
|
||||
- react-native-webrtc (1.75.2):
|
||||
- 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: c5e3d631179a933548a8e49bddbd8fad02586095
|
||||
react-native-webrtc: f6783727706d8bec5fb302b76eda60c33dfe3191
|
||||
react-native-webview: 4dbc1d2a4a6b9c5e9e723c62651917aa2b5e579e
|
||||
React-RCTActionSheet: 94671eef55b01a93be735605822ef712d5ea208e
|
||||
React-RCTAnimation: 524ae33e73de9c0fe6501a7a4bda8e01d26499d9
|
||||
@@ -553,6 +553,6 @@ SPEC CHECKSUMS:
|
||||
RNWatch: 09738b339eceb66e4d80a2371633ca5fb380fa42
|
||||
Yoga: 02036f6383c0008edb7ef0773a0e6beb6ce82bd1
|
||||
|
||||
PODFILE CHECKSUM: 63c90b1d33cd96709fb72bad6be440ae9c3deecb
|
||||
PODFILE CHECKSUM: 0fdfa45ae809c9460c80be3e0d4bbb822fccc418
|
||||
|
||||
COCOAPODS: 1.8.1
|
||||
|
||||
@@ -747,7 +747,6 @@
|
||||
/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 = (
|
||||
@@ -758,7 +757,6 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet;
|
||||
PRODUCT_NAME = "jitsi-meet";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -783,7 +781,6 @@
|
||||
/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 = (
|
||||
@@ -794,7 +791,6 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet;
|
||||
PRODUCT_NAME = "jitsi-meet";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -850,10 +846,11 @@
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -902,10 +899,11 @@
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>19.4.0</string>
|
||||
<string>19.5.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>19.4.0</string>
|
||||
<string>19.5.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>19.4.0</string>
|
||||
<string>19.5.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
|
||||
@@ -61,7 +61,21 @@ platform :ios do
|
||||
)
|
||||
|
||||
# Upload the build to TestFlight (but don't distribute it)
|
||||
upload_to_testflight(skip_submission: true, skip_waiting_for_build_processing: true)
|
||||
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
|
||||
)
|
||||
|
||||
# Cleanup
|
||||
clean_build_artifacts
|
||||
|
||||
@@ -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 archive
|
||||
xcodebuild -workspace ios/jitsi-meet.xcworkspace -scheme JitsiMeet -destination='generic/platform=iOS' -configuration Release ENABLE_BITCODE=NO clean archive
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
git tag ios-sdk-${SDK_VERSION}
|
||||
fi
|
||||
|
||||
@@ -555,7 +555,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.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 = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
@@ -639,7 +639,6 @@
|
||||
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)";
|
||||
@@ -650,7 +649,6 @@
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -671,7 +669,6 @@
|
||||
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)";
|
||||
@@ -681,7 +678,6 @@
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.4.0</string>
|
||||
<string>2.5.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -225,7 +225,7 @@ static NSString *const WelcomePageEnabledFeatureFlag = @"welcomepage.enabled";
|
||||
urlProps[@"jwt"] = _token;
|
||||
}
|
||||
|
||||
if (_token == nil && _userInfo != nil) {
|
||||
if (_userInfo != nil) {
|
||||
props[@"userInfo"] = [self.userInfo asDict];
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,10 @@
|
||||
},
|
||||
"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"
|
||||
|
||||
8
modules/API/external/external_api.js
vendored
8
modules/API/external/external_api.js
vendored
@@ -232,6 +232,8 @@ 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();
|
||||
@@ -246,7 +248,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
jwt = undefined,
|
||||
onload = undefined,
|
||||
invitees,
|
||||
devices
|
||||
devices,
|
||||
userInfo
|
||||
} = parseArguments(args);
|
||||
|
||||
this._parentNode = parentNode;
|
||||
@@ -256,7 +259,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
jwt,
|
||||
noSSL,
|
||||
roomName,
|
||||
devices
|
||||
devices,
|
||||
userInfo
|
||||
});
|
||||
this._createIFrame(height, width, onload);
|
||||
this._transport = new Transport({
|
||||
|
||||
@@ -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 = width / height;
|
||||
const currentAspectRatio = height === 0 ? 0 : width / height;
|
||||
|
||||
if (this._videoAspectRatio !== currentAspectRatio) {
|
||||
this._videoAspectRatio = currentAspectRatio;
|
||||
|
||||
@@ -5,10 +5,8 @@ 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 { ORIENTATION, LargeVideoBackground } from '../../../react/features/large-video';
|
||||
import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
import Filmstrip from './Filmstrip';
|
||||
@@ -55,8 +53,12 @@ function computeDesktopVideoSize( // eslint-disable-line max-params
|
||||
videoHeight,
|
||||
videoSpaceWidth,
|
||||
videoSpaceHeight) {
|
||||
const aspectRatio = videoWidth / videoHeight;
|
||||
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);
|
||||
|
||||
@@ -99,6 +101,11 @@ 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) {
|
||||
@@ -322,7 +329,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()) {
|
||||
@@ -414,13 +421,29 @@ export class VideoContainer extends LargeContainer {
|
||||
if (this.$video.length === 0) {
|
||||
return;
|
||||
}
|
||||
const currentLayout = getCurrentLayout(APP.store.getState());
|
||||
|
||||
const [ width, height ]
|
||||
= this.getVideoSize(containerWidth, containerHeight);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -429,15 +452,7 @@ export class VideoContainer extends LargeContainer {
|
||||
this._updateBackground();
|
||||
|
||||
const { horizontalIndent, verticalIndent }
|
||||
= 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.getVideoPosition(width, height, containerWidth, containerHeight);
|
||||
|
||||
this.$wrapper.animate({
|
||||
width,
|
||||
|
||||
@@ -22,7 +22,6 @@ 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';
|
||||
@@ -663,14 +662,6 @@ 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) {
|
||||
|
||||
@@ -9,43 +9,6 @@ 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.
|
||||
*
|
||||
@@ -63,34 +26,13 @@ export default class PostMessageTransportBackend {
|
||||
* @param {Object} options - Optional parameters for configuration of the
|
||||
* transport.
|
||||
*/
|
||||
constructor({ enableLegacyFormat, postisOptions } = {}) {
|
||||
constructor({ 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.
|
||||
@@ -101,37 +43,6 @@ 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.
|
||||
*
|
||||
@@ -152,14 +63,6 @@ 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 || {});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,12 +36,7 @@ let transport;
|
||||
*/
|
||||
export function getJitsiMeetTransport() {
|
||||
if (!transport) {
|
||||
transport = new Transport({
|
||||
backend: new PostMessageTransportBackend({
|
||||
enableLegacyFormat: true,
|
||||
postisOptions
|
||||
})
|
||||
});
|
||||
transport = new Transport({ backend: new PostMessageTransportBackend({ postisOptions }) });
|
||||
}
|
||||
|
||||
return transport;
|
||||
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -10931,8 +10931,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#1de69abe22aa632c9a4255ee9f6ae48dab9be756",
|
||||
"from": "github:jitsi/lib-jitsi-meet#1de69abe22aa632c9a4255ee9f6ae48dab9be756",
|
||||
"version": "github:jitsi/lib-jitsi-meet#dd31f0aff0a38b3cfd8e808e457a2e3a0f966514",
|
||||
"from": "github:jitsi/lib-jitsi-meet#dd31f0aff0a38b3cfd8e808e457a2e3a0f966514",
|
||||
"requires": {
|
||||
"@jitsi/sdp-interop": "0.1.14",
|
||||
"@jitsi/sdp-simulcast": "0.2.2",
|
||||
@@ -14887,8 +14887,9 @@
|
||||
"integrity": "sha512-l3Quzbb+qa4in2U5RSt/lT0/pHrIpEChT1NnqrVAAXNrjkXjVOsxduaaEDdDhTzNJQEm/PcAcoyrFmgvGOohxw=="
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "github:react-native-webrtc/react-native-webrtc#a12a6cdfdefe53d03b388394e4cf10966bd99fca",
|
||||
"from": "github:react-native-webrtc/react-native-webrtc#a12a6cdfdefe53d03b388394e4cf10966bd99fca",
|
||||
"version": "1.75.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.75.2.tgz",
|
||||
"integrity": "sha512-mdEukmHNhiyVIiwdooxk4kVXWG83OOENFV9YIkC7dtGU/sOdL81vDzynqd6Af9YbGMeOr0xdpFuEGsc1OFnKZg==",
|
||||
"requires": {
|
||||
"base64-js": "^1.1.2",
|
||||
"event-target-shim": "^1.0.5",
|
||||
|
||||
@@ -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#1de69abe22aa632c9a4255ee9f6ae48dab9be756",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#dd31f0aff0a38b3cfd8e808e457a2e3a0f966514",
|
||||
"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": "github:react-native-webrtc/react-native-webrtc#a12a6cdfdefe53d03b388394e4cf10966bd99fca",
|
||||
"react-native-webrtc": "1.75.2",
|
||||
"react-native-webview": "7.4.1",
|
||||
"react-redux": "7.1.0",
|
||||
"react-textarea-autosize": "7.1.0",
|
||||
|
||||
@@ -9,10 +9,7 @@ import { DialogContainer } from '../../base/dialog';
|
||||
import { CALL_INTEGRATION_ENABLED, updateFlags } from '../../base/flags';
|
||||
import '../../base/jwt';
|
||||
import { Platform } from '../../base/react';
|
||||
import {
|
||||
AspectRatioDetector,
|
||||
ReducedUIDetector
|
||||
} from '../../base/responsive-ui';
|
||||
import '../../base/responsive-ui';
|
||||
import { updateSettings } from '../../base/settings';
|
||||
import '../../google-api';
|
||||
import '../../mobile/audio-mode';
|
||||
@@ -110,22 +107,6 @@ 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
|
||||
|
||||
@@ -5,7 +5,6 @@ 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';
|
||||
|
||||
@@ -14,6 +14,7 @@ import { JitsiConferenceEvents } from '../lib-jitsi-meet';
|
||||
import { setAudioMuted, setVideoMuted } from '../media';
|
||||
import {
|
||||
dominantSpeakerChanged,
|
||||
getLocalParticipant,
|
||||
getNormalizedDisplayName,
|
||||
participantConnectionStatusChanged,
|
||||
participantKicked,
|
||||
@@ -393,14 +394,18 @@ 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), {
|
||||
...state['features/base/config'],
|
||||
...config,
|
||||
applicationName: getName(),
|
||||
getWiFiStatsMethod: getJitsiMeetGlobalNS().getWiFiStats,
|
||||
confID: `${locationURL.host}${locationURL.pathname}`
|
||||
confID: `${locationURL.host}${locationURL.pathname}`,
|
||||
statisticsDisplayName: config.enableDisplayNameInStats ? nick : undefined,
|
||||
statisticsId: config.enableEmailInStats ? email : undefined
|
||||
});
|
||||
|
||||
connection[JITSI_CONNECTION_CONFERENCE_KEY] = conference;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
participantLeft
|
||||
} from '../participants';
|
||||
import { toState } from '../redux';
|
||||
import { safeDecodeURIComponent } from '../util';
|
||||
|
||||
import {
|
||||
AVATAR_ID_COMMAND,
|
||||
@@ -163,7 +164,7 @@ export function getConferenceName(stateful: Function | Object): string {
|
||||
|| subject
|
||||
|| callDisplayName
|
||||
|| (callee && callee.name)
|
||||
|| _.startCase(decodeURIComponent(room));
|
||||
|| _.startCase(safeDecodeURIComponent(room));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,13 +178,15 @@ export function getConferenceName(stateful: Function | Object): string {
|
||||
* @returns {JitsiConference|undefined}
|
||||
*/
|
||||
export function getCurrentConference(stateful: Function | Object) {
|
||||
const { conference, joining, leaving }
|
||||
const { conference, joining, leaving, passwordRequired }
|
||||
= toState(stateful)['features/base/conference'];
|
||||
|
||||
return (
|
||||
conference
|
||||
? conference === leaving ? undefined : conference
|
||||
: joining);
|
||||
// There is a precendence
|
||||
if (conference) {
|
||||
return conference === leaving ? undefined : conference;
|
||||
}
|
||||
|
||||
return joining || passwordRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,10 +15,6 @@ export default [
|
||||
'autoRecord',
|
||||
'autoRecordToken',
|
||||
'avgRtpStatsN',
|
||||
'callFlowsEnabled',
|
||||
'callStatsConfIDNamespace',
|
||||
'callStatsID',
|
||||
'callStatsSecret',
|
||||
|
||||
/**
|
||||
* The display name of the CallKit call representing the conference/meeting
|
||||
@@ -34,6 +30,7 @@ export default [
|
||||
* @type string
|
||||
*/
|
||||
'callDisplayName',
|
||||
'callFlowsEnabled',
|
||||
|
||||
/**
|
||||
* The handle
|
||||
@@ -48,6 +45,9 @@ 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',
|
||||
'desktopSharingFrameRate',
|
||||
'desktopSharingFirefoxDisabled',
|
||||
'desktopSharingFrameRate',
|
||||
'desktopSharingSources',
|
||||
'disable1On1Mode',
|
||||
'disableAEC',
|
||||
@@ -84,6 +84,7 @@ export default [
|
||||
'disableDeepLinking',
|
||||
'disableH264',
|
||||
'disableHPF',
|
||||
'disableLocalVideoFlip',
|
||||
'disableNS',
|
||||
'disableRemoteControl',
|
||||
'disableRtx',
|
||||
@@ -91,11 +92,10 @@ export default [
|
||||
'displayJids',
|
||||
'e2eping',
|
||||
'enableDisplayNameInStats',
|
||||
'enableEmailInStats',
|
||||
'enableLayerSuspension',
|
||||
'enableLipSync',
|
||||
'disableLocalVideoFlip',
|
||||
'enableRemb',
|
||||
'enableStatsID',
|
||||
'enableTalkWhileMuted',
|
||||
'enableTcc',
|
||||
'etherpad_base',
|
||||
@@ -123,11 +123,12 @@ export default [
|
||||
'startAudioMuted',
|
||||
'startAudioOnly',
|
||||
'startBitrate',
|
||||
'startSilent',
|
||||
'startScreenSharing',
|
||||
'startSilent',
|
||||
'startVideoMuted',
|
||||
'startWithAudioMuted',
|
||||
'startWithVideoMuted',
|
||||
'stereo',
|
||||
'subject',
|
||||
'testing',
|
||||
'useIPv6',
|
||||
|
||||
@@ -22,6 +22,7 @@ export default [
|
||||
'DEFAULT_REMOTE_DISPLAY_NAME',
|
||||
'DISABLE_DOMINANT_SPEAKER_INDICATOR',
|
||||
'DISABLE_FOCUS_INDICATOR',
|
||||
'DISABLE_PRIVATE_MESSAGES',
|
||||
'DISABLE_RINGING',
|
||||
'DISABLE_TRANSCRIPTION_SUBTITLES',
|
||||
'DISABLE_VIDEO_BACKGROUND',
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
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';
|
||||
@@ -21,15 +20,6 @@ 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().
|
||||
@@ -50,11 +40,9 @@ const INITIAL_RN_STATE = {
|
||||
// fastest to merely disable them.
|
||||
disableAudioLevels: true,
|
||||
|
||||
disableH264: !RN_ENABLE_H264,
|
||||
|
||||
p2p: {
|
||||
disableH264: !RN_ENABLE_H264,
|
||||
preferH264: RN_ENABLE_H264
|
||||
disableH264: false,
|
||||
preferH264: true
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent, type Node } from 'react';
|
||||
import { Platform, SafeAreaView, ScrollView, View } from 'react-native';
|
||||
import { SafeAreaView, ScrollView, View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../color-scheme';
|
||||
import { SlidingView } from '../../../react';
|
||||
@@ -61,41 +61,18 @@ class BottomSheet extends PureComponent<Props> {
|
||||
styles.sheetItemContainer,
|
||||
_styles.sheet
|
||||
] }>
|
||||
{ this._getWrappedContent() }
|
||||
<SafeAreaView>
|
||||
<ScrollView
|
||||
bounces = { false }
|
||||
showsVerticalScrollIndicator = { false } >
|
||||
{ this.props.children }
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component, type Node } from 'react';
|
||||
import { Platform, SafeAreaView, StatusBar, View } from 'react-native';
|
||||
import React, { PureComponent, type Node } from 'react';
|
||||
import { SafeAreaView, StatusBar, View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../color-scheme';
|
||||
import { connect } from '../../../redux';
|
||||
import { isDarkColor } from '../../../styles';
|
||||
|
||||
import { HEADER_PADDING } from './headerstyles';
|
||||
|
||||
/**
|
||||
* Compatibility header padding size for iOS 10 (and older) devices.
|
||||
*/
|
||||
const IOS10_PADDING = 20;
|
||||
// Register style
|
||||
import './headerstyles';
|
||||
|
||||
/**
|
||||
* Constanst for the (currently) supported statusbar colors.
|
||||
@@ -44,19 +40,7 @@ type Props = {
|
||||
/**
|
||||
* A generic screen header component.
|
||||
*/
|
||||
class Header extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code Header} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._getIOS10CompatiblePadding
|
||||
= this._getIOS10CompatiblePadding.bind(this);
|
||||
}
|
||||
|
||||
class Header extends PureComponent<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -66,11 +50,7 @@ class Header extends Component<Props> {
|
||||
const { _styles } = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { [
|
||||
_styles.headerOverlay,
|
||||
this._getIOS10CompatiblePadding()
|
||||
] } >
|
||||
<View style = { _styles.headerOverlay }>
|
||||
<StatusBar
|
||||
backgroundColor = { _styles.statusBar }
|
||||
barStyle = { this._getStatusBarContentColor() }
|
||||
@@ -90,32 +70,6 @@ class Header extends Component<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.
|
||||
|
||||
@@ -7,8 +7,7 @@ import { BoxModel } from '../../../styles';
|
||||
|
||||
const HEADER_FONT_SIZE = 18;
|
||||
const HEADER_HEIGHT = 48;
|
||||
|
||||
export const HEADER_PADDING = BoxModel.padding / 2;
|
||||
const HEADER_PADDING = BoxModel.padding / 2;
|
||||
|
||||
ColorSchemeRegistry.register('Header', {
|
||||
|
||||
|
||||
@@ -12,9 +12,7 @@ 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. I'm bumping from 240 to 300 because I
|
||||
* don't have the time now to refactor {@code ReducedUIDetector} or rip it out
|
||||
* completely.
|
||||
* determine whether and how to render it.
|
||||
*/
|
||||
const REDUCED_UI_THRESHOLD = 300;
|
||||
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
// @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);
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React, { PureComponent } 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 Component<Props> {
|
||||
export default class DimensionsDetector extends PureComponent<Props> {
|
||||
/**
|
||||
* Initializes a new DimensionsDetector instance.
|
||||
*
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
// @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);
|
||||
@@ -1,4 +1,2 @@
|
||||
export * from './AspectRatioAware';
|
||||
export { default as AspectRatioDetector } from './AspectRatioDetector';
|
||||
export { default as DimensionsDetector } from './DimensionsDetector';
|
||||
export { default as ReducedUIDetector } from './ReducedUIDetector';
|
||||
|
||||
@@ -3,4 +3,5 @@ export * from './actionTypes';
|
||||
export * from './components';
|
||||
export * from './constants';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
83
react/features/base/responsive-ui/middleware.native.js
Normal file
83
react/features/base/responsive-ui/middleware.native.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// @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));
|
||||
}
|
||||
0
react/features/base/responsive-ui/middleware.web.js
Normal file
0
react/features/base/responsive-ui/middleware.web.js
Normal file
@@ -13,16 +13,14 @@ 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;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// @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';
|
||||
|
||||
@@ -19,15 +23,37 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT:
|
||||
_initializeCallIntegration(store);
|
||||
break;
|
||||
case SETTINGS_UPDATED:
|
||||
_maybeHandleCallIntegrationChange(action);
|
||||
_maybeSetAudioOnly(store, action);
|
||||
_updateLocalParticipant(store, action);
|
||||
break;
|
||||
case SET_LOCATION_URL:
|
||||
_updateLocalParticipantFromUrl(store);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes the audio device handler based on the `disableCallIntegration` setting.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _initializeCallIntegration({ getState }) {
|
||||
const { disableCallIntegration } = getState()['features/base/settings'];
|
||||
|
||||
if (typeof disableCallIntegration === 'boolean') {
|
||||
handleCallIntegrationChange(disableCallIntegration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the settings field names to participant names where they don't match.
|
||||
* Currently there is only one such field, but may be extended in the future.
|
||||
@@ -46,7 +72,7 @@ function _mapSettingsFieldToParticipant(settingsField) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates {@code startAudioOnly} flag if it's updated in the settings.
|
||||
* Handles a change in the `disableCallIntegration` setting.
|
||||
*
|
||||
* @param {Object} action - The redux action.
|
||||
* @private
|
||||
@@ -98,3 +124,30 @@ 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)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ 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.
|
||||
*/
|
||||
@@ -87,7 +89,9 @@ class PersistenceRegistry {
|
||||
// Initialize the checksum.
|
||||
this._checksum = this._calculateChecksum(filteredPersistedState);
|
||||
|
||||
logger.info('redux state rehydrated as', filteredPersistedState);
|
||||
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
||||
logger.info('redux state rehydrated as', filteredPersistedState);
|
||||
}
|
||||
|
||||
return filteredPersistedState;
|
||||
}
|
||||
@@ -113,7 +117,6 @@ class PersistenceRegistry {
|
||||
logger.error(
|
||||
'Error persisting redux subtree',
|
||||
subtreeName,
|
||||
filteredState[subtreeName],
|
||||
error);
|
||||
}
|
||||
}
|
||||
@@ -153,7 +156,7 @@ class PersistenceRegistry {
|
||||
try {
|
||||
return md5.hex(JSON.stringify(state) || '');
|
||||
} catch (error) {
|
||||
logger.error('Error calculating checksum for state', state, error);
|
||||
logger.error('Error calculating checksum for state', error);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -117,8 +117,8 @@ class TestConnectionInfo extends Component<Props, State> {
|
||||
this.setState({
|
||||
stats: {
|
||||
bitrate: {
|
||||
download: stats.bitrate.download,
|
||||
upload: stats.bitrate.upload
|
||||
download: stats.bitrate?.download || 0,
|
||||
upload: stats.bitrate?.upload || 0
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -373,6 +373,23 @@ 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
|
||||
@@ -510,7 +527,7 @@ export function urlObjectToString(o: Object): ?string {
|
||||
|
||||
let { hash } = url;
|
||||
|
||||
for (const urlPrefix of [ 'config', 'interfaceConfig', 'devices' ]) {
|
||||
for (const urlPrefix of [ 'config', 'interfaceConfig', 'devices', 'userInfo' ]) {
|
||||
const urlParamsArray
|
||||
= _objectToURLParamsArray(
|
||||
o[`${urlPrefix}Overwrite`]
|
||||
|
||||
@@ -15,7 +15,7 @@ export type Props = {
|
||||
*
|
||||
* @extends PureComponent
|
||||
*/
|
||||
export default class AbstractMessageContainer extends PureComponent<Props> {
|
||||
export default class AbstractMessageContainer<P: Props> extends PureComponent<P> {
|
||||
static defaultProps = {
|
||||
messages: []
|
||||
};
|
||||
@@ -46,7 +46,7 @@ export default class AbstractMessageContainer extends PureComponent<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
groups.push(currentGrouping);
|
||||
currentGrouping.length && groups.push(currentGrouping);
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
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';
|
||||
|
||||
@@ -13,7 +14,12 @@ type Props = {
|
||||
/**
|
||||
* Callback to invoke on message send.
|
||||
*/
|
||||
onSend: Function
|
||||
onSend: Function,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -37,7 +43,7 @@ type State = {
|
||||
/**
|
||||
* Implements the chat input bar with text field and action(s).
|
||||
*/
|
||||
export default class ChatInputBar extends Component<Props, State> {
|
||||
class ChatInputBar extends Component<Props, State> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
@@ -53,6 +59,7 @@ export default 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);
|
||||
}
|
||||
@@ -76,6 +83,8 @@ export default 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 } />
|
||||
@@ -105,6 +114,18 @@ export default 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;
|
||||
|
||||
/**
|
||||
@@ -138,3 +159,5 @@ export default class ChatInputBar extends Component<Props, State> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(ChatInputBar);
|
||||
|
||||
@@ -1,18 +1,36 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
import { FlatList, Text, View } from 'react-native';
|
||||
|
||||
import AbstractMessageContainer, { type Props }
|
||||
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 }
|
||||
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.
|
||||
*/
|
||||
export default class MessageContainer extends AbstractMessageContainer {
|
||||
class MessageContainer extends AbstractMessageContainer<Props> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
@@ -22,6 +40,7 @@ export default class MessageContainer extends AbstractMessageContainer {
|
||||
super(props);
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
this._renderListEmptyComponent = this._renderListEmptyComponent.bind(this);
|
||||
this._renderMessageGroup = this._renderMessageGroup.bind(this);
|
||||
}
|
||||
|
||||
@@ -31,10 +50,16 @@ export default class MessageContainer extends AbstractMessageContainer {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const data = this._getMessagesGroupedBySender();
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data = { this._getMessagesGroupedBySender() }
|
||||
inverted = { true }
|
||||
ListEmptyComponent = { this._renderListEmptyComponent }
|
||||
data = { data }
|
||||
|
||||
// Workaround for RN bug:
|
||||
// https://github.com/facebook/react-native/issues/21196
|
||||
inverted = { Boolean(data.length) }
|
||||
keyExtractor = { this._keyExtractor }
|
||||
keyboardShouldPersistTaps = 'always'
|
||||
renderItem = { this._renderMessageGroup }
|
||||
@@ -58,7 +83,26 @@ export default class MessageContainer extends AbstractMessageContainer {
|
||||
return `key_${index}`;
|
||||
}
|
||||
|
||||
_renderMessageGroup: Object => React$Element<*>;
|
||||
_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>;
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
@@ -70,3 +114,17 @@ export default class MessageContainer extends AbstractMessageContainer {
|
||||
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));
|
||||
|
||||
@@ -42,6 +42,13 @@ 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).
|
||||
*/
|
||||
@@ -143,6 +150,11 @@ ColorSchemeRegistry.register('Chat', {
|
||||
fontSize: 13
|
||||
},
|
||||
|
||||
emptyComponentText: {
|
||||
color: schemeColor('displayName'),
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
localMessageBubble: {
|
||||
backgroundColor: schemeColor('localMsgBackground'),
|
||||
borderTopRightRadius: 0
|
||||
|
||||
@@ -14,7 +14,7 @@ import ChatMessageGroup from './ChatMessageGroup';
|
||||
*
|
||||
* @extends AbstractMessageContainer
|
||||
*/
|
||||
export default class MessageContainer extends AbstractMessageContainer {
|
||||
export default class MessageContainer extends AbstractMessageContainer<Props> {
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -20,7 +20,7 @@ export function notifyKickedOut(participant: Object, _: ?Function) { // eslint-d
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const args = {
|
||||
participantDisplayName:
|
||||
getParticipantDisplayName(getState, participant.getDisplayName())
|
||||
getParticipantDisplayName(getState, participant.getId())
|
||||
};
|
||||
|
||||
dispatch(showNotification({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
|
||||
import { NativeModules, SafeAreaView, StatusBar } from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
|
||||
import { appNavigate } from '../../../app';
|
||||
@@ -279,7 +279,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
</TintedView>
|
||||
}
|
||||
|
||||
<View
|
||||
<SafeAreaView
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.toolboxAndFilmstripContainer }>
|
||||
|
||||
@@ -320,7 +320,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
*/
|
||||
_shouldDisplayTileView ? undefined : <Filmstrip />
|
||||
}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
|
||||
<SafeAreaView
|
||||
pointerEvents = 'box-none'
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
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';
|
||||
|
||||
@@ -13,6 +14,11 @@ 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.
|
||||
@@ -39,12 +45,12 @@ class Subject extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _subject, _visible } = this.props;
|
||||
const { _showParticipantCount, _subject, _visible } = this.props;
|
||||
|
||||
return (
|
||||
<div className = { `subject ${_visible ? 'visible' : ''}` }>
|
||||
<span className = 'subject-text'>{ _subject }</span>
|
||||
<ParticipantsCount />
|
||||
{ _showParticipantCount && <ParticipantsCount /> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -62,8 +68,10 @@ class Subject extends Component<Props> {
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const participantCount = getParticipantCount(state);
|
||||
|
||||
return {
|
||||
_showParticipantCount: participantCount > 2,
|
||||
_subject: getConferenceName(state),
|
||||
_visible: isToolboxVisible(state)
|
||||
};
|
||||
|
||||
@@ -94,6 +94,23 @@ 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'>
|
||||
@@ -113,20 +130,18 @@ class DeepLinkingMobilePage extends Component<Props> {
|
||||
{ t(`${_TNS}.appNotInstalled`, { app: NATIVE_APP_NAME }) }
|
||||
</p>
|
||||
<a
|
||||
{ ...onOpenLinkProperties }
|
||||
href = { this._generateDownloadURL() }
|
||||
onClick = { this._onDownloadApp }
|
||||
rel = 'noopener noreferrer'
|
||||
target = '_blank'>
|
||||
onClick = { this._onDownloadApp }>
|
||||
<button className = { downloadButtonClassName }>
|
||||
{ t(`${_TNS}.downloadApp`) }
|
||||
</button>
|
||||
</a>
|
||||
<a
|
||||
{ ...onOpenLinkProperties }
|
||||
className = { `${_SNS}__href` }
|
||||
href = { generateDeepLinkingURL() }
|
||||
onClick = { this._onOpenApp }
|
||||
rel = 'noopener noreferrer'
|
||||
target = '_blank'>
|
||||
onClick = { this._onOpenApp }>
|
||||
{/* <button className = { `${_SNS}__button` }> */}
|
||||
{ t(`${_TNS}.openApp`) }
|
||||
{/* </button> */}
|
||||
|
||||
@@ -15,10 +15,10 @@ import {
|
||||
CONNECTION_DISCONNECTED,
|
||||
CONNECTION_FAILED,
|
||||
JITSI_CONNECTION_CONFERENCE_KEY,
|
||||
JITSI_CONNECTION_URL_KEY
|
||||
JITSI_CONNECTION_URL_KEY,
|
||||
getURLWithoutParams
|
||||
} 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: toURLString(locationURL)
|
||||
url: _normalizeUrl(locationURL)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
CONFERENCE_TERMINATED,
|
||||
/* data */ {
|
||||
error: _toErrorString(error),
|
||||
url: toURLString(locationURL)
|
||||
url: _normalizeUrl(locationURL)
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -161,10 +161,20 @@ function _maybeTriggerEarlyConferenceWillJoin(store, action) {
|
||||
store,
|
||||
CONFERENCE_WILL_JOIN,
|
||||
/* data */ {
|
||||
url: toURLString(locationURL)
|
||||
url: _normalizeUrl(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.
|
||||
@@ -186,7 +196,7 @@ function _sendConferenceEvent(
|
||||
// instance. The external API cannot transport such an object so we have to
|
||||
// transport an "equivalent".
|
||||
if (conference) {
|
||||
data.url = toURLString(conference[JITSI_CONFERENCE_URL_KEY]);
|
||||
data.url = _normalizeUrl(conference[JITSI_CONFERENCE_URL_KEY]);
|
||||
}
|
||||
|
||||
if (_swallowEvent(store, action, data)) {
|
||||
@@ -233,7 +243,7 @@ function _sendConferenceFailedOnConnectionError(store, action) {
|
||||
store,
|
||||
CONFERENCE_TERMINATED,
|
||||
/* data */ {
|
||||
url: toURLString(locationURL),
|
||||
url: _normalizeUrl(locationURL),
|
||||
error: action.error.name
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
getLocalizedDurationFormatter
|
||||
} from '../base/i18n';
|
||||
import { NavigateSectionList } from '../base/react';
|
||||
import { parseURIString } from '../base/util';
|
||||
import { parseURIString, safeDecodeURIComponent } 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: decodeURIComponent(location.room),
|
||||
title: safeDecodeURIComponent(location.room),
|
||||
url: item.conference
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,10 +5,25 @@ import React, { Component } from 'react';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconMessage } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { _mapDispatchToProps, _mapStateToProps, type Props } from '../../../chat/components/PrivateMessageButton';
|
||||
import {
|
||||
_mapDispatchToProps,
|
||||
_mapStateToProps as _abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from '../../../chat/components/PrivateMessageButton';
|
||||
import { isButtonEnabled } from '../../../toolbox';
|
||||
|
||||
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
|
||||
@@ -34,7 +49,11 @@ class PrivateMessageMenuButton extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { participantID, t } = this.props;
|
||||
const { participantID, t, _hidden } = this.props;
|
||||
|
||||
if (_hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RemoteVideoMenuButton
|
||||
@@ -59,4 +78,19 @@ 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));
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Text, View } from 'react-native';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import styles, { ANDROID_UNDERLINE_COLOR } from './styles';
|
||||
import styles, { ANDROID_UNDERLINE_COLOR, PLACEHOLDER_COLOR } from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link FormRow}
|
||||
@@ -107,6 +107,7 @@ class FormRow extends Component<Props> {
|
||||
switch (field.type.displayName) {
|
||||
case 'TextInput':
|
||||
return {
|
||||
placeholderTextColor: PLACEHOLDER_COLOR,
|
||||
style: styles.textInputField,
|
||||
underlineColorAndroid: ANDROID_UNDERLINE_COLOR
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
} from '../../../base/styles';
|
||||
|
||||
export const ANDROID_UNDERLINE_COLOR = 'transparent';
|
||||
export const PLACEHOLDER_COLOR = ColorPalette.lightGrey;
|
||||
|
||||
const TEXT_SIZE = 17;
|
||||
|
||||
/**
|
||||
@@ -79,6 +81,7 @@ export default createStyleSheet({
|
||||
* Standard text input field style.
|
||||
*/
|
||||
textInputField: {
|
||||
color: ColorPalette.black,
|
||||
flex: 1,
|
||||
fontSize: TEXT_SIZE,
|
||||
textAlign: 'right'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
@@ -46,37 +46,10 @@ 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 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);
|
||||
}
|
||||
|
||||
class Toolbox extends PureComponent<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -86,7 +59,6 @@ class Toolbox extends Component<Props, State> {
|
||||
render() {
|
||||
return (
|
||||
<Container
|
||||
onLayout = { this._onLayout }
|
||||
style = { styles.toolbox }
|
||||
visible = { this.props._visible }>
|
||||
{ this._renderToolbar() }
|
||||
@@ -125,20 +97,6 @@ class Toolbox extends Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
_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
|
||||
|
||||
@@ -57,7 +57,10 @@ import {
|
||||
} from '../../../settings';
|
||||
import { toggleSharedVideo } from '../../../shared-video';
|
||||
import { SpeakerStats } from '../../../speaker-stats';
|
||||
import { TileViewButton } from '../../../video-layout';
|
||||
import {
|
||||
TileViewButton,
|
||||
toggleTileView
|
||||
} from '../../../video-layout';
|
||||
import {
|
||||
OverflowMenuVideoQualityItem,
|
||||
VideoQualityDialog
|
||||
@@ -122,6 +125,11 @@ 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.
|
||||
@@ -236,6 +244,7 @@ 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
|
||||
@@ -274,6 +283,11 @@ 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'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -475,6 +489,16 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -565,6 +589,24 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -1295,6 +1337,7 @@ 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,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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";
|
||||
@@ -25,7 +26,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(roomAddress);
|
||||
local room = get_room_from_jid(room_jid_match_rewrite(roomAddress));
|
||||
|
||||
if not room then
|
||||
log("warn", "No room found %s", roomAddress);
|
||||
|
||||
12
static/load-test/load-test-participant.html
Normal file
12
static/load-test/load-test-participant.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!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>
|
||||
261
static/load-test/load-test-participant.js
Normal file
261
static/load-test/load-test-participant.js
Normal file
@@ -0,0 +1,261 @@
|
||||
/* 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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
6983
static/load-test/package-lock.json
generated
Normal file
6983
static/load-test/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
55
static/load-test/package.json
Normal file
55
static/load-test/package.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
131
static/load-test/webpack.config.js
Normal file
131
static/load-test/webpack.config.js
Normal file
@@ -0,0 +1,131 @@
|
||||
/* 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)
|
||||
})
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user