Compare commits

..

6 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
12082ebb70 rn: set SDK version to 2.9.2 2020-07-17 16:30:35 +02:00
Saúl Ibarra Corretgé
0d826dc58d ios: set version to 20.3.1 2020-07-17 16:08:31 +02:00
Saúl Ibarra Corretgé
cb47720cb4 HOTFIX: fix participant kicking 2020-07-17 16:07:00 +02:00
Saúl Ibarra Corretgé
036f4166fe android: set version to 20.3.1 2020-07-17 14:21:59 +02:00
Saúl Ibarra Corretgé
eb1f057ba2 deps: react-native-calendar-events@latest
Fixes a crash on Android.
2020-07-17 14:21:09 +02:00
Saúl Ibarra Corretgé
5b2d28490b rn: set SDK version to 2.9.1 2020-07-14 13:57:16 +02:00
121 changed files with 993 additions and 2935 deletions

View File

@@ -20,5 +20,5 @@
android.useAndroidX=true
android.enableJetifier=true
appVersion=20.4.0
sdkVersion=2.10.0
appVersion=20.3.1
sdkVersion=2.9.2

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present 8x8, Inc.
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -125,7 +126,7 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
= ReactInstanceManagerHolder.getNativeModule(
PictureInPictureModule.class);
if (pipModule != null
&& pipModule.isPictureInPictureSupported()
&& PictureInPictureModule.isPictureInPictureSupported()
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()
&& this.url != null) {
try {

View File

@@ -1,5 +1,5 @@
/*
* Copyright @ 2017-present 8x8, Inc.
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.jitsi.meet.sdk;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.os.Build;
import android.util.Rational;
@@ -31,41 +30,20 @@ import com.facebook.react.module.annotations.ReactModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.HashMap;
import java.util.Map;
import static android.content.Context.ACTIVITY_SERVICE;
@ReactModule(name = PictureInPictureModule.NAME)
class PictureInPictureModule extends ReactContextBaseJavaModule {
class PictureInPictureModule
extends ReactContextBaseJavaModule {
public static final String NAME = "PictureInPicture";
private static final String TAG = NAME;
private static boolean isSupported;
static boolean isPictureInPictureSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
public PictureInPictureModule(ReactApplicationContext reactContext) {
super(reactContext);
ActivityManager am = (ActivityManager) reactContext.getSystemService(ACTIVITY_SERVICE);
// Android Go devices don't support PiP. There doesn't seem to be a better way to detect it than
// to use ActivityManager.isLowRamDevice().
// https://stackoverflow.com/questions/58340558/how-to-detect-android-go
isSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !am.isLowRamDevice();
}
/**
* Gets a {@code Map} of constants this module exports to JS. Supports JSON
* types.
*
* @return a {@link Map} of constants this module exports to JS
*/
@Override
public Map<String, Object> getConstants() {
Map<String, Object> constants = new HashMap<>();
constants.put("SUPPORTED", isSupported);
return constants;
}
/**
@@ -83,7 +61,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
*/
@TargetApi(Build.VERSION_CODES.O)
public void enterPictureInPicture() {
if (!isSupported) {
if (!isPictureInPictureSupported()) {
throw new IllegalStateException("Picture-in-Picture not supported");
}
@@ -126,10 +104,6 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
}
}
public boolean isPictureInPictureSupported() {
return isSupported;
}
@Override
public String getName() {
return NAME;

View File

@@ -411,10 +411,6 @@ function disconnect() {
return Promise.resolve();
};
if (!connection) {
return onDisconnected();
}
return connection.disconnect().then(onDisconnected, onDisconnected);
}

View File

@@ -111,11 +111,6 @@ var config = {
// participants and to enable it back a reload is needed.
// startSilent: false
// Sets the preferred target bitrate for the Opus audio codec by setting its
// 'maxaveragebitrate' parameter. Currently not available in p2p mode.
// Valid values are in the range 6000 to 510000
// opusMaxAvgBitrate: 20000,
// Video
// Sets the preferred resolution (height) for local video. Defaults to 720.
@@ -406,15 +401,6 @@ var config = {
// The Amplitude APP Key:
// amplitudeAPPKey: '<APP_KEY>'
// Configuration for the rtcstats server:
// In order to enable rtcstats one needs to provide a endpoint url.
// rtcstatsEndpoint: wss://rtcstats-server-pilot.jitsi.net/,
// The interval at which rtcstats will poll getStats, defaults to 1000ms.
// If the value is set to 0 getStats won't be polled and the rtcstats client
// will only send data related to RTCPeerConnection events.
// rtcstatsPolIInterval: 1000
// Array of script URLs to load as lib-jitsi-meet "analytics handlers".
// scriptURLs: [
// "libs/analytics-ga.min.js", // google-analytics
@@ -522,7 +508,7 @@ var config = {
/**
External API url used to receive branding specific information.
If there is no url set or there are missing fields, the defaults are applied.
None of the fields are mandatory and the response must have the shape:
None of the fieds are mandatory and the response must have the shape:
{
// The hex value for the colour used as background
backgroundColor: '#fff',

View File

@@ -33,26 +33,6 @@ body {
}
}
/**
* AtlasKit sets a default margin on the rendered modals, so
* when the shift-right class is set when the chat opens, we
* pad the modal container in order for the modals to be centered
* while also taking the chat size into consideration.
*/
@media (min-width: 480px + $sidebarWidth) {
.shift-right [class^="Modal__FillScreen"] {
padding-left: $sidebarWidth;
}
}
/**
* Similarly, we offset the notifications when the chat is open by
* padding the container.
*/
.shift-right [class^="styledFlagGroup-"] {
padding-left: $sidebarWidth;
}
.jitsi-icon svg {
fill: white;
}

View File

@@ -4,11 +4,16 @@
color: #FFF;
display: flex;
flex-direction: column;
height: 100%;
/**
* Make the sidebar flush with the top of the toolbar. Take the size of
* the toolbar and subtract from 100%.
*/
height: calc(100% - #{$newToolbarSizeWithPadding});
left: -$sidebarWidth;
overflow: hidden;
position: absolute;
top: 0;
transition: left 0.5s;
width: $sidebarWidth;
z-index: $sideToolbarContainerZ;

View File

@@ -36,7 +36,13 @@
}
&-checkbox-container {
margin-bottom: 14px;
align-items: center;
color: #fff;
display: none;
font-size: 13px;
justify-content: center;
line-height: 20px;
margin-top: 16px;
width: 100%;
}
}

View File

@@ -1,21 +1,17 @@
/**
* Shared style for full screen local track based dialogs/modals.
*/
.premeeting-screen,
.preview-overlay {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.premeeting-screen {
.premeeting-screen {
align-items: stretch;
background: radial-gradient(50% 50% at 50% 50%, #5D95C7 0%, #376288 100%), #FFFFFF;
background: #1C2025;
bottom: 0;
display: flex;
flex-direction: column;
font-size: 1.3em;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: $toolbarZ + 1;
.action-btn {
@@ -78,13 +74,9 @@
}
}
.preview-overlay {
background-image: linear-gradient(transparent, black);
z-index: $toolbarZ + 1;
}
.content {
align-items: center;
background-image: linear-gradient(transparent, black);
display: flex;
flex: 1;
flex-direction: column;
@@ -197,16 +189,9 @@
text-align: center;
}
.preview-avatar-container {
width: 100%;
height: 80%;
display: flex;
align-items: center;
justify-content: center;
}
.avatar {
background: #A4B8D1;
margin: 200px auto 0 auto;
}
video {
@@ -216,66 +201,3 @@
width: 100%;
}
}
@mixin flex-centered() {
align-items: center;
display: flex;
justify-content: center;
}
@mixin icon-container($bg, $fill) {
.toggle-button-icon-container {
background: $bg;
svg {
fill: $fill
}
}
}
.toggle-button {
border-radius: 3px;
cursor: pointer;
color: #fff;
font-size: 13px;
height: 40px;
margin: 0 auto;
width: 320px;
@include flex-centered();
svg {
fill: transparent;
}
&:hover {
background: #1C2025;
@include icon-container(#A4B8D1, #1C2025);
}
&-container {
position: relative;
@include flex-centered();
}
&-icon-container {
border-radius: 50%;
left: -22px;
padding: 2px;
position: absolute;
}
&--toggled {
background: #75757A;
&:hover {
background: #75757A;
@include icon-container(#A4B8D1, #75757A);
}
@include icon-container(#A4B8D1, #75757A);
}
}

View File

@@ -42,11 +42,6 @@
display: none;
}
&.shift-right {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
}
.toolbox-background {
background-image: linear-gradient(to top, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0));
transition: bottom .3s ease-in;

View File

@@ -181,13 +181,6 @@
visibility: hidden;
z-index: $zindex2;
}
&.shift-right {
&#largeVideoContainer {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
}
}
}
#localVideoWrapper {

View File

@@ -46,16 +46,7 @@
position: fixed;
top: 0;
width: 100%;
z-index: $filmstripVideosZ;
&.shift-right {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
#filmstripRemoteVideos {
width: calc(100vw - #{$sidebarWidth});
}
}
z-index: $filmstripVideosZ
}
/**

View File

@@ -25,10 +25,6 @@
font-size: 14px;
color: #6FB1EA;
}
& > :first-child:not(:last-child) {
margin-right: 24px;
}
}
}
}

View File

@@ -30,11 +30,9 @@
width: 100%;
}
.profile-edit-field {
flex: 1;
}
.profile-edit-field,
.settings-sub-pane {
flex-grow: 1;
flex: 1;
}
.profile-edit-field {

View File

@@ -87,36 +87,9 @@ case "$1" in
if [[ -f $TURN_CONFIG ]] ; then
echo "------------------------------------------------"
echo ""
echo "turnserver is already configured on this machine."
echo "turnserver is already configured on this machine, skipping."
echo ""
echo "------------------------------------------------"
if grep -q "jitsi-meet coturn config" "$TURN_CONFIG" && ! grep -q "jitsi-meet coturn relay disable config" "$TURN_CONFIG" ; then
echo "Updating coturn config"
echo "# jitsi-meet coturn relay disable config. Do not modify this line
no-multicast-peers
no-cli
no-loopback-peers
no-tcp-relay
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.0.0.0-192.0.0.255
denied-peer-ip=192.0.2.0-192.0.2.255
denied-peer-ip=192.88.99.0-192.88.99.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=198.51.100.0-198.51.100.255
denied-peer-ip=203.0.113.0-203.0.113.255
denied-peer-ip=240.0.0.0-255.255.255.255" >> $TURN_CONFIG
invoke-rc.d coturn restart || true
fi
db_stop
exit 0
fi

View File

@@ -17,7 +17,6 @@ no-tlsv1
no-tlsv1_1
# https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=intermediate&openssl=1.1.0g&guideline=5.4
cipher-list=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
# jitsi-meet coturn relay disable config. Do not modify this line
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255

View File

@@ -8,17 +8,7 @@
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
<link rel="stylesheet" href="css/all.css">
<script>
document.addEventListener('DOMContentLoaded', () => {
if (!JitsiMeetJS.app) {
return;
}
JitsiMeetJS.app.renderEntryPoint({
Component: JitsiMeetJS.app.entryPoints.APP
})
})
</script>
<script>
// IE11 and earlier can be identified via their user agent and be
// redirected to a page that is known to have no newer js syntax.

View File

@@ -291,9 +291,9 @@
13B07F8E1A680F5B00A75B9A /* Resources */,
0B26BE701EC5BC3C00EEFB41 /* Embed Frameworks */,
B35383AD1DDA0083008F406A /* Adjust embedded framework architectures */,
DE3A859324C701EA009B7D76 /* Copy WebRTC dSYM */,
0BB7DA181EC9E695007AAE98 /* Adjust ATS */,
DEF4813D224925A2002AD03A /* Copy Google Plist file */,
DEC2069321CBBD6900072F03 /* Setup Crashlytics */,
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
DE4F6D6E22005C0400DE699E /* Setup Dropbox */,
0BEA5C491F7B8F73000D0AB4 /* Embed Watch Content */,
@@ -474,24 +474,6 @@
shellPath = /bin/sh;
shellScript = "INFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nGOOGLE_PLIST=\"$PROJECT_DIR/GoogleService-Info.plist\"\n\nif [[ -f $GOOGLE_PLIST ]]; then\n REVERSED_CLIENT_ID=$(/usr/libexec/PlistBuddy -c \"Print :REVERSED_CLIENT_ID:\" $GOOGLE_PLIST)\n /usr/libexec/PlistBuddy -c \"Set :CFBundleURLTypes:1:CFBundleURLSchemes:0 $REVERSED_CLIENT_ID\" $INFO_PLIST\nfi\n";
};
DE3A859324C701EA009B7D76 /* Copy WebRTC dSYM */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Copy WebRTC dSYM";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -x\n\nif [[ \"${CONFIGURATION}\" != \"Debug\" ]]; then\n cp -r ../../node_modules/react-native-webrtc/ios/WebRTC.dSYM ${DWARF_DSYM_FOLDER_PATH}/\nfi\n";
};
DE4F6D6E22005C0400DE699E /* Setup Dropbox */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -510,6 +492,24 @@
shellPath = /bin/sh;
shellScript = "INFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nDROPBOX_KEY_FILE=\"$PROJECT_DIR/dropbox.key\"\n\nif [[ -f $DROPBOX_KEY_FILE ]]; then\n /usr/libexec/PlistBuddy -c \"Delete :LSApplicationQueriesSchemes\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes array\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes:0 string 'dbapi-2'\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes:1 string 'dbapi-8-emm'\" $INFO_PLIST\n\n DROPBOX_KEY=$(head -n 1 $DROPBOX_KEY_FILE)\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLName string dropbox\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLSchemes array\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLSchemes:0 string $DROPBOX_KEY\" $INFO_PLIST\nfi\n";
};
DEC2069321CBBD6900072F03 /* Setup Crashlytics */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Setup Crashlytics";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "GOOGLE_PLIST=\"$PROJECT_DIR/GoogleService-Info.plist\"\n\nif [[ -f $GOOGLE_PLIST ]]; then\n if [ \"${CONFIGURATION}\" != \"Debug\" ]; then\n find \"${DWARF_DSYM_FOLDER_PATH}\" -name \"*.dSYM\" | xargs -I \\{\\} ${PODS_ROOT}/Fabric/upload-symbols -gsp $GOOGLE_PLIST -p ios \\{\\}\n fi\nfi\n";
};
DEF4813D224925A2002AD03A /* Copy Google Plist file */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;

View File

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

View File

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

View File

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

View File

@@ -80,10 +80,6 @@ platform :ios do
uses_non_exempt_encryption: false
)
# Upload dSYMs to Crashlytics
download_dsyms
upload_symbols_to_crashlytics
# Cleanup
clean_build_artifacts
reset_git_repo(skip_clean: true)

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -290,9 +290,9 @@
"inviteLiveStream": "この会議のライブストリームを表示するには、このリンクをクリックしてください:{{url}}",
"invitePhone": "",
"invitePhoneAlternatives": "",
"inviteURLFirstPartGeneral": "あなたはミーティングに招待されました。",
"inviteURLFirstPartPersonal": "{{name}} があなたをミーティングに招待しました。\n",
"inviteURLSecondPart": "\nミーティングにご参加ください:\n{{url}}\n",
"inviteURLFirstPartGeneral": "",
"inviteURLFirstPartPersonal": "",
"inviteURLSecondPart": "",
"liveStreamURL": "ライブストリーム:",
"moreNumbers": "その他の番号",
"noNumbers": "ダイヤルイン番号はありません。",

File diff suppressed because it is too large Load Diff

View File

@@ -639,7 +639,7 @@
"raiseHand": "Podnieś / Opuść rękę",
"raiseYourHand": "Podnieś rękę",
"Settings": "Ustawienia",
"sharedvideo": "Udostępnij wideo z Youtube",
"sharedvideo": "Udostępnij wideo w Youtube",
"shareRoom": "Zaproś kogoś",
"shortcuts": "Wyświetl skróty",
"speakerStats": "Statystyki mówców",

View File

@@ -110,7 +110,6 @@
"localaddress_plural": "Local addresses:",
"localport": "Local port:",
"localport_plural": "Local ports:",
"maxEnabledResolution": "send max",
"more": "Show more",
"packetloss": "Packet loss:",
"quality": {
@@ -203,8 +202,6 @@
"enterDisplayName": "Please enter your name here",
"error": "Error",
"gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
"grantModeratorDialog": "Are you sure you want to make this participant a moderator?",
"grantModeratorTitle": "Grant moderator",
"IamHost": "I am the host",
"incorrectRoomLockPassword": "Incorrect password",
"incorrectPassword": "Incorrect username or password",
@@ -501,7 +498,6 @@
"audioAndVideoError": "Audio and video error:",
"audioOnlyError": "Audio error:",
"audioTrackError": "Could not create audio track.",
"calling": "Calling",
"callMe": "Call me",
"callMeAtNumber": "Call me at this number:",
"configuringDevices": "Configuring devices...",
@@ -525,8 +521,7 @@
"linkCopied": "Link copied to clipboard",
"lookGood": "It sounds like your microphone is working properly",
"or": "or",
"premeeting": "Pre meeting",
"showScreen": "Enable pre meeting screen",
"calling": "Calling",
"startWithPhone": "Start with phone audio",
"screenSharingError": "Screen sharing error:",
"videoOnlyError": "Video error:",
@@ -673,7 +668,6 @@
"e2ee": "End-to-End Encryption",
"feedback": "Leave feedback",
"fullScreen": "Toggle full screen",
"grantModerator": "Grant Moderator",
"hangup": "Leave the call",
"help": "Help",
"invite": "Invite people",
@@ -822,7 +816,6 @@
"domute": "Mute",
"domuteOthers": "Mute everyone else",
"flip": "Flip",
"grantModerator": "Grant Moderator",
"kick": "Kick out",
"moderator": "Moderator",
"mute": "Participant is muted",
@@ -871,7 +864,7 @@
"header": "Help center"
},
"lobby": {
"knockingParticipantList": "Knocking participant list",
"knockingParticipantList" : "Knocking participant list",
"allow": "Allow",
"backToKnockModeButton": "No password, ask to join instead",
"dialogTitle": "Lobby mode",

View File

@@ -12,7 +12,6 @@ import {
JitsiParticipantConnectionStatus
} from '../../../react/features/base/lib-jitsi-meet';
import { VIDEO_TYPE } from '../../../react/features/base/media';
import { CHAT_SIZE } from '../../../react/features/chat';
import {
updateKnownLargeVideoResolution
} from '../../../react/features/large-video';
@@ -324,18 +323,7 @@ export default class LargeVideoManager {
* Update container size.
*/
updateContainerSize() {
let widthToUse = UIUtil.getAvailableVideoWidth();
const { isOpen } = APP.store.getState()['features/chat'];
if (isOpen) {
/**
* If chat state is open, we re-compute the container width
* by subtracting the default width of the chat.
*/
widthToUse -= CHAT_SIZE;
}
this.width = widthToUse;
this.width = UIUtil.getAvailableVideoWidth();
this.height = window.innerHeight;
}

21
package-lock.json generated
View File

@@ -10725,8 +10725,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#2c4e3816e97d174b0a9e82ce1ace5a77eda9a891",
"from": "github:jitsi/lib-jitsi-meet#2c4e3816e97d174b0a9e82ce1ace5a77eda9a891",
"version": "github:jitsi/lib-jitsi-meet#cd008d726f1f57562eb5d8e6a3cd91c7e69826a0",
"from": "github:jitsi/lib-jitsi-meet#cd008d726f1f57562eb5d8e6a3cd91c7e69826a0",
"requires": {
"@jitsi/js-utils": "1.0.0",
"@jitsi/sdp-interop": "1.0.3",
@@ -10793,9 +10793,9 @@
}
},
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.13.tgz",
"integrity": "sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA=="
},
"lodash.clonedeep": {
"version": "4.5.0",
@@ -14215,8 +14215,8 @@
"integrity": "sha512-cuXIIv+dcG8a8qkTD8pMzeqOEZCO+UGKglZWIe1osve+yJslmCowYQff+bI9xa7NOt2w+Vtd4L3d9JonlSqODg=="
},
"react-native-calendar-events": {
"version": "github:jitsi/react-native-calendar-events#928a80e2ffef0d7e84936d7e7e0acc4f53ee8470",
"from": "github:jitsi/react-native-calendar-events#928a80e2ffef0d7e84936d7e7e0acc4f53ee8470"
"version": "github:jitsi/react-native-calendar-events#df48ecdc4e1e90c5352f803ddbab1fa7269b74a7",
"from": "github:jitsi/react-native-calendar-events#df48ecdc4e1e90c5352f803ddbab1fa7269b74a7"
},
"react-native-callstats": {
"version": "3.61.0",
@@ -15003,13 +15003,6 @@
"sdp": "^2.6.0"
}
},
"rtcstats": {
"version": "github:jitsi/rtcstats#02a1a089d9a97d1414d216ff7d9c432253e50190",
"from": "github:jitsi/rtcstats#v6.1.3",
"requires": {
"@jitsi/js-utils": "1.0.0"
}
},
"run-async": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",

View File

@@ -56,9 +56,9 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#2c4e3816e97d174b0a9e82ce1ace5a77eda9a891",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#cd008d726f1f57562eb5d8e6a3cd91c7e69826a0",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.19",
"lodash": "4.17.13",
"moment": "2.19.4",
"moment-duration-format": "2.2.2",
"pixelmatch": "5.1.0",
@@ -69,7 +69,7 @@
"react-linkify": "1.0.0-alpha",
"react-native": "github:jitsi/react-native#efd2aff5661d75a230e36406b698cfe0ee545be2",
"react-native-background-timer": "2.1.1",
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#928a80e2ffef0d7e84936d7e7e0acc4f53ee8470",
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#df48ecdc4e1e90c5352f803ddbab1fa7269b74a7",
"react-native-callstats": "3.61.0",
"react-native-collapsible": "1.5.1",
"react-native-default-preference": "1.4.2",
@@ -90,7 +90,6 @@
"redux": "4.0.4",
"redux-thunk": "2.2.0",
"rnnoise-wasm": "github:jitsi/rnnoise-wasm.git#566a16885897704d6e6d67a1d5ac5d39781db2af",
"rtcstats": "github:jitsi/rtcstats#v6.1.3",
"styled-components": "3.4.9",
"util": "0.12.1",
"uuid": "3.1.0",

View File

@@ -538,26 +538,6 @@ export function createRemoteVideoMenuButtonEvent(buttonName, attributes) {
};
}
/**
* The rtcstats websocket onclose event. We send this to amplitude in order
* to detect trace ws prematurely closing.
*
* @param {Object} closeEvent - The event with which the websocket closed.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createRTCStatsTraceCloseEvent(closeEvent) {
const event = {
action: 'trace.onclose',
source: 'rtcstats'
};
event.code = closeEvent.code;
event.reason = closeEvent.reason;
return event;
}
/**
* Creates an event indicating that an action related to video blur
* occurred (e.g. It was started or stopped).

View File

@@ -1,6 +1,6 @@
// @flow
import { API_ID } from '../../../modules/API/constants';
import { API_ID } from '../../../modules/API';
import {
checkChromeExtensionsInstalled,
isMobileBrowser
@@ -30,16 +30,6 @@ export function sendAnalytics(event: Object) {
}
}
/**
* Return saved amplitude identity info such as session id, device id and user id. We assume these do not change for
* the duration of the conference.
*
* @returns {Object}
*/
export function getAmplitudeIdentity() {
return analytics.amplitudeIdentityProps;
}
/**
* Resets the analytics adapter to its initial state - removes handlers, cache,
* disabled state, etc.
@@ -102,8 +92,6 @@ export function createHandlers({ getState }: { getState: Function }) {
try {
const amplitude = new AmplitudeHandler(handlerConstructorOptions);
analytics.amplitudeIdentityProps = amplitude.getIdentityProps();
handlers.push(amplitude);
// eslint-disable-next-line no-empty
} catch (e) {}
@@ -129,9 +117,7 @@ export function createHandlers({ getState }: { getState: Function }) {
})
.catch(e => {
analytics.dispose();
if (handlers.length !== 0) {
logger.error(e);
}
logger.error(e);
return [];
}));

View File

@@ -65,17 +65,4 @@ export default class AmplitudeHandler extends AbstractHandler {
this._extractName(event),
event);
}
/**
* Return amplitude identity information.
*
* @returns {Object}
*/
getIdentityProps() {
return {
sessionId: amplitude.getInstance(this._amplitudeOptions).getSessionId(),
deviceId: amplitude.getInstance(this._amplitudeOptions).options.deviceId,
userId: amplitude.getInstance(this._amplitudeOptions).options.userId
};
}
}

View File

@@ -4,9 +4,7 @@ import React from 'react';
import { setColorScheme } from '../../base/color-scheme';
import { DialogContainer } from '../../base/dialog';
import { updateFlags } from '../../base/flags/actions';
import { CALL_INTEGRATION_ENABLED, SERVER_URL_CHANGE_ENABLED } from '../../base/flags/constants';
import { getFeatureFlag } from '../../base/flags/functions';
import { CALL_INTEGRATION_ENABLED, SERVER_URL_CHANGE_ENABLED, updateFlags } from '../../base/flags';
import { Platform } from '../../base/react';
import { DimensionsDetector, clientResized } from '../../base/responsive-ui';
import { updateSettings } from '../../base/settings';
@@ -85,14 +83,11 @@ export class App extends AbstractApp {
super.componentDidMount();
this._init.then(() => {
const { dispatch, getState } = this.state.store;
// We set these early enough so then we avoid any unnecessary re-renders.
dispatch(setColorScheme(this.props.colorScheme));
dispatch(updateFlags(this.props.flags));
const { dispatch } = this.state.store;
// Check if serverURL is configured externally and not allowed to change.
const serverURLChangeEnabled = getFeatureFlag(getState(), SERVER_URL_CHANGE_ENABLED, true);
const serverURLChangeEnabled = this.props.flags[SERVER_URL_CHANGE_ENABLED];
if (!serverURLChangeEnabled) {
// As serverURL is provided externally, so we push it to settings.
@@ -105,6 +100,8 @@ export class App extends AbstractApp {
}
}
dispatch(setColorScheme(this.props.colorScheme));
dispatch(updateFlags(this.props.flags));
dispatch(updateSettings(this.props.userInfo || {}));
// Update settings with feature-flag.

View File

@@ -37,7 +37,6 @@ import '../recent-list/middleware';
import '../recording/middleware';
import '../rejoin/middleware';
import '../room-lock/middleware';
import '../rtcstats/middleware';
import '../subtitles/middleware';
import '../toolbox/middleware';
import '../transcribing/middleware';

View File

@@ -122,14 +122,14 @@ export default class BaseApp extends Component<*, State> {
* @returns {ReactElement}
*/
render() {
const { route: { component, props }, store } = this.state;
const { route: { component }, store } = this.state;
if (store) {
return (
<I18nextProvider i18n = { i18next }>
<Provider store = { store }>
<Fragment>
{ this._createMainElement(component, props) }
{ this._createMainElement(component) }
<SoundCollection />
{ this._createExtraElement() }
{ this._renderDialogContainer() }

View File

@@ -125,14 +125,12 @@ export default [
'minParticipants',
'nick',
'openBridgeChannel',
'opusMaxAvgBitrate',
'p2p',
'pcStatsInterval',
'preferH264',
'prejoinPageEnabled',
'requireDisplayName',
'remoteVideoMenu',
'roomPasswordNumberOfDigits',
'resolution',
'startAudioMuted',
'startAudioOnly',

View File

@@ -25,12 +25,6 @@ export const CALL_INTEGRATION_ENABLED = 'call-integration.enabled';
*/
export const CLOSE_CAPTIONS_ENABLED = 'close-captions.enabled';
/**
* Flag indicating if conference timer should be enabled.
* Default: enabled (true).
*/
export const CONFERENCE_TIMER_ENABLED = 'conference-timer.enabled';
/**
* Flag indicating if chat should be enabled.
* Default: enabled (true).
@@ -112,12 +106,6 @@ export const TILE_VIEW_ENABLED = 'tile-view.enabled';
*/
export const TOOLBOX_ALWAYS_VISIBLE = 'toolbox.alwaysVisible';
/**
* Flag indicating if the video share button should be enabled
* Default: enabled (true).
*/
export const VIDEO_SHARE_BUTTON_ENABLED = 'video-share.enabled';
/**
* Flag indicating if the welcome page should be enabled.
* Default: disabled (false).

View File

@@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 4C14 4.85739 13.4605 5.58876 12.7024 5.87317L14.2286 9.94296L14.9455 11.8546C15.0074 11.9292 15.0708 11.9292 15.1098 11.8902L16.5535 10.4465L18.5858 8.41421C18.2239 8.05228 18 7.55228 18 7C18 5.89543 18.8954 5 20 5C21.1046 5 22 5.89543 22 7C22 8.10457 21.1046 9 20 9C19.9441 9 19.8887 8.9977 19.8339 8.9932L19 19C19 20.1046 18.1046 21 17 21H7C5.89543 21 5 20.1046 5 19L4.1661 8.9932C4.11133 8.9977 4.05593 9 4 9C2.89543 9 2 8.10457 2 7C2 5.89543 2.89543 5 4 5C5.10457 5 6 5.89543 6 7C6 7.55228 5.77614 8.05228 5.41421 8.41421L7.44654 10.4465L8.89019 11.8902C8.9775 11.9325 9.03514 11.9063 9.05453 11.8546L9.77139 9.94296L11.2976 5.87317C10.5395 5.58876 10 4.85739 10 4C10 2.89543 10.8954 2 12 2C13.1046 2 14 2.89543 14 4ZM6.84027 17L6.44651 12.2749L7.47597 13.3044C7.68795 13.5164 7.94285 13.6805 8.22354 13.7858C9.30949 14.193 10.52 13.6428 10.9272 12.5568L12 9.696L13.0728 12.5568C13.1781 12.8375 13.3422 13.0924 13.5542 13.3044C14.3743 14.1245 15.7039 14.1245 16.524 13.3044L17.5535 12.2749L17.1597 17H6.84027Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -23,7 +23,6 @@ export { default as IconClosedCaption } from './closed_caption.svg';
export { default as IconConnectionActive } from './gsm-bars.svg';
export { default as IconConnectionInactive } from './ninja.svg';
export { default as IconCopy } from './copy.svg';
export { default as IconCrown } from './crown.svg';
export { default as IconDeviceBluetooth } from './bluetooth.svg';
export { default as IconDeviceEarpiece } from './phone-talk.svg';
export { default as IconDeviceHeadphone } from './headset.svg';

View File

@@ -12,16 +12,6 @@
*/
export const DOMINANT_SPEAKER_CHANGED = 'DOMINANT_SPEAKER_CHANGED';
/**
* Create an action for granting moderator to a participant.
*
* {
* type: GRANT_MODERATOR,
* id: string
* }
*/
export const GRANT_MODERATOR = 'GRANT_MODERATOR';
/**
* Create an action for removing a participant from the conference.
*

View File

@@ -5,7 +5,6 @@ import {
DOMINANT_SPEAKER_CHANGED,
HIDDEN_PARTICIPANT_JOINED,
HIDDEN_PARTICIPANT_LEFT,
GRANT_MODERATOR,
KICK_PARTICIPANT,
MUTE_REMOTE_PARTICIPANT,
PARTICIPANT_ID_CHANGED,
@@ -48,22 +47,6 @@ export function dominantSpeakerChanged(id, conference) {
};
}
/**
* Create an action for granting moderator to a participant.
*
* @param {string} id - Participant's ID.
* @returns {{
* type: GRANT_MODERATOR,
* id: string
* }}
*/
export function grantModerator(id) {
return {
type: GRANT_MODERATOR,
id
};
}
/**
* Create an action for removing a participant from the conference.
*
@@ -433,6 +416,10 @@ export function participantMutedUs(participant) {
*/
export function participantKicked(kicker, kicked) {
return (dispatch, getState) => {
// HOTFIX, DO NOT LAND ON MASTER. This has been properly fixed in LJM.
if (typeof kicker === 'undefined') {
return;
}
dispatch({
type: PARTICIPANT_KICKED,

View File

@@ -259,16 +259,6 @@ export function getYoutubeParticipant(stateful: Object | Function) {
return participants.filter(p => p.isFakeParticipant)[0];
}
/**
* Returns true if the participant is a moderator.
*
* @param {string} participant - Participant object.
* @returns {boolean}
*/
export function isParticipantModerator(participant: Object) {
return participant?.role === PARTICIPANT_ROLE.MODERATOR;
}
/**
* Returns true if all of the meeting participants are moderators.
*
@@ -279,7 +269,13 @@ export function isParticipantModerator(participant: Object) {
export function isEveryoneModerator(stateful: Object | Function) {
const participants = _getAllParticipants(stateful);
return participants.every(isParticipantModerator);
for (const participant of participants) {
if (participant.role !== PARTICIPANT_ROLE.MODERATOR) {
return false;
}
}
return true;
}
/**

View File

@@ -15,7 +15,6 @@ import { playSound, registerSound, unregisterSound } from '../sounds';
import {
DOMINANT_SPEAKER_CHANGED,
GRANT_MODERATOR,
KICK_PARTICIPANT,
MUTE_REMOTE_PARTICIPANT,
PARTICIPANT_DISPLAY_NAME_CHANGED,
@@ -87,13 +86,6 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case GRANT_MODERATOR: {
const { conference } = store.getState()['features/base/conference'];
conference.grantOwner(action.id);
break;
}
case KICK_PARTICIPANT: {
const { conference } = store.getState()['features/base/conference'];

View File

@@ -6,7 +6,7 @@ import { getCurrentConferenceUrl } from '../../../connection';
import { translate } from '../../../i18n';
import { Icon, IconCopy, IconCheck } from '../../../icons';
import { connect } from '../../../redux';
import { copyText, getDecodedURI } from '../../../util';
import { copyText } from '../../../util';
type Props = {
@@ -156,7 +156,7 @@ class CopyMeetingUrl extends Component<Props, State> {
className = { `url ${showLinkCopied ? 'done' : ''}` }
onClick = { _copyUrl } >
<div className = 'copy-meeting-text'>
{ !showCopyLink && !showLinkCopied && getDecodedURI(url) }
{ !showCopyLink && !showLinkCopied && url }
{ showCopyLink && t('prejoin.copyAndShare') }
{ showLinkCopied && t('prejoin.linkCopied') }
</div>

View File

@@ -24,26 +24,11 @@ type Props = {
*/
name?: string,
/**
* Indicates whether the avatar should be shown when video is off
*/
showAvatar: boolean,
/**
* Indicates whether the label and copy url action should be shown
*/
showConferenceInfo: boolean,
/**
* Title of the screen.
*/
title: string,
/**
* The 'Skip prejoin' button to be rendered (if any).
*/
skipPrejoinButton?: React$Node,
/**
* True if the preview overlay should be muted, false otherwise.
*/
@@ -60,23 +45,13 @@ type Props = {
* on the prejoin screen (pre-connection) or lobby (post-connection).
*/
export default class PreMeetingScreen extends PureComponent<Props> {
/**
* Default values for {@code Prejoin} component's properties.
*
* @static
*/
static defaultProps = {
showAvatar: true,
showConferenceInfo: true
};
/**
* Implements {@code PureComponent#render}.
*
* @inheritdoc
*/
render() {
const { name, showAvatar, showConferenceInfo, title, videoMuted, videoTrack } = this.props;
const { name, title, videoMuted, videoTrack } = this.props;
return (
<div
@@ -84,25 +59,18 @@ export default class PreMeetingScreen extends PureComponent<Props> {
id = 'lobby-screen'>
<Preview
name = { name }
showAvatar = { showAvatar }
videoMuted = { videoMuted }
videoTrack = { videoTrack } />
{!videoMuted && <div className = 'preview-overlay' />}
<div className = 'content'>
{showConferenceInfo && (
<>
<div className = 'title'>
{ title }
</div>
<CopyMeetingUrl />
</>
)}
<div className = 'title'>
{ title }
</div>
<CopyMeetingUrl />
{ this.props.children }
<div className = 'media-btn-container'>
<AudioSettingsButton visible = { true } />
<VideoSettingsButton visible = { true } />
</div>
{ this.props.skipPrejoinButton }
{ this.props.footer }
</div>
</div>

View File

@@ -14,11 +14,6 @@ export type Props = {
*/
name: string,
/**
* Indicates whether the avatar should be shown when video is off
*/
showAvatar: boolean,
/**
* Flag signaling the visibility of camera preview.
*/
@@ -37,7 +32,7 @@ export type Props = {
* @returns {ReactElement}
*/
function Preview(props: Props) {
const { name, showAvatar, videoMuted, videoTrack } = props;
const { name, videoMuted, videoTrack } = props;
if (!videoMuted && videoTrack) {
return (
@@ -49,29 +44,19 @@ function Preview(props: Props) {
);
}
if (showAvatar) {
return (
<div
className = 'no-video'
id = 'preview'>
<div className = 'preview-avatar-container'>
<Avatar
className = 'preview-avatar'
displayName = { name }
participantId = 'local'
size = { 200 } />
</div>
</div>
);
}
return null;
return (
<div
className = 'no-video'
id = 'preview'>
<Avatar
className = 'preview-avatar'
displayName = { name }
participantId = 'local'
size = { 200 } />
</div>
);
}
Preview.defaultProps = {
showAvatar: true
};
/**
* Maps part of the Redux state to the props of this component.
*

View File

@@ -1,52 +0,0 @@
// @flow
import React from 'react';
import { Icon, IconCheck } from '../../../icons';
const mainClass = 'toggle-button';
type Props = {
/**
* Text of the button.
*/
children: React$Node,
/**
* If the button is toggled or not.
*/
isToggled?: boolean,
/**
* OnClick button handler.
*/
onClick: Function
}
/**
* Button used as a toggle.
*
* @returns {ReactElement}
*/
function ToggleButton({ children, isToggled, onClick }: Props) {
const className = isToggled ? `${mainClass} ${mainClass}--toggled` : mainClass;
return (
<div
className = { className }
onClick = { onClick }>
<div className = 'toggle-button-container'>
<div className = 'toggle-button-icon-container'>
<Icon
className = 'toggle-button-icon'
size = { 10 }
src = { IconCheck } />
</div>
<span>{children}</span>
</div>
</div>
);
}
export default ToggleButton;

View File

@@ -3,4 +3,3 @@
export { default as ActionButton } from './ActionButton';
export { default as InputField } from './InputField';
export { default as PreMeetingScreen } from './PreMeetingScreen';
export { default as ToggleButton } from './ToggleButton';

View File

@@ -2,7 +2,6 @@
import type { Store } from 'redux';
import { equals } from './functions';
import logger from './logger';
/**
@@ -38,18 +37,6 @@ type Listener
*/
type Selector = (state: Object, prevSelection: any) => any;
/**
* Options that can be passed to the register method.
*/
type RegistrationOptions = {
/**
* @property {boolean} [deepEquals=false] - whether or not a deep equals check should be performed on the selection
* returned by {@link Selector}.
*/
deepEquals: ?boolean
}
/**
* A type of a {@link Selector}-{@link Listener} association in which the
* {@code Listener} listens to changes in the values derived from a redux
@@ -63,11 +50,6 @@ type SelectorListener = {
*/
listener: Listener,
/**
* The {@link RegistrationOptions} passed during the registration to be applied on the listener.
*/
options: ?RegistrationOptions,
/**
* The {@code Selector} which selects values whose changes are listened to
* by {@link listener}.
@@ -112,10 +94,8 @@ class StateListenerRegistry {
= selectorListener.selector(
store.getState(),
prevSelection);
const useDeepEquals = selectorListener?.options?.deepEquals;
if ((useDeepEquals && !equals(prevSelection, selection))
|| (!useDeepEquals && prevSelection !== selection)) {
if (prevSelection !== selection) {
prevSelections.set(selectorListener, selection);
selectorListener.listener(selection, store, prevSelection);
}
@@ -137,14 +117,12 @@ class StateListenerRegistry {
* @param {Function} listener - The listener to register with this
* {@code StateListenerRegistry} so that it gets invoked when the value
* returned by the specified {@code selector} changes.
* @param {RegistrationOptions} [options] - Any options to be applied to the registration.
* @returns {void}
*/
register(selector: Selector, listener: Listener, options: ?RegistrationOptions) {
register(selector: Selector, listener: Listener) {
this._selectorListeners.add({
listener,
selector,
options
selector
});
}

View File

@@ -36,11 +36,6 @@ type Props = {
*/
_localUserId: string,
/**
* The local participant's role.
*/
_localUserRole: string,
/**
* Indicates whether or not the test mode is currently on. Otherwise the
* TestConnectionInfo component will not render.
@@ -184,12 +179,6 @@ class TestConnectionInfo extends Component<Props, State> {
<TestHint
id = 'org.jitsi.meet.conference.joinedState'
value = { this.props._conferenceJoinedState } />
<TestHint
id = 'org.jitsi.meet.conference.grantModeratorAvailable'
value = { true } />
<TestHint
id = 'org.jitsi.meet.conference.localParticipantRole'
value = { this.props._localUserRole } />
<TestHint
id = 'org.jitsi.meet.stats.rtp'
value = { JSON.stringify(this.state.stats) } />
@@ -204,7 +193,12 @@ class TestConnectionInfo extends Component<Props, State> {
*
* @param {Object} state - The Redux state.
* @private
* @returns {Props}
* @returns {{
* _conferenceConnectionState: string,
* _conferenceJoinedState: string,
* _localUserId: string,
* _testMode: boolean
* }}
*/
function _mapStateToProps(state) {
const conferenceJoined
@@ -214,8 +208,7 @@ function _mapStateToProps(state) {
return {
_conferenceConnectionState: state['features/testing'].connectionState,
_conferenceJoinedState: conferenceJoined.toString(),
_localUserId: localParticipant?.id,
_localUserRole: localParticipant?.role,
_localUserId: localParticipant && localParticipant.id,
_testMode: isTestModeEnabled(state)
};
}

View File

@@ -230,14 +230,13 @@ export default class AbstractButton<P: Props, S: *> extends Component<P, S> {
/**
* Helper function to be implemented by subclasses, which must return a
* {@code boolean} value indicating if this button is toggled or not or
* undefined if the button is not toggleable.
* {@code boolean} value indicating if this button is toggled or not.
*
* @protected
* @returns {?boolean}
* @returns {boolean}
*/
_isToggled() {
return undefined;
return false;
}
_onClick: (*) => void;

View File

@@ -12,41 +12,6 @@ import type { Props } from './AbstractToolboxItem';
* Web implementation of {@code AbstractToolboxItem}.
*/
export default class ToolboxItem extends AbstractToolboxItem<Props> {
/**
* Initializes a new {@code ToolboxItem} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onKeyDown = this._onKeyDown.bind(this);
}
_onKeyDown: (Object) => void;
/**
* Handles 'Enter' key on the button to trigger onClick for accessibility.
* We should be handling Space onKeyUp but it conflicts with PTT.
*
* @param {Object} event - The key event.
* @private
* @returns {void}
*/
_onKeyDown(event) {
// If the event coming to the dialog has been subject to preventDefault
// we don't handle it here.
if (event.defaultPrevented) {
return;
}
if (event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
this.props.onClick();
}
}
/**
* Handles rendering of the actual item. If the label is being shown, which
* is controlled with the `showLabel` prop, the item is rendered for its
@@ -62,21 +27,14 @@ export default class ToolboxItem extends AbstractToolboxItem<Props> {
elementAfter,
onClick,
showLabel,
tooltipPosition,
toggled
tooltipPosition
} = this.props;
const className = showLabel ? 'overflow-menu-item' : 'toolbox-button';
const props = {
'aria-pressed': toggled,
'aria-disabled': disabled,
'aria-label': this.accessibilityLabel,
className: className + (disabled ? ' disabled' : ''),
onClick: disabled ? undefined : onClick,
onKeyDown: this._onKeyDown,
tabIndex: 0,
role: 'button'
onClick: disabled ? undefined : onClick
};
const elementType = showLabel ? 'li' : 'div';
const useTooltip = this.tooltip && this.tooltip.length > 0;
let children = (

View File

@@ -1,7 +1,7 @@
/* global APP */
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
import { MEDIA_TYPE, setAudioMuted } from '../media';
import { MEDIA_TYPE } from '../media';
import {
getUserSelectedCameraDeviceId,
getUserSelectedMicDeviceId
@@ -125,89 +125,6 @@ export function createLocalTracksF(options = {}, firePermissionPromptIsShownEven
}));
}
/**
* Returns an object containing a promise which resolves with the created tracks &
* the errors resulting from that process.
*
* @returns {Promise<JitsiLocalTrack>}
*
* @todo Refactor to not use APP
*/
export function createPrejoinTracks() {
const errors = {};
const initialDevices = [ 'audio' ];
const requestedAudio = true;
let requestedVideo = false;
const { startAudioOnly, startWithAudioMuted, startWithVideoMuted } = APP.store.getState()['features/base/settings'];
// Always get a handle on the audio input device so that we have statistics even if the user joins the
// conference muted. Previous implementation would only acquire the handle when the user first unmuted,
// which would results in statistics ( such as "No audio input" or "Are you trying to speak?") being available
// only after that point.
if (startWithAudioMuted) {
APP.store.dispatch(setAudioMuted(true));
}
if (!startWithVideoMuted && !startAudioOnly) {
initialDevices.push('video');
requestedVideo = true;
}
let tryCreateLocalTracks;
if (!requestedAudio && !requestedVideo) {
// Resolve with no tracks
tryCreateLocalTracks = Promise.resolve([]);
} else {
tryCreateLocalTracks = createLocalTracksF({ devices: initialDevices }, true)
.catch(err => {
if (requestedAudio && requestedVideo) {
// Try audio only...
errors.audioAndVideoError = err;
return (
createLocalTracksF({ devices: [ 'audio' ] }, true));
} else if (requestedAudio && !requestedVideo) {
errors.audioOnlyError = err;
return [];
} else if (requestedVideo && !requestedAudio) {
errors.videoOnlyError = err;
return [];
}
logger.error('Should never happen');
})
.catch(err => {
// Log this just in case...
if (!requestedAudio) {
logger.error('The impossible just happened', err);
}
errors.audioOnlyError = err;
// Try video only...
return requestedVideo
? createLocalTracksF({ devices: [ 'video' ] }, true)
: [];
})
.catch(err => {
// Log this just in case...
if (!requestedVideo) {
logger.error('The impossible just happened', err);
}
errors.videoOnlyError = err;
return [];
});
}
return {
tryCreateLocalTracks,
errors
};
}
/**
* Returns local audio track.
*

View File

@@ -143,10 +143,6 @@ MiddlewareRegistry.register(store => next => action => {
if (typeof APP !== 'undefined') {
const result = next(action);
if (isPrejoinPageVisible(store.getState())) {
return result;
}
const { jitsiTrack } = action.track;
const muted = jitsiTrack.isMuted();
const participantID = jitsiTrack.getParticipantId();

View File

@@ -591,13 +591,3 @@ export function addHashParamsToURL(url: URL, hashParamsToAdd: Object = {}) {
return url;
}
/**
* Returns the decoded URI.
*
* @param {string} uri - The URI to decode.
* @returns {string}
*/
export function getDecodedURI(uri: string) {
return decodeURI(uri.replace(/^https?:\/\//i, ''));
}

View File

@@ -4,7 +4,8 @@ import {
ADD_MESSAGE,
CLEAR_MESSAGES,
SEND_MESSAGE,
SET_PRIVATE_MESSAGE_RECIPIENT
SET_PRIVATE_MESSAGE_RECIPIENT,
TOGGLE_CHAT
} from './actionTypes';
/**
@@ -83,3 +84,16 @@ export function setPrivateMessageRecipient(participant: Object) {
type: SET_PRIVATE_MESSAGE_RECIPIENT
};
}
/**
* Toggles display of the chat side panel.
*
* @returns {{
* type: TOGGLE_CHAT
* }}
*/
export function toggleChat() {
return {
type: TOGGLE_CHAT
};
}

View File

@@ -1,16 +0,0 @@
// @flow
import { TOGGLE_CHAT } from './actionTypes';
export * from './actions.any';
/**
* Toggles display of the chat panel.
*
* @returns {Function}
*/
export function toggleChat() {
return function(dispatch: (Object) => Object) {
dispatch({ type: TOGGLE_CHAT });
};
}

View File

@@ -1,20 +0,0 @@
// @flow
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
import { TOGGLE_CHAT } from './actionTypes';
export * from './actions.any';
/**
* Toggles display of the chat side panel while also taking window
* resize into account.
*
* @returns {Function}
*/
export function toggleChat() {
return function(dispatch: (Object) => Object) {
dispatch({ type: TOGGLE_CHAT });
VideoLayout.onResize();
};
}

View File

@@ -1,6 +1,7 @@
// @flow
import React from 'react';
import Transition from 'react-transition-group/Transition';
import { translate } from '../../../base/i18n';
import { Icon, IconClose } from '../../../base/icons';
@@ -83,9 +84,11 @@ class Chat extends AbstractChat<Props> {
*/
render() {
return (
<>
{ this._renderPanelContent() }
</>
<Transition
in = { this.props._isOpen }
timeout = { 500 }>
{ this._renderPanelContent }
</Transition>
);
}
@@ -142,25 +145,30 @@ class Chat extends AbstractChat<Props> {
);
}
_renderPanelContent: () => React$Node | null;
_renderPanelContent: (string) => React$Node | null;
/**
* Renders the contents of the chat panel.
* Renders the contents of the chat panel, depending on the current
* animation state provided by {@code Transition}.
*
* @param {string} state - The current display transition state of the
* {@code Chat} component, as provided by {@code Transition}.
* @private
* @returns {ReactElement | null}
*/
_renderPanelContent() {
_renderPanelContent(state) {
this._isExited = state === 'exited';
const { _isOpen, _showNamePrompt } = this.props;
const ComponentToRender = _isOpen
? (
const ComponentToRender = !_isOpen && state === 'exited'
? null
: (
<>
{ this._renderChatHeader() }
{ _showNamePrompt
? <DisplayNameForm /> : this._renderChat() }
</>
)
: null;
);
let className = '';
if (_isOpen) {

View File

@@ -2,11 +2,6 @@
export const CHAT_VIEW_MODAL_ID = 'chatView';
/**
* The size of the chat.
*/
export const CHAT_SIZE = 375;
/**
* The audio ID of the audio element for which the {@link playAudio} action is
* triggered when new chat message is received.

View File

@@ -295,9 +295,7 @@ class Conference extends AbstractConference<Props, *> {
<Captions onPress = { this._onClick } />
{ _shouldDisplayTileView || <Container style = { styles.displayNameContainer }>
<DisplayNameLabel participantId = { _largeVideoParticipantId } />
</Container> }
{ _shouldDisplayTileView || <DisplayNameLabel participantId = { _largeVideoParticipantId } /> }
<LonelyMeetingExperience />

View File

@@ -126,12 +126,11 @@ class LonelyMeetingExperience extends PureComponent<Props> {
*/
function _mapStateToProps(state): $Shape<Props> {
const { disableInviteFunctions } = state['features/base/config'];
const { conference } = state['features/base/conference'];
const flag = getFeatureFlag(state, INVITE_ENABLED, true);
return {
_isInviteFunctionsDiabled: !flag || disableInviteFunctions,
_isLonelyMeeting: conference && getParticipantCount(state) === 1,
_isLonelyMeeting: getParticipantCount(state) === 1,
_styles: ColorSchemeRegistry.get(state, 'Conference')
};
}

View File

@@ -5,7 +5,7 @@ import { SafeAreaView, Text, View } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { getConferenceName } from '../../../base/conference';
import { getFeatureFlag, CONFERENCE_TIMER_ENABLED, MEETING_NAME_ENABLED } from '../../../base/flags';
import { getFeatureFlag, MEETING_NAME_ENABLED } from '../../../base/flags';
import { connect } from '../../../base/redux';
import { PictureInPictureButton } from '../../../mobile/picture-in-picture';
import { isToolboxVisible } from '../../../toolbox';
@@ -15,11 +15,6 @@ import styles, { NAVBAR_GRADIENT_COLORS } from './styles';
type Props = {
/**
* Whether displaying the current conference timer is enabled or not.
*/
_conferenceTimerEnabled: boolean,
/**
* Name of the meeting we're currently in.
*/
@@ -78,9 +73,7 @@ class NavigationBar extends Component<Props> {
{ this.props._meetingName }
</Text>
}
{
this.props._conferenceTimerEnabled && <ConferenceTimer />
}
<ConferenceTimer />
</View>
</View>
];
@@ -96,7 +89,6 @@ class NavigationBar extends Component<Props> {
*/
function _mapStateToProps(state) {
return {
_conferenceTimerEnabled: getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true),
_meetingName: getConferenceName(state),
_meetingNameEnabled: getFeatureFlag(state, MEETING_NAME_ENABLED, true),
_visible: isToolboxVisible(state)

View File

@@ -33,10 +33,6 @@ export default {
flex: 1
}),
displayNameContainer: {
margin: 10
},
gradient: {
position: 'absolute',
top: 0,

View File

@@ -28,8 +28,10 @@ import {
} from '../AbstractConference';
import type { AbstractProps } from '../AbstractConference';
import InviteMore from './InviteMore';
import Labels from './Labels';
import { default as Notice } from './Notice';
import { default as Subject } from './Subject';
declare var APP: Object;
declare var config: Object;
@@ -199,6 +201,8 @@ class Conference extends AbstractConference<Props, *> {
onMouseMove = { this._onShowToolbar }>
<Notice />
<Subject />
<InviteMore />
<div id = 'videospace'>
<LargeVideo />
<KnockingParticipantList />

View File

@@ -3,5 +3,3 @@
export { default as Conference } from './Conference';
export { default as renderConferenceTimer } from './ConferenceTimerDisplay';
export { default as InsecureRoomNameLabel } from './InsecureRoomNameLabel';
export { default as InviteMore } from './InviteMore';
export { default as Subject } from './Subject';

View File

@@ -3,8 +3,10 @@ import { appNavigate } from '../app/actions';
import {
CONFERENCE_JOINED,
KICKED_OUT,
VIDEO_QUALITY_LEVELS,
conferenceLeft,
getCurrentConference
getCurrentConference,
setMaxReceiverVideoQuality
} from '../base/conference';
import { hideDialog, isDialogOpen } from '../base/dialog';
import { setActiveModalId } from '../base/modal';
@@ -30,6 +32,12 @@ MiddlewareRegistry.register(store => next => action => {
dispatch(setToolboxEnabled(!reducedUI));
dispatch(setFilmstripEnabled(!reducedUI));
dispatch(
setMaxReceiverVideoQuality(
reducedUI
? VIDEO_QUALITY_LEVELS.LOW
: VIDEO_QUALITY_LEVELS.HIGH));
break;
}

View File

@@ -342,7 +342,6 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
bridgeCount,
e2eRtt,
framerate,
maxEnabledResolution,
packetLoss,
region,
resolution,
@@ -359,7 +358,6 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
e2eRtt = { e2eRtt }
framerate = { framerate }
isLocalVideo = { this.props.isLocalVideo }
maxEnabledResolution = { maxEnabledResolution }
onShowMore = { this._onToggleShowMore }
packetLoss = { packetLoss }
region = { region }

View File

@@ -57,12 +57,6 @@ type Props = {
*/
isLocalVideo: boolean,
/**
* The send-side max enabled resolution (aka the highest layer that is not
* suspended on the send-side).
*/
maxEnabledResolution: number,
/**
* Callback to invoke when the show additional stats link is clicked.
*/
@@ -386,8 +380,8 @@ class ConnectionStatsTable extends Component<Props> {
* @returns {ReactElement}
*/
_renderResolution() {
const { resolution, maxEnabledResolution, t } = this.props;
let resolutionString = Object.keys(resolution || {})
const { resolution, t } = this.props;
const resolutionString = Object.keys(resolution || {})
.map(ssrc => {
const { width, height } = resolution[ssrc];
@@ -395,12 +389,6 @@ class ConnectionStatsTable extends Component<Props> {
})
.join(', ') || 'N/A';
if (maxEnabledResolution && maxEnabledResolution < 720) {
const maxEnabledResolutionTitle = t('connectionindicator.maxEnabledResolution');
resolutionString += ` (${maxEnabledResolutionTitle} ${maxEnabledResolution}p)`;
}
return (
<tr>
<td>

View File

@@ -73,7 +73,7 @@ function _mapStateToProps(state: Object, ownProps: Props) {
// participant and there is no video rendered for
// them.
const _render = Boolean(participantId)
&& localParticipant?.id !== participantId
&& localParticipant.id !== participantId
&& !shouldRenderParticipantVideo(state, participantId)
&& !isFakeParticipant;

View File

@@ -7,6 +7,7 @@ export default {
alignSelf: 'center',
backgroundColor: 'rgba(28, 32, 37, 0.6)',
borderRadius: 4,
margin: 16,
paddingHorizontal: 16,
paddingVertical: 4
},

View File

@@ -34,19 +34,7 @@ export const SET_FILMSTRIP_VISIBLE = 'SET_FILMSTRIP_VISIBLE';
*
* {
* type: SET_TILE_VIEW_DIMENSIONS,
* dimensions: {
* gridDimensions: {
* columns: number,
* height: number,
* visibleRows: number,
* width: number
* },
* thumbnailSize: {
* height: number,
* width: number
* },
* filmstripWidth: number
* }
* dimensions: Object
* }
*/
export const SET_TILE_VIEW_DIMENSIONS = 'SET_TILE_VIEW_DIMENSIONS';

View File

@@ -3,8 +3,7 @@
import {
SET_FILMSTRIP_ENABLED,
SET_FILMSTRIP_HOVERED,
SET_FILMSTRIP_VISIBLE,
SET_TILE_VIEW_DIMENSIONS
SET_FILMSTRIP_VISIBLE
} from './actionTypes';
/**
@@ -54,26 +53,3 @@ export function setFilmstripVisible(visible: boolean) {
visible
};
}
/**
* Sets the dimensions of the tile view grid. The action is only partially implemented on native as not all
* of the values are currently used. Check the description of {@link SET_TILE_VIEW_DIMENSIONS} for the full set
* of properties.
*
* @param {Object} dimensions - The tile view dimensions.
* @param {Object} thumbnailSize - The size of an individual video thumbnail.
* @param {number} thumbnailSize.height - The height of an individual video thumbnail.
* @param {number} thumbnailSize.width - The width of an individual video thumbnail.
* @returns {{
* type: SET_TILE_VIEW_DIMENSIONS,
* dimensions: Object
* }}
*/
export function setTileViewDimensions({ thumbnailSize }: Object) {
return {
type: SET_TILE_VIEW_DIMENSIONS,
dimensions: {
thumbnailSize
}
};
}

View File

@@ -1,7 +1,5 @@
// @flow
import { CHAT_SIZE } from '../chat/constants';
import { SET_HORIZONTAL_VIEW_DIMENSIONS, SET_TILE_VIEW_DIMENSIONS } from './actionTypes';
import { calculateThumbnailSizeForHorizontalView, calculateThumbnailSizeForTileView } from './functions';
@@ -15,25 +13,15 @@ const TILE_VIEW_SIDE_MARGINS = 10 * 2;
*
* @param {Object} dimensions - Whether the filmstrip is visible.
* @param {Object} windowSize - The size of the window.
* @param {boolean} isChatOpen - Whether the chat panel is displayed, in
* order to properly compute the tile view size.
* @returns {{
* type: SET_TILE_VIEW_DIMENSIONS,
* dimensions: Object
* }}
*/
export function setTileViewDimensions(dimensions: Object, windowSize: Object, isChatOpen: boolean) {
const { clientWidth, clientHeight } = windowSize;
let widthToUse = clientWidth;
if (isChatOpen) {
widthToUse -= CHAT_SIZE;
}
export function setTileViewDimensions(dimensions: Object, windowSize: Object) {
const thumbnailSize = calculateThumbnailSizeForTileView({
...dimensions,
clientWidth: widthToUse,
clientHeight
...windowSize
});
const filmstripWidth = dimensions.columns * (TILE_VIEW_SIDE_MARGINS + thumbnailSize.width);

View File

@@ -149,7 +149,7 @@ function Thumbnail(props: Props) {
touchFeedback = { false }>
<ParticipantView
avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
avatarSize = { AVATAR_SIZE }
disableVideo = { isScreenShare || participant.isFakeParticipant }
participantId = { participantId }
style = { _styles.participantViewStyle }
@@ -157,9 +157,7 @@ function Thumbnail(props: Props) {
tintStyle = { _styles.activeThumbnailTint }
zOrder = { 1 } />
{ renderDisplayName && <Container style = { styles.displayNameContainer }>
<DisplayNameLabel participantId = { participantId } />
</Container> }
{ renderDisplayName && <DisplayNameLabel participantId = { participantId } /> }
{ renderModeratorIndicator
&& <View style = { styles.moderatorIndicatorContainer }>

View File

@@ -8,9 +8,12 @@ import {
} from 'react-native';
import type { Dispatch } from 'redux';
import {
getNearestReceiverVideoQualityLevel,
setMaxReceiverVideoQuality
} from '../../../base/conference';
import { connect } from '../../../base/redux';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import { setTileViewDimensions } from '../../actions.native';
import Thumbnail from './Thumbnail';
import styles from './styles';
@@ -263,14 +266,10 @@ class TileView extends Component<Props> {
* @returns {void}
*/
_updateReceiverQuality() {
const { height, width } = this._getTileDimensions();
const { height } = this._getTileDimensions();
const qualityLevel = getNearestReceiverVideoQualityLevel(height);
this.props.dispatch(setTileViewDimensions({
thumbnailSize: {
height,
width
}
}));
this.props.dispatch(setMaxReceiverVideoQuality(qualityLevel));
}
}

View File

@@ -14,17 +14,6 @@ export const AVATAR_SIZE = 50;
*/
export default {
/**
* The display name container.
*/
displayNameContainer: {
alignSelf: 'center',
bottom: 0,
flex: 1,
margin: 4,
position: 'absolute'
},
/**
* The style of the narrow {@link Filmstrip} version which displays
* thumbnails in a row at the bottom of the screen.

View File

@@ -371,15 +371,13 @@ function _mapStateToProps(state) {
const reduceHeight
= !isFilmstripOnly && state['features/toolbox'].visible && interfaceConfig.TOOLBAR_BUTTONS.length;
const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
const { isOpen: shiftRight } = state['features/chat'];
const className = `${remoteVideosVisible ? '' : 'hide-videos'} ${
reduceHeight ? 'reduce-height' : ''
} ${shiftRight ? 'shift-right' : ''}`.trim();
const className = `${remoteVideosVisible ? '' : 'hide-videos'} ${reduceHeight ? 'reduce-height' : ''}`.trim();
const videosClassName = `filmstrip__videos${
isFilmstripOnly ? ' filmstrip__videos-filmstripOnly' : ''}${
visible ? '' : ' hidden'}`;
const { gridDimensions = {}, filmstripWidth } = state['features/filmstrip'].tileViewDimensions;
return {
_className: className,
_columns: gridDimensions.columns,

View File

@@ -1,6 +1,7 @@
// @flow
import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
import { getNearestReceiverVideoQualityLevel, setMaxReceiverVideoQuality } from '../base/conference';
import { MiddlewareRegistry } from '../base/redux';
import { CLIENT_RESIZED } from '../base/responsive-ui';
import {
@@ -10,7 +11,7 @@ import {
} from '../video-layout';
import { SET_HORIZONTAL_VIEW_DIMENSIONS, SET_TILE_VIEW_DIMENSIONS } from './actionTypes';
import { setHorizontalViewDimensions, setTileViewDimensions } from './actions.web';
import { setHorizontalViewDimensions, setTileViewDimensions } from './actions';
import './subscriber.web';
@@ -29,18 +30,11 @@ MiddlewareRegistry.register(store => next => action => {
case LAYOUTS.TILE_VIEW: {
const { gridDimensions } = state['features/filmstrip'].tileViewDimensions;
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
store.dispatch(
setTileViewDimensions(
gridDimensions,
{
clientHeight,
clientWidth
},
isOpen
)
);
store.dispatch(setTileViewDimensions(gridDimensions, {
clientHeight,
clientWidth
}));
break;
}
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
@@ -54,6 +48,9 @@ MiddlewareRegistry.register(store => next => action => {
if (shouldDisplayTileView(state)) {
const { width, height } = state['features/filmstrip'].tileViewDimensions.thumbnailSize;
const qualityLevel = getNearestReceiverVideoQualityLevel(height);
store.dispatch(setMaxReceiverVideoQuality(qualityLevel));
// Once the thumbnails are reactified this should be moved there too.
Filmstrip.resizeThumbnailsForTileView(width, height, true);

View File

@@ -5,7 +5,7 @@ import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
import { StateListenerRegistry, equals } from '../base/redux';
import { getCurrentLayout, getTileViewGridDimensions, shouldDisplayTileView, LAYOUTS } from '../video-layout';
import { setHorizontalViewDimensions, setTileViewDimensions } from './actions.web';
import { setHorizontalViewDimensions, setTileViewDimensions } from './actions';
/**
* Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
@@ -19,19 +19,12 @@ StateListenerRegistry.register(
const gridDimensions = getTileViewGridDimensions(state);
const oldGridDimensions = state['features/filmstrip'].tileViewDimensions.gridDimensions;
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
if (!equals(gridDimensions, oldGridDimensions)) {
store.dispatch(
setTileViewDimensions(
gridDimensions,
{
clientHeight,
clientWidth
},
isOpen
)
);
store.dispatch(setTileViewDimensions(gridDimensions, {
clientHeight,
clientWidth
}));
}
}
});
@@ -47,18 +40,12 @@ StateListenerRegistry.register(
switch (layout) {
case LAYOUTS.TILE_VIEW: {
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
store.dispatch(
setTileViewDimensions(
getTileViewGridDimensions(state),
{
clientHeight,
clientWidth
},
isOpen
)
);
store.dispatch(setTileViewDimensions(
getTileViewGridDimensions(state), {
clientHeight,
clientWidth
}));
break;
}
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
@@ -89,36 +76,3 @@ StateListenerRegistry.register(
}
}
);
/**
* Listens for changes in the chat state to calculate the dimensions of the tile view grid and the tiles.
*/
StateListenerRegistry.register(
/* selector */ state => state['features/chat'].isOpen,
/* listener */ (isChatOpen, store) => {
const state = store.getState();
if (isChatOpen) {
// $FlowFixMe
document.body.classList.add('shift-right');
} else {
// $FlowFixMe
document.body.classList.remove('shift-right');
}
if (shouldDisplayTileView(state)) {
const gridDimensions = getTileViewGridDimensions(state);
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
store.dispatch(
setTileViewDimensions(
gridDimensions,
{
clientHeight,
clientWidth
},
isChatOpen
)
);
}
});

View File

@@ -4,7 +4,7 @@ import React, { useState } from 'react';
import { translate } from '../../../../base/i18n';
import { Icon, IconCheck, IconCopy } from '../../../../base/icons';
import { copyText, getDecodedURI } from '../../../../base/util';
import { copyText } from '../../../../base/util';
type Props = {
@@ -82,7 +82,7 @@ function CopyMeetingLinkSection({ t, url }: Props) {
);
}
const displayUrl = getDecodedURI(url);
const displayUrl = decodeURI(url.replace(/^https?:\/\//i, ''));
return (
<>

View File

@@ -4,7 +4,6 @@ import React, { Component } from 'react';
import { Watermarks } from '../../base/react';
import { connect } from '../../base/redux';
import { InviteMore, Subject } from '../../conference';
import { fetchCustomBrandingData } from '../../dynamic-branding';
import { Captions } from '../../subtitles/';
@@ -27,11 +26,6 @@ type Props = {
*/
_fetchCustomBrandingData: Function,
/**
* Prop that indicates whether the chat is open.
*/
_isChatOpen: boolean,
/**
* Used to determine the value of the autoplay attribute of the underlying
* video element.
@@ -63,15 +57,12 @@ class LargeVideo extends Component<Props> {
*/
render() {
const style = this._getCustomSyles();
const className = `videocontainer${this.props._isChatOpen ? ' shift-right' : ''}`;
return (
<div
className = { className }
className = 'videocontainer'
id = 'largeVideoContainer'
style = { style }>
<Subject />
<InviteMore />
<div id = 'sharedVideo'>
<div id = 'sharedVideoIFrame' />
</div>
@@ -142,12 +133,10 @@ class LargeVideo extends Component<Props> {
function _mapStateToProps(state) {
const testingConfig = state['features/base/config'].testing;
const { backgroundColor, backgroundImageUrl } = state['features/dynamic-branding'];
const { isOpen: isChatOpen } = state['features/chat'];
return {
_customBackgroundColor: backgroundColor,
_customBackgroundImageUrl: backgroundImageUrl,
_isChatOpen: isChatOpen,
_noAutoPlayVideo: testingConfig?.noAutoPlayVideo
};
}

View File

@@ -1,6 +1,6 @@
// @flow
import { NativeModules, Platform } from 'react-native';
import { Platform } from 'react-native';
import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
import { translate } from '../../../base/i18n';
@@ -66,8 +66,9 @@ function _mapStateToProps(state): Object {
const flag = Boolean(getFeatureFlag(state, PIP_ENABLED));
let enabled = flag;
// Override flag for Android, since it might be unsupported.
if (Platform.OS === 'android' && !NativeModules.PictureInPicture.SUPPORTED) {
// Override flag for Android < 26, PiP was introduced in Oreo.
// https://developer.android.com/guide/topics/ui/picture-in-picture
if (Platform.OS === 'android' && Platform.Version < 26) {
enabled = false;
}

View File

@@ -7,7 +7,7 @@ import { getRoomName } from '../../base/conference';
import { translate } from '../../base/i18n';
import { Icon, IconPhone, IconVolumeOff } from '../../base/icons';
import { isVideoMutedByUser } from '../../base/media';
import { ActionButton, InputField, PreMeetingScreen, ToggleButton } from '../../base/premeeting';
import { ActionButton, InputField, PreMeetingScreen } from '../../base/premeeting';
import { connect } from '../../base/redux';
import { getDisplayName, updateSettings } from '../../base/settings';
import { getLocalJitsiVideoTrack } from '../../base/tracks';
@@ -21,8 +21,7 @@ import {
isDeviceStatusVisible,
isDisplayNameRequired,
isJoinByPhoneButtonVisible,
isJoinByPhoneDialogVisible,
isPrejoinSkipped
isJoinByPhoneDialogVisible
} from '../functions';
import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
@@ -30,11 +29,6 @@ import DeviceStatus from './preview/DeviceStatus';
type Props = {
/**
* Flag signaling if the 'skip prejoin' button is toggled or not.
*/
buttonIsToggled: boolean,
/**
* Flag signaling if the device status is visible or not.
*/
@@ -85,21 +79,11 @@ type Props = {
*/
setJoinByPhoneDialogVisiblity: Function,
/**
* Indicates whether the avatar should be shown when video is off
*/
showAvatar: boolean,
/**
* Flag signaling the visibility of camera preview.
*/
showCameraPreview: boolean,
/**
* Flag signaling the visibility of join label, input and buttons
*/
showJoinActions: boolean,
/**
* If 'JoinByPhoneDialog' is visible or not.
*/
@@ -128,15 +112,6 @@ type State = {
* This component is displayed before joining a meeting.
*/
class Prejoin extends Component<Props, State> {
/**
* Default values for {@code Prejoin} component's properties.
*
* @static
*/
static defaultProps = {
showJoinActions: true
};
/**
* Initializes a new {@code Prejoin} instance.
*
@@ -151,22 +126,22 @@ class Prejoin extends Component<Props, State> {
this._closeDialog = this._closeDialog.bind(this);
this._showDialog = this._showDialog.bind(this);
this._onToggleButtonClick = this._onToggleButtonClick.bind(this);
this._onCheckboxChange = this._onCheckboxChange.bind(this);
this._onDropdownClose = this._onDropdownClose.bind(this);
this._onOptionsClick = this._onOptionsClick.bind(this);
this._setName = this._setName.bind(this);
}
_onToggleButtonClick: () => void;
_onCheckboxChange: () => void;
/**
* Handler for the toggle button.
* Handler for the checkbox.
*
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onToggleButtonClick() {
this.props.setSkipPrejoin(!this.props.buttonIsToggled);
_onCheckboxChange(e) {
this.props.setSkipPrejoin(e.target.checked);
}
_onDropdownClose: () => void;
@@ -248,73 +223,74 @@ class Prejoin extends Component<Props, State> {
joinConference,
joinConferenceWithoutAudio,
name,
showAvatar,
showCameraPreview,
showDialog,
showJoinActions,
t,
videoTrack
} = this.props;
const { _closeDialog, _onDropdownClose, _onOptionsClick, _setName, _showDialog } = this;
const { _closeDialog, _onCheckboxChange, _onDropdownClose, _onOptionsClick, _setName, _showDialog } = this;
const { showJoinByPhoneButtons } = this.state;
return (
<PreMeetingScreen
footer = { this._renderFooter() }
name = { name }
showAvatar = { showAvatar }
showConferenceInfo = { showJoinActions }
skipPrejoinButton = { this._renderSkipPrejoinButton() }
title = { t('prejoin.joinMeeting') }
videoMuted = { !showCameraPreview }
videoTrack = { videoTrack }>
{showJoinActions && (
<div className = 'prejoin-input-area-container'>
<div className = 'prejoin-input-area'>
<InputField
onChange = { _setName }
onSubmit = { joinConference }
placeHolder = { t('dialog.enterDisplayName') }
value = { name } />
<div className = 'prejoin-input-area-container'>
<div className = 'prejoin-input-area'>
<InputField
onChange = { _setName }
onSubmit = { joinConference }
placeHolder = { t('dialog.enterDisplayName') }
value = { name } />
<div className = 'prejoin-preview-dropdown-container'>
<InlineDialog
content = { <div className = 'prejoin-preview-dropdown-btns'>
<div
className = 'prejoin-preview-dropdown-btn'
onClick = { joinConferenceWithoutAudio }>
<Icon
className = 'prejoin-preview-dropdown-icon'
size = { 24 }
src = { IconVolumeOff } />
{ t('prejoin.joinWithoutAudio') }
</div>
{hasJoinByPhoneButton && <div
className = 'prejoin-preview-dropdown-btn'
onClick = { _showDialog }>
<Icon
className = 'prejoin-preview-dropdown-icon'
size = { 24 }
src = { IconPhone } />
{ t('prejoin.joinAudioByPhone') }
</div>}
</div> }
isOpen = { showJoinByPhoneButtons }
onClose = { _onDropdownClose }>
<ActionButton
disabled = { joinButtonDisabled }
hasOptions = { true }
onClick = { joinConference }
onOptionsClick = { _onOptionsClick }
type = 'primary'>
{ t('prejoin.joinMeeting') }
</ActionButton>
</InlineDialog>
</div>
<div className = 'prejoin-preview-dropdown-container'>
<InlineDialog
content = { <div className = 'prejoin-preview-dropdown-btns'>
<div
className = 'prejoin-preview-dropdown-btn'
onClick = { joinConferenceWithoutAudio }>
<Icon
className = 'prejoin-preview-dropdown-icon'
size = { 24 }
src = { IconVolumeOff } />
{ t('prejoin.joinWithoutAudio') }
</div>
{hasJoinByPhoneButton && <div
className = 'prejoin-preview-dropdown-btn'
onClick = { _showDialog }>
<Icon
className = 'prejoin-preview-dropdown-icon'
size = { 24 }
src = { IconPhone } />
{ t('prejoin.joinAudioByPhone') }
</div>}
</div> }
isOpen = { showJoinByPhoneButtons }
onClose = { _onDropdownClose }>
<ActionButton
disabled = { joinButtonDisabled }
hasOptions = { true }
onClick = { joinConference }
onOptionsClick = { _onOptionsClick }
type = 'primary'>
{ t('prejoin.joinMeeting') }
</ActionButton>
</InlineDialog>
</div>
</div>
)}
<div className = 'prejoin-checkbox-container'>
<input
className = 'prejoin-checkbox'
onChange = { _onCheckboxChange }
type = 'checkbox' />
<span>{t('prejoin.doNotShow')}</span>
</div>
</div>
{ showDialog && (
<JoinByPhoneDialog
joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
@@ -332,25 +308,6 @@ class Prejoin extends Component<Props, State> {
_renderFooter() {
return this.props.deviceStatusVisible && <DeviceStatus />;
}
/**
* Renders the 'skip prejoin' button.
*
* @returns {React$Element}
*/
_renderSkipPrejoinButton() {
const { buttonIsToggled, t } = this.props;
return (
<div className = 'prejoin-checkbox-container'>
<ToggleButton
isToggled = { buttonIsToggled }
onClick = { this._onToggleButtonClick }>
{t('prejoin.doNotShow')}
</ToggleButton>
</div>
);
}
}
/**
@@ -364,7 +321,6 @@ function mapStateToProps(state): Object {
const joinButtonDisabled = isDisplayNameRequired(state) && !name;
return {
buttonIsToggled: isPrejoinSkipped(state),
joinButtonDisabled,
name,
deviceStatusVisible: isDeviceStatusVisible(state),

View File

@@ -1,93 +0,0 @@
// @flow
import { AtlasKitThemeProvider } from '@atlaskit/theme';
import React from 'react';
import { BaseApp } from '../../../features/base/app';
import { setConfig } from '../../base/config';
import { createPrejoinTracks } from '../../base/tracks';
import { initPrejoin } from '../actions';
import Prejoin from './Prejoin';
type Props = {
/**
* Indicates whether the avatar should be shown when video is off
*/
showAvatar: boolean,
/**
* Flag signaling the visibility of join label, input and buttons
*/
showJoinActions: boolean,
};
/**
* Wrapper application for prejoin.
*
* @extends BaseApp
*/
export default class PrejoinApp extends BaseApp<Props> {
_init: Promise<*>;
/**
* Navigates to {@link Prejoin} upon mount.
*
* @returns {void}
*/
componentDidMount() {
super.componentDidMount();
this._init.then(async () => {
const { store } = this.state;
const { dispatch } = store;
const { showAvatar, showJoinActions } = this.props;
super._navigate({
component: Prejoin,
props: {
showAvatar,
showJoinActions
}
});
const { startWithAudioMuted, startWithVideoMuted } = store.getState()['features/base/settings'];
dispatch(setConfig({
prejoinPageEnabled: true,
startWithAudioMuted,
startWithVideoMuted
}));
const { tryCreateLocalTracks, errors } = createPrejoinTracks();
const tracks = await tryCreateLocalTracks;
dispatch(initPrejoin(tracks, errors));
});
}
/**
* Overrides the parent method to inject {@link AtlasKitThemeProvider} as
* the top most component.
*
* @override
*/
_createMainElement(component, props) {
return (
<AtlasKitThemeProvider mode = 'dark'>
{ super._createMainElement(component, props) }
</AtlasKitThemeProvider>
);
}
/**
* Renders the platform specific dialog container.
*
* @returns {React$Element}
*/
_renderDialogContainer() {
return null;
}
}

View File

@@ -36,16 +36,6 @@ export function isDisplayNameRequired(state: Object): boolean {
|| state['features/base/config'].requireDisplayName;
}
/**
* Selector for determining if the user has chosen to skip prejoin page.
*
* @param {Object} state - The state of the app.
* @returns {boolean}
*/
export function isPrejoinSkipped(state: Object) {
return state['features/prejoin'].userSelectedSkipPrejoin;
}
/**
* Returns the text for the prejoin status bar.
*

View File

@@ -1,13 +1,11 @@
// @flow
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
import { MiddlewareRegistry } from '../base/redux';
import { updateSettings } from '../base/settings';
import { getLocalVideoTrack, replaceLocalTrack } from '../base/tracks';
import { PREJOIN_START_CONFERENCE } from './actionTypes';
import { setPrejoinPageVisibility } from './actions';
import { isPrejoinPageVisible } from './functions';
declare var APP: Object;
@@ -40,26 +38,8 @@ MiddlewareRegistry.register(store => next => async action => {
break;
}
case SET_AUDIO_MUTED: {
if (isPrejoinPageVisible(store.getState())) {
store.dispatch(updateSettings({
startWithAudioMuted: Boolean(action.muted)
}));
}
break;
}
case SET_VIDEO_MUTED: {
if (isPrejoinPageVisible(store.getState())) {
store.dispatch(updateSettings({
startWithVideoMuted: Boolean(action.muted)
}));
}
break;
}
}
return next(action);
});

View File

@@ -1,75 +0,0 @@
// @flow
import { openDialog } from '../../base/dialog';
import { IconCrown } from '../../base/icons';
import {
getLocalParticipant,
getParticipantById,
isParticipantModerator,
PARTICIPANT_ROLE
} from '../../base/participants';
import { AbstractButton } from '../../base/toolbox';
import type { AbstractButtonProps } from '../../base/toolbox';
import { GrantModeratorDialog } from '.';
export type Props = AbstractButtonProps & {
/**
* The redux {@code dispatch} function.
*/
dispatch: Function,
/**
* The ID of the participant for whom to grant moderator status.
*/
participantID: string,
/**
* The function to be used to translate i18n labels.
*/
t: Function
};
/**
* An abstract remote video menu button which kicks the remote participant.
*/
export default class AbstractGrantModeratorButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.grantModerator';
icon = IconCrown;
label = 'videothumbnail.grantModerator';
/**
* Handles clicking / pressing the button, and kicks the participant.
*
* @private
* @returns {void}
*/
_handleClick() {
const { dispatch, participantID } = this.props;
dispatch(openDialog(GrantModeratorDialog, { participantID }));
}
}
/**
* Function that maps parts of Redux state tree into component props.
*
* @param {Object} state - Redux state.
* @param {Object} ownProps - Properties of component.
* @private
* @returns {{
* visible: boolean
* }}
*/
export function _mapStateToProps(state: Object, ownProps: Props) {
const { participantID } = ownProps;
const localParticipant = getLocalParticipant(state);
const targetParticipant = getParticipantById(state, participantID);
return {
visible: Boolean(localParticipant?.role === PARTICIPANT_ROLE.MODERATOR)
&& !isParticipantModerator(targetParticipant)
};
}

View File

@@ -1,66 +0,0 @@
// @flow
import { Component } from 'react';
import {
createRemoteVideoMenuButtonEvent,
sendAnalytics
} from '../../analytics';
import { grantModerator } from '../../base/participants';
type Props = {
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* The ID of the remote participant to be granted moderator rights.
*/
participantID: string,
/**
* Function to translate i18n labels.
*/
t: Function
};
/**
* Abstract dialog to confirm granting moderator to a participant.
*/
export default class AbstractGrantModeratorDialog
extends Component<Props> {
/**
* Initializes a new {@code AbstractGrantModeratorDialog} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onSubmit = this._onSubmit.bind(this);
}
_onSubmit: () => boolean;
/**
* Callback for the confirm button.
*
* @private
* @returns {boolean} - True (to note that the modal should be closed).
*/
_onSubmit() {
const { dispatch, participantID } = this.props;
sendAnalytics(createRemoteVideoMenuButtonEvent(
'grant.moderator.button',
{
'participant_id': participantID
}));
dispatch(grantModerator(participantID));
return true;
}
}

View File

@@ -1,9 +0,0 @@
// @flow
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import AbstractGrantModeratorButton, {
_mapStateToProps
} from '../AbstractGrantModeratorButton';
export default translate(connect(_mapStateToProps)(AbstractGrantModeratorButton));

View File

@@ -1,32 +0,0 @@
// @flow
import React from 'react';
import { ConfirmDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import AbstractGrantModeratorDialog
from '../AbstractGrantModeratorDialog';
/**
* Dialog to confirm a remote participant kick action.
*/
class GrantModeratorDialog extends AbstractGrantModeratorDialog {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<ConfirmDialog
contentKey = 'dialog.grantModeratorDialog'
onSubmit = { this._onSubmit } />
);
}
_onSubmit: () => boolean;
}
export default translate(connect()(GrantModeratorDialog));

View File

@@ -12,7 +12,6 @@ import { StyleType } from '../../../base/styles';
import { PrivateMessageButton } from '../../../chat';
import { hideRemoteVideoMenu } from '../../actions';
import GrantModeratorButton from './GrantModeratorButton';
import KickButton from './KickButton';
import MuteButton from './MuteButton';
import PinButton from './PinButton';
@@ -99,8 +98,6 @@ class RemoteVideoMenu extends Component<Props> {
buttons.push(<MuteButton { ...buttonProps } />);
}
buttons.push(<GrantModeratorButton { ...buttonProps } />);
if (!_disableKick) {
buttons.push(<KickButton { ...buttonProps } />);
}

View File

@@ -1,8 +1,5 @@
// @flow
export {
default as GrantModeratorDialog
} from './GrantModeratorDialog';
export {
default as KickRemoteParticipantDialog
} from './KickRemoteParticipantDialog';

View File

@@ -1,60 +0,0 @@
/* @flow */
import React from 'react';
import { translate } from '../../../base/i18n';
import { IconCrown } from '../../../base/icons';
import { connect } from '../../../base/redux';
import AbstractGrantModeratorButton, {
_mapStateToProps,
type Props
} from '../AbstractGrantModeratorButton';
import RemoteVideoMenuButton from './RemoteVideoMenuButton';
declare var interfaceConfig: Object;
/**
* Implements a React {@link Component} which displays a button for granting
* moderator to a participant.
*/
class GrantModeratorButton extends AbstractGrantModeratorButton {
/**
* Instantiates a new {@code GrantModeratorButton}.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._handleClick = this._handleClick.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { participantID, t, visible } = this.props;
if (!visible) {
return null;
}
return (
<RemoteVideoMenuButton
buttonText = { t('videothumbnail.grantModerator') }
displayClass = 'grantmoderatorlink'
icon = { IconCrown }
id = { `grantmoderatorlink_${participantID}` }
// eslint-disable-next-line react/jsx-handler-names
onClick = { this._handleClick } />
);
}
_handleClick: () => void
}
export default translate(connect(_mapStateToProps)(GrantModeratorButton));

View File

@@ -1,38 +0,0 @@
// @flow
import React from 'react';
import { Dialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import AbstractGrantModeratorDialog
from '../AbstractGrantModeratorDialog';
/**
* Dialog to confirm a grant moderator action.
*/
class GrantModeratorDialog extends AbstractGrantModeratorDialog {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<Dialog
okKey = 'dialog.Yes'
onSubmit = { this._onSubmit }
titleKey = 'dialog.grantModeratorTitle'
width = 'small'>
<div>
{ this.props.t('dialog.grantModeratorDialog') }
</div>
</Dialog>
);
}
_onSubmit: () => boolean;
}
export default translate(connect()(GrantModeratorDialog));

View File

@@ -8,7 +8,6 @@ import { Popover } from '../../../base/popover';
import { connect } from '../../../base/redux';
import {
GrantModeratorButton,
MuteButton,
MuteEveryoneElseButton,
KickButton,
@@ -196,12 +195,6 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
);
}
buttons.push(
<GrantModeratorButton
key = 'grant-moderator'
participantID = { participantID } />
);
if (!_disableKick) {
buttons.push(
<KickButton

View File

@@ -1,9 +1,5 @@
// @flow
export { default as GrantModeratorButton } from './GrantModeratorButton';
export {
default as GrantModeratorDialog
} from './GrantModeratorDialog';
export { default as KickButton } from './KickButton';
export {
default as KickRemoteParticipantDialog

View File

@@ -1,111 +0,0 @@
import rtcstatsInit from 'rtcstats/rtcstats';
import traceInit from 'rtcstats/trace-ws';
import {
createRTCStatsTraceCloseEvent,
sendAnalytics
} from '../analytics';
import logger from './logger';
/**
* Filter out RTCPeerConnection that are created by callstats.io.
*
* @param {*} config - Config object sent to the PC c'tor.
* @returns {boolean}
*/
function connectionFilter(config) {
if (config && config.iceServers[0] && config.iceServers[0].urls) {
for (const iceUrl of config.iceServers[0].urls) {
if (iceUrl.indexOf('taas.callstats.io') >= 0) {
return true;
}
}
}
}
/**
* Class that controls the rtcstats flow, because it overwrites and proxies global function it should only be
* initialized once.
*/
class RTCStats {
/**
* Initialize the rtcstats components. First off we initialize the trace, which is a wrapped websocket
* that does the actual communication with the server. Secondly, the rtcstats component is initialized,
* it overwrites GUM and PeerConnection global functions and adds a proxy over them used to capture stats.
* Note, lib-jitsi-meet takes references to these methods before initializing so the init method needs to be
* loaded before it does.
*
* @param {Object} options -.
* @param {string} options.rtcstatsEndpoint - The Amplitude app key required.
* @param {number} options.rtcstatsPollInterval - The getstats poll interval in ms.
* @returns {void}
*/
init(options) {
this.handleTraceWSClose = this.handleTraceWSClose.bind(this);
this.trace = traceInit(options.rtcstatsEndpoint, this.handleTraceWSClose);
rtcstatsInit(this.trace, options.rtcstatsPollInterval, [ '' ], connectionFilter);
this.initialized = true;
}
/**
* Check whether or not the RTCStats is initialized.
*
* @returns {boolean}
*/
isInitialized() {
return this.initialized;
}
/**
* Send identity data to rtcstats server, this will be reflected in the identity section of the stats dump.
* It can be generally used to send additional metadata that might be relevant such as amplitude user data
* or deployment specific information.
*
* @param {Object} identityData - Metadata object to send as identity.
* @returns {void}
*/
sendIdentityData(identityData) {
this.trace && this.trace('identity', null, identityData);
}
/**
* Connect to the rtcstats server instance. Stats (data obtained from getstats) won't be send until the
* connect successfully initializes, however calls to GUM are recorded in an internal buffer even if not
* connected and sent once it is established.
*
* @returns {void}
*/
connect() {
this.trace && this.trace.connect();
}
/**
* Self explanatory; closes the web socked connection.
* Note, at the point of writing this documentation there was no method to reset the function overwrites,
* thus even if the websocket is closed the global function proxies are still active but send no data,
* this shouldn't influence the normal flow of the application.
*
* @returns {void}
*/
close() {
this.trace && this.trace.close();
}
/**
* The way rtcstats is currently designed the ws wouldn't normally be closed by the application logic but rather
* by the page being closed/reloaded. Using this assumption any onclose event is most likely something abnormal
* that happened on the ws. We then track this in order to determine how many rtcstats connection were closed
* prematurely.
*
* @param {Object} closeEvent - Event sent by ws onclose.
* @returns {void}
*/
handleTraceWSClose(closeEvent) {
logger.info('RTCStats trace ws closed', closeEvent);
sendAnalytics(createRTCStatsTraceCloseEvent(closeEvent));
}
}
export default new RTCStats();

Some files were not shown because too many files have changed in this diff Show More