Compare commits
24 Commits
ios_locksc
...
6828
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93566e313e | ||
|
|
a7c653bc30 | ||
|
|
4b969cf4ab | ||
|
|
3081b41d0d | ||
|
|
30e5d213cb | ||
|
|
752da71387 | ||
|
|
645609974a | ||
|
|
4f2f6df2bb | ||
|
|
e93c480e7c | ||
|
|
a795e0797a | ||
|
|
94ec2c720d | ||
|
|
b11e14ea34 | ||
|
|
f5f55c4f23 | ||
|
|
243a330318 | ||
|
|
98bc87ea18 | ||
|
|
f7926c9cfb | ||
|
|
533501deb6 | ||
|
|
f38c9f5450 | ||
|
|
55b80c948f | ||
|
|
971fe0481f | ||
|
|
1259e54d46 | ||
|
|
0cb95f1dd6 | ||
|
|
5cde9a138b | ||
|
|
867c488e10 |
@@ -76,7 +76,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import groovy.json.JsonSlurper
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
import org.gradle.util.VersionNumber
|
||||
|
||||
// Top-level build file where you can add configuration options common to all
|
||||
@@ -12,16 +11,16 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||
classpath 'com.google.gms:google-services:4.3.10'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
|
||||
classpath 'com.google.gms:google-services:4.3.14'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
buildToolsVersion = "31.0.0"
|
||||
compileSdkVersion = 31
|
||||
compileSdkVersion = 32
|
||||
minSdkVersion = 23
|
||||
targetSdkVersion = 31
|
||||
targetSdkVersion = 32
|
||||
supportLibVersion = "28.0.0"
|
||||
|
||||
if (System.properties['os.arch'] == "aarch64") {
|
||||
|
||||
@@ -42,10 +42,10 @@ public class WebRTCVideoDecoderFactory implements VideoDecoderFactory {
|
||||
public VideoCodecInfo[] getSupportedCodecs() {
|
||||
List<VideoCodecInfo> codecs = new ArrayList<>();
|
||||
|
||||
codecs.add(H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC);
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP8.name(), new HashMap<>()));
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP9.name(), new HashMap<>()));
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.AV1.name(), new HashMap<>()));
|
||||
codecs.add(H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC);
|
||||
|
||||
return codecs.toArray(new VideoCodecInfo[codecs.size()]);
|
||||
}
|
||||
|
||||
@@ -43,10 +43,10 @@ public class WebRTCVideoEncoderFactory implements VideoEncoderFactory {
|
||||
public VideoCodecInfo[] getSupportedCodecs() {
|
||||
List<VideoCodecInfo> codecs = new ArrayList<>();
|
||||
|
||||
codecs.add(H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC);
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP8.name(), new HashMap<>()));
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP9.name(), new HashMap<>()));
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.AV1.name(), new HashMap<>()));
|
||||
codecs.add(H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC);
|
||||
|
||||
return codecs.toArray(new VideoCodecInfo[codecs.size()]);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
conferenceUniqueIdSet,
|
||||
conferenceWillJoin,
|
||||
conferenceWillLeave,
|
||||
dataChannelClosed,
|
||||
dataChannelOpened,
|
||||
e2eRttChanged,
|
||||
getConferenceOptions,
|
||||
@@ -54,10 +55,7 @@ import {
|
||||
p2pStatusChanged,
|
||||
sendLocalParticipant
|
||||
} from './react/features/base/conference';
|
||||
import {
|
||||
getMultipleVideoSendingSupportFeatureFlag,
|
||||
getReplaceParticipant
|
||||
} from './react/features/base/config/functions';
|
||||
import { getReplaceParticipant } from './react/features/base/config/functions';
|
||||
import {
|
||||
checkAndNotifyForNewDevice,
|
||||
getAvailableDevices,
|
||||
@@ -134,9 +132,12 @@ import {
|
||||
import { maybeSetLobbyChatMessageListener } from './react/features/lobby/actions.any';
|
||||
import { setNoiseSuppressionEnabled } from './react/features/noise-suppression/actions';
|
||||
import {
|
||||
DATA_CHANNEL_CLOSED_NOTIFICATION_ID,
|
||||
NOTIFICATION_TIMEOUT_TYPE,
|
||||
hideNotification,
|
||||
isModerationNotificationDisplayed,
|
||||
showNotification
|
||||
showNotification,
|
||||
showWarningNotification
|
||||
} from './react/features/notifications';
|
||||
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
|
||||
import { suspendDetected } from './react/features/power-monitor';
|
||||
@@ -1418,30 +1419,13 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// In the multi-stream mode, add the track to the conference if there is no existing track, replace it
|
||||
// otherwise.
|
||||
if (getMultipleVideoSendingSupportFeatureFlag(state)) {
|
||||
const trackAction = oldTrack
|
||||
? replaceLocalTrack(oldTrack, newTrack, room)
|
||||
: addLocalTrack(newTrack);
|
||||
// Add the track to the conference if there is no existing track, replace it otherwise.
|
||||
const trackAction = oldTrack
|
||||
? replaceLocalTrack(oldTrack, newTrack, room)
|
||||
: addLocalTrack(newTrack);
|
||||
|
||||
APP.store.dispatch(trackAction)
|
||||
.then(() => {
|
||||
this.setVideoMuteStatus();
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(error => {
|
||||
logger.error(`useVideoStream failed: ${error}`);
|
||||
reject(error);
|
||||
})
|
||||
.then(onFinish);
|
||||
|
||||
return;
|
||||
}
|
||||
APP.store.dispatch(
|
||||
replaceLocalTrack(oldTrack, newTrack, room))
|
||||
APP.store.dispatch(trackAction)
|
||||
.then(() => {
|
||||
this._setSharingScreen(newTrack);
|
||||
this.setVideoMuteStatus();
|
||||
})
|
||||
.then(resolve)
|
||||
@@ -2067,6 +2051,18 @@ export default {
|
||||
room.on(
|
||||
JitsiConferenceEvents.DATA_CHANNEL_OPENED, () => {
|
||||
APP.store.dispatch(dataChannelOpened());
|
||||
APP.store.dispatch(hideNotification(DATA_CHANNEL_CLOSED_NOTIFICATION_ID));
|
||||
}
|
||||
);
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.DATA_CHANNEL_CLOSED, ev => {
|
||||
APP.store.dispatch(dataChannelClosed(ev.code, ev.reason));
|
||||
APP.store.dispatch(showWarningNotification({
|
||||
descriptionKey: 'notify.dataChannelClosedDescription',
|
||||
titleKey: 'notify.dataChannelClosed',
|
||||
uid: DATA_CHANNEL_CLOSED_NOTIFICATION_ID
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
2
globals.native.d.ts
vendored
@@ -23,6 +23,8 @@ interface IWindow {
|
||||
onerror: (event: string, source: any, lineno: any, colno: any, e: Error) => void;
|
||||
onunhandledrejection: (event: any) => void;
|
||||
|
||||
setInterval: typeof setInterval;
|
||||
clearInterval: typeof clearInterval;
|
||||
setTimeout: typeof setTimeout;
|
||||
clearTimeout: typeof clearTimeout;
|
||||
setImmediate: typeof setImmediate;
|
||||
|
||||
430
ios/Podfile.lock
@@ -13,14 +13,14 @@ PODS:
|
||||
- CocoaLumberjack/Core (= 3.7.2)
|
||||
- CocoaLumberjack/Core (3.7.2)
|
||||
- DoubleConversion (1.1.6)
|
||||
- FBLazyVector (0.68.4)
|
||||
- FBReactNativeSpec (0.68.4):
|
||||
- FBLazyVector (0.68.5)
|
||||
- FBReactNativeSpec (0.68.5):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTRequired (= 0.68.4)
|
||||
- RCTTypeSafety (= 0.68.4)
|
||||
- React-Core (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- ReactCommon/turbomodule/core (= 0.68.4)
|
||||
- RCTRequired (= 0.68.5)
|
||||
- RCTTypeSafety (= 0.68.5)
|
||||
- React-Core (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- ReactCommon/turbomodule/core (= 0.68.5)
|
||||
- Firebase/Analytics (8.15.0):
|
||||
- Firebase/Core
|
||||
- Firebase/Core (8.15.0):
|
||||
@@ -163,201 +163,201 @@ PODS:
|
||||
- DoubleConversion
|
||||
- fmt (~> 6.2.1)
|
||||
- glog
|
||||
- RCTRequired (0.68.4)
|
||||
- RCTTypeSafety (0.68.4):
|
||||
- FBLazyVector (= 0.68.4)
|
||||
- RCTRequired (0.68.5)
|
||||
- RCTTypeSafety (0.68.5):
|
||||
- FBLazyVector (= 0.68.5)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTRequired (= 0.68.4)
|
||||
- React-Core (= 0.68.4)
|
||||
- React (0.68.4):
|
||||
- React-Core (= 0.68.4)
|
||||
- React-Core/DevSupport (= 0.68.4)
|
||||
- React-Core/RCTWebSocket (= 0.68.4)
|
||||
- React-RCTActionSheet (= 0.68.4)
|
||||
- React-RCTAnimation (= 0.68.4)
|
||||
- React-RCTBlob (= 0.68.4)
|
||||
- React-RCTImage (= 0.68.4)
|
||||
- React-RCTLinking (= 0.68.4)
|
||||
- React-RCTNetwork (= 0.68.4)
|
||||
- React-RCTSettings (= 0.68.4)
|
||||
- React-RCTText (= 0.68.4)
|
||||
- React-RCTVibration (= 0.68.4)
|
||||
- React-callinvoker (0.68.4)
|
||||
- React-Codegen (0.68.4):
|
||||
- FBReactNativeSpec (= 0.68.4)
|
||||
- RCTRequired (= 0.68.5)
|
||||
- React-Core (= 0.68.5)
|
||||
- React (0.68.5):
|
||||
- React-Core (= 0.68.5)
|
||||
- React-Core/DevSupport (= 0.68.5)
|
||||
- React-Core/RCTWebSocket (= 0.68.5)
|
||||
- React-RCTActionSheet (= 0.68.5)
|
||||
- React-RCTAnimation (= 0.68.5)
|
||||
- React-RCTBlob (= 0.68.5)
|
||||
- React-RCTImage (= 0.68.5)
|
||||
- React-RCTLinking (= 0.68.5)
|
||||
- React-RCTNetwork (= 0.68.5)
|
||||
- React-RCTSettings (= 0.68.5)
|
||||
- React-RCTText (= 0.68.5)
|
||||
- React-RCTVibration (= 0.68.5)
|
||||
- React-callinvoker (0.68.5)
|
||||
- React-Codegen (0.68.5):
|
||||
- FBReactNativeSpec (= 0.68.5)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTRequired (= 0.68.4)
|
||||
- RCTTypeSafety (= 0.68.4)
|
||||
- React-Core (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- ReactCommon/turbomodule/core (= 0.68.4)
|
||||
- React-Core (0.68.4):
|
||||
- RCTRequired (= 0.68.5)
|
||||
- RCTTypeSafety (= 0.68.5)
|
||||
- React-Core (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- ReactCommon/turbomodule/core (= 0.68.5)
|
||||
- React-Core (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-Core/Default (= 0.68.5)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/CoreModulesHeaders (0.68.4):
|
||||
- React-Core/CoreModulesHeaders (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/Default (0.68.4):
|
||||
- React-Core/Default (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/DevSupport (0.68.4):
|
||||
- React-Core/DevSupport (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default (= 0.68.4)
|
||||
- React-Core/RCTWebSocket (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-jsinspector (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-Core/Default (= 0.68.5)
|
||||
- React-Core/RCTWebSocket (= 0.68.5)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-jsinspector (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/RCTActionSheetHeaders (0.68.4):
|
||||
- React-Core/RCTActionSheetHeaders (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/RCTAnimationHeaders (0.68.4):
|
||||
- React-Core/RCTAnimationHeaders (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/RCTBlobHeaders (0.68.4):
|
||||
- React-Core/RCTBlobHeaders (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/RCTImageHeaders (0.68.4):
|
||||
- React-Core/RCTImageHeaders (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/RCTLinkingHeaders (0.68.4):
|
||||
- React-Core/RCTLinkingHeaders (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/RCTNetworkHeaders (0.68.4):
|
||||
- React-Core/RCTNetworkHeaders (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/RCTSettingsHeaders (0.68.4):
|
||||
- React-Core/RCTSettingsHeaders (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/RCTTextHeaders (0.68.4):
|
||||
- React-Core/RCTTextHeaders (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/RCTVibrationHeaders (0.68.4):
|
||||
- React-Core/RCTVibrationHeaders (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-Core/RCTWebSocket (0.68.4):
|
||||
- React-Core/RCTWebSocket (0.68.5):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsiexecutor (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-Core/Default (= 0.68.5)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsiexecutor (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- Yoga
|
||||
- React-CoreModules (0.68.4):
|
||||
- React-CoreModules (0.68.5):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.68.4)
|
||||
- React-Codegen (= 0.68.4)
|
||||
- React-Core/CoreModulesHeaders (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-RCTImage (= 0.68.4)
|
||||
- ReactCommon/turbomodule/core (= 0.68.4)
|
||||
- React-cxxreact (0.68.4):
|
||||
- RCTTypeSafety (= 0.68.5)
|
||||
- React-Codegen (= 0.68.5)
|
||||
- React-Core/CoreModulesHeaders (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-RCTImage (= 0.68.5)
|
||||
- ReactCommon/turbomodule/core (= 0.68.5)
|
||||
- React-cxxreact (0.68.5):
|
||||
- boost (= 1.76.0)
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-callinvoker (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-jsinspector (= 0.68.4)
|
||||
- React-logger (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-runtimeexecutor (= 0.68.4)
|
||||
- React-jsi (0.68.4):
|
||||
- React-callinvoker (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-jsinspector (= 0.68.5)
|
||||
- React-logger (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- React-runtimeexecutor (= 0.68.5)
|
||||
- React-jsi (0.68.5):
|
||||
- boost (= 1.76.0)
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-jsi/Default (= 0.68.4)
|
||||
- React-jsi/Default (0.68.4):
|
||||
- React-jsi/Default (= 0.68.5)
|
||||
- React-jsi/Default (0.68.5):
|
||||
- boost (= 1.76.0)
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-jsiexecutor (0.68.4):
|
||||
- React-jsiexecutor (0.68.5):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-jsinspector (0.68.4)
|
||||
- React-logger (0.68.4):
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- React-jsinspector (0.68.5)
|
||||
- React-logger (0.68.5):
|
||||
- glog
|
||||
- react-native-background-timer (2.4.1):
|
||||
- React-Core
|
||||
@@ -390,71 +390,71 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-webview (11.15.1):
|
||||
- React-Core
|
||||
- React-perflogger (0.68.4)
|
||||
- React-RCTActionSheet (0.68.4):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.68.4)
|
||||
- React-RCTAnimation (0.68.4):
|
||||
- React-perflogger (0.68.5)
|
||||
- React-RCTActionSheet (0.68.5):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.68.5)
|
||||
- React-RCTAnimation (0.68.5):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.68.4)
|
||||
- React-Codegen (= 0.68.4)
|
||||
- React-Core/RCTAnimationHeaders (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- ReactCommon/turbomodule/core (= 0.68.4)
|
||||
- React-RCTBlob (0.68.4):
|
||||
- RCTTypeSafety (= 0.68.5)
|
||||
- React-Codegen (= 0.68.5)
|
||||
- React-Core/RCTAnimationHeaders (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- ReactCommon/turbomodule/core (= 0.68.5)
|
||||
- React-RCTBlob (0.68.5):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Codegen (= 0.68.4)
|
||||
- React-Core/RCTBlobHeaders (= 0.68.4)
|
||||
- React-Core/RCTWebSocket (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-RCTNetwork (= 0.68.4)
|
||||
- ReactCommon/turbomodule/core (= 0.68.4)
|
||||
- React-RCTImage (0.68.4):
|
||||
- React-Codegen (= 0.68.5)
|
||||
- React-Core/RCTBlobHeaders (= 0.68.5)
|
||||
- React-Core/RCTWebSocket (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-RCTNetwork (= 0.68.5)
|
||||
- ReactCommon/turbomodule/core (= 0.68.5)
|
||||
- React-RCTImage (0.68.5):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.68.4)
|
||||
- React-Codegen (= 0.68.4)
|
||||
- React-Core/RCTImageHeaders (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-RCTNetwork (= 0.68.4)
|
||||
- ReactCommon/turbomodule/core (= 0.68.4)
|
||||
- React-RCTLinking (0.68.4):
|
||||
- React-Codegen (= 0.68.4)
|
||||
- React-Core/RCTLinkingHeaders (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- ReactCommon/turbomodule/core (= 0.68.4)
|
||||
- React-RCTNetwork (0.68.4):
|
||||
- RCTTypeSafety (= 0.68.5)
|
||||
- React-Codegen (= 0.68.5)
|
||||
- React-Core/RCTImageHeaders (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-RCTNetwork (= 0.68.5)
|
||||
- ReactCommon/turbomodule/core (= 0.68.5)
|
||||
- React-RCTLinking (0.68.5):
|
||||
- React-Codegen (= 0.68.5)
|
||||
- React-Core/RCTLinkingHeaders (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- ReactCommon/turbomodule/core (= 0.68.5)
|
||||
- React-RCTNetwork (0.68.5):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.68.4)
|
||||
- React-Codegen (= 0.68.4)
|
||||
- React-Core/RCTNetworkHeaders (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- ReactCommon/turbomodule/core (= 0.68.4)
|
||||
- React-RCTSettings (0.68.4):
|
||||
- RCTTypeSafety (= 0.68.5)
|
||||
- React-Codegen (= 0.68.5)
|
||||
- React-Core/RCTNetworkHeaders (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- ReactCommon/turbomodule/core (= 0.68.5)
|
||||
- React-RCTSettings (0.68.5):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.68.4)
|
||||
- React-Codegen (= 0.68.4)
|
||||
- React-Core/RCTSettingsHeaders (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- ReactCommon/turbomodule/core (= 0.68.4)
|
||||
- React-RCTText (0.68.4):
|
||||
- React-Core/RCTTextHeaders (= 0.68.4)
|
||||
- React-RCTVibration (0.68.4):
|
||||
- RCTTypeSafety (= 0.68.5)
|
||||
- React-Codegen (= 0.68.5)
|
||||
- React-Core/RCTSettingsHeaders (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- ReactCommon/turbomodule/core (= 0.68.5)
|
||||
- React-RCTText (0.68.5):
|
||||
- React-Core/RCTTextHeaders (= 0.68.5)
|
||||
- React-RCTVibration (0.68.5):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Codegen (= 0.68.4)
|
||||
- React-Core/RCTVibrationHeaders (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- ReactCommon/turbomodule/core (= 0.68.4)
|
||||
- React-runtimeexecutor (0.68.4):
|
||||
- React-jsi (= 0.68.4)
|
||||
- ReactCommon/turbomodule/core (0.68.4):
|
||||
- React-Codegen (= 0.68.5)
|
||||
- React-Core/RCTVibrationHeaders (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- ReactCommon/turbomodule/core (= 0.68.5)
|
||||
- React-runtimeexecutor (0.68.5):
|
||||
- React-jsi (= 0.68.5)
|
||||
- ReactCommon/turbomodule/core (0.68.5):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-callinvoker (= 0.68.4)
|
||||
- React-Core (= 0.68.4)
|
||||
- React-cxxreact (= 0.68.4)
|
||||
- React-jsi (= 0.68.4)
|
||||
- React-logger (= 0.68.4)
|
||||
- React-perflogger (= 0.68.4)
|
||||
- React-callinvoker (= 0.68.5)
|
||||
- React-Core (= 0.68.5)
|
||||
- React-cxxreact (= 0.68.5)
|
||||
- React-jsi (= 0.68.5)
|
||||
- React-logger (= 0.68.5)
|
||||
- React-perflogger (= 0.68.5)
|
||||
- RNCalendarEvents (2.2.0):
|
||||
- React
|
||||
- RNCAsyncStorage (1.17.3):
|
||||
@@ -706,8 +706,8 @@ SPEC CHECKSUMS:
|
||||
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
||||
CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
|
||||
DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
|
||||
FBLazyVector: 023a2028f218d648b588348bfa9261b4914b93db
|
||||
FBReactNativeSpec: 9f4902cc009389d3704ff75de2aa513dee34d5c2
|
||||
FBLazyVector: 2b47ff52037bd9ae07cc9b051c9975797814b736
|
||||
FBReactNativeSpec: 0e0d384ef17a33b385f13f0c7f97702c7cd17858
|
||||
Firebase: 5f8193dff4b5b7c5d5ef72ae54bb76c08e2b841d
|
||||
FirebaseAnalytics: 7761cbadb00a717d8d0939363eb46041526474fa
|
||||
FirebaseCore: 5743c5785c074a794d35f2fff7ecc254a91e08b1
|
||||
@@ -732,18 +732,18 @@ SPEC CHECKSUMS:
|
||||
PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb
|
||||
PromisesSwift: 99fddfe4a0ec88a56486644c0da106694c92a604
|
||||
RCT-Folly: 4d8508a426467c48885f1151029bc15fa5d7b3b8
|
||||
RCTRequired: e6003505912d056f21f64465063cf4b79418f2b9
|
||||
RCTTypeSafety: d7ef4745c8d9c9faa65c26b4b6230fc5cd4c4424
|
||||
React: 6692c30fb74ab29078b25c31c9841d863e08cdd9
|
||||
React-callinvoker: fe2b234fa518d8bb7600707c536ab0a3e1f5edba
|
||||
React-Codegen: 9964bb2422c7014894182ac50068caae05f68551
|
||||
React-Core: a07bcd2f15ff93cddc9ceb07eddeec3d2ff8d990
|
||||
React-CoreModules: 7fb4ee0fc35ad2b7daf775f0ef6309efdd8d3d82
|
||||
React-cxxreact: 51a8058a35a2f02ad4175334a7cd24aa5558ced4
|
||||
React-jsi: 69b974b418d2658a3f1799903be7cbcb8ac59755
|
||||
React-jsiexecutor: 4f35a29798ba9d0d892a84001d11f626688dbb8e
|
||||
React-jsinspector: 6f75220cd4b6020976d340ab21c63458dd3cad9e
|
||||
React-logger: 7013d2499df6346e6a72802d4084badaaa82543b
|
||||
RCTRequired: 0f06b6068f530932d10e1a01a5352fad4eaacb74
|
||||
RCTTypeSafety: b0ee81f10ef1b7d977605a2b266823dabd565e65
|
||||
React: 3becd12bd51ea8a43bdde7e09d0f40fba7820e03
|
||||
React-callinvoker: 11abfff50e6bf7a55b3a90b4dc2187f71f224593
|
||||
React-Codegen: f8946ce0768fb8e92e092e30944489c4b2955b2d
|
||||
React-Core: 203cdb6ee2657b198d97d41031c249161060e6ca
|
||||
React-CoreModules: 6eb0c06a4a223fde2cb6a8d0f44f58b67e808942
|
||||
React-cxxreact: afb0c6c07d19adbd850747fedeac20c6832d40b9
|
||||
React-jsi: 14d37a6db2af2c1a49f6f5c2e4ee667c364ae45c
|
||||
React-jsiexecutor: 45c0496ca8cef6b02d9fa0274c25cf458fe91a56
|
||||
React-jsinspector: eb202e43b3879aba9a14f3f65788aec85d4e1ea9
|
||||
React-logger: 98f663b292a60967ebbc6d803ae96c1381183b6d
|
||||
react-native-background-timer: 17ea5e06803401a379ebf1f20505b793ac44d0fe
|
||||
react-native-get-random-values: 30b3f74ca34e30e2e480de48e4add2706a40ac8f
|
||||
react-native-keep-awake: afad8a51dfef9fe9655a6344771be32c8596d774
|
||||
@@ -757,18 +757,18 @@ SPEC CHECKSUMS:
|
||||
react-native-video: bb6f12a7198db53b261fefb5d609dc77417acc8b
|
||||
react-native-webrtc: 4a4c31be61f88d1d3356526eebce72f462a6760e
|
||||
react-native-webview: ea4899a1056c782afa96dd082179a66cbebf5504
|
||||
React-perflogger: 0b0500685176e53ea582c45179a653aa82e4ae49
|
||||
React-RCTActionSheet: 38469be9d20242f9c717e43c2983e8e3e6c640c4
|
||||
React-RCTAnimation: 93774f3e8857e7c3c1cbbd277056d02be4496be1
|
||||
React-RCTBlob: 6d0567d7a6561b62feb8c3b1cc33b3c591ba85ab
|
||||
React-RCTImage: 1006a91318a6181a0256b89d2e321b6ea0e2e6e3
|
||||
React-RCTLinking: 0b2300493c879c3bcac2d4c6b0178e8d0e5e2202
|
||||
React-RCTNetwork: b9a33a95703651abed92490e50396d54b7270a17
|
||||
React-RCTSettings: e6464123e5b5062fc23bb5adb51188a6061e9601
|
||||
React-RCTText: 188d6f0ae20cd28891f59ecad41028ee2f793757
|
||||
React-RCTVibration: a67beb7d2f3c73e9b74c4124ef61b84c601be649
|
||||
React-runtimeexecutor: 088723cf020113e64736a709f52719dbb359c73e
|
||||
ReactCommon: 1a4f19f3b4366feec03a98bdbb200b6085c5000f
|
||||
React-perflogger: 0458a87ea9a7342079e7a31b0d32b3734fb8415f
|
||||
React-RCTActionSheet: 22538001ea2926dea001111dd2846c13a0730bc9
|
||||
React-RCTAnimation: 732ce66878d4aa151d56a0d142b1105aa12fd313
|
||||
React-RCTBlob: 9cb9e3e9a41d27be34aaf89b0e0f52c7ca415d57
|
||||
React-RCTImage: 6bd16627eb9c4bb79903c4cdec7c551266ee1a5b
|
||||
React-RCTLinking: e9edfc8919c8fa9a3f3c7b34362811f58a2ebba4
|
||||
React-RCTNetwork: 880eccd21bbe2660a0b63da5ccba75c46eceeaa6
|
||||
React-RCTSettings: 8c85d8188c97d6c6bd470af6631a6c4555b79bb3
|
||||
React-RCTText: bbd275ee287730c5acbab1aadc0db39c25c5c64e
|
||||
React-RCTVibration: 9819a3bf6230e4b2a99877c21268b0b2416157a1
|
||||
React-runtimeexecutor: b1f1995089b90696dbc2a7ffe0059a80db5c8eb1
|
||||
ReactCommon: 149e2c0acab9bac61378da0db5b2880a1b5ff59b
|
||||
RNCalendarEvents: 7e65eb4a94f53c1744d1e275f7fafcfaa619f7a3
|
||||
RNCAsyncStorage: 005c0e2f09575360f142d0d1f1f15e4ec575b1af
|
||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
||||
@@ -781,7 +781,7 @@ SPEC CHECKSUMS:
|
||||
RNSound: 27e8268bdb0a1f191f219a33267f7e0445e8d62f
|
||||
RNSVG: f3b60aeeaa81960e2e0536c3a9eef50b667ef3a9
|
||||
RNWatch: dae6c858a2051dbdcfb00b9a86cf4d90400263b4
|
||||
Yoga: c926c8eec5c78a788b51e6c8a604825d00d694d7
|
||||
Yoga: c4d61225a466f250c35c1ee78d2d0b3d41fe661c
|
||||
|
||||
PODFILE CHECKSUM: e671cdcdb80fab67e305861c36bfae8ed5a5b0ef
|
||||
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
"mute": "Mute or unmute your microphone",
|
||||
"pushToTalk": "Press to transmit",
|
||||
"raiseHand": "Raise or lower your hand",
|
||||
"showSpeakerStats": "Show speaker stats",
|
||||
"showSpeakerStats": "Show participants stats",
|
||||
"toggleChat": "Open or close the chat",
|
||||
"toggleFilmstrip": "Show or hide video thumbnails",
|
||||
"toggleScreensharing": "Switch between camera and screen sharing",
|
||||
@@ -579,7 +579,7 @@
|
||||
"minutes": "{{count}}m",
|
||||
"name": "Name",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "Speaker Stats",
|
||||
"speakerStats": "Participants Stats",
|
||||
"speakerTime": "Speaker Time"
|
||||
},
|
||||
"startupoverlay": {
|
||||
@@ -626,7 +626,7 @@
|
||||
"sharedvideo": "Toggle video sharing",
|
||||
"shortcuts": "Toggle shortcuts",
|
||||
"show": "Show on stage",
|
||||
"speakerStats": "Toggle speaker statistics",
|
||||
"speakerStats": "Toggle participants statistics",
|
||||
"tileView": "Toggle tile view",
|
||||
"toggleCamera": "Toggle camera",
|
||||
"videoblur": "",
|
||||
@@ -662,7 +662,7 @@
|
||||
"shareRoom": "Invite someone",
|
||||
"sharedvideo": "Share video",
|
||||
"shortcuts": "View shortcuts",
|
||||
"speakerStats": "Speaker stats",
|
||||
"speakerStats": "Participants stats",
|
||||
"startScreenSharing": "Start screen sharing",
|
||||
"startSubtitles": "Start subtitles",
|
||||
"startvideoblur": "",
|
||||
|
||||
@@ -657,7 +657,7 @@
|
||||
"login": "Bejelentkezés",
|
||||
"logout": "Kijelentkezés",
|
||||
"lowerYourHand": "Kéz leengedése",
|
||||
"moreActions": "További műveltek",
|
||||
"moreActions": "További műveletek",
|
||||
"moreOptions": "További beállítások",
|
||||
"mute": "Némítás / Visszahangosítás",
|
||||
"muteEveryone": "Mindenki elnémítása",
|
||||
|
||||
@@ -511,7 +511,7 @@
|
||||
"mute": "Mute or unmute your microphone",
|
||||
"pushToTalk": "Push to talk",
|
||||
"raiseHand": "Raise or lower your hand",
|
||||
"showSpeakerStats": "Show speaker stats",
|
||||
"showSpeakerStats": "Show participants stats",
|
||||
"toggleChat": "Open or close the chat",
|
||||
"toggleFilmstrip": "Show or hide video thumbnails",
|
||||
"toggleParticipantsPane": "Show or hide the participants pane",
|
||||
@@ -649,6 +649,8 @@
|
||||
"connectedOneMember": "{{name}} joined the meeting",
|
||||
"connectedThreePlusMembers": "{{name}} and many others joined the meeting",
|
||||
"connectedTwoMembers": "{{first}} and {{second}} joined the meeting",
|
||||
"dataChannelClosed": "Video quality impaired",
|
||||
"dataChannelClosedDescription": "The bridge channel has been disconnected and thus video quality is limited to its lowest setting.",
|
||||
"disconnected": "disconnected",
|
||||
"displayNotifications": "Display notifications for",
|
||||
"focus": "Conference focus",
|
||||
@@ -1036,7 +1038,7 @@
|
||||
"sad": "Sad",
|
||||
"search": "Search",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "Speaker Stats",
|
||||
"speakerStats": "Participants Stats",
|
||||
"speakerTime": "Speaker Time",
|
||||
"surprised": "Surprised"
|
||||
},
|
||||
@@ -1117,7 +1119,7 @@
|
||||
"shortcuts": "Toggle shortcuts",
|
||||
"show": "Show on stage",
|
||||
"silence": "Silence",
|
||||
"speakerStats": "Toggle speaker statistics",
|
||||
"speakerStats": "Toggle participants statistics",
|
||||
"surprised": "Surprised",
|
||||
"tileView": "Toggle tile view",
|
||||
"toggleCamera": "Toggle camera",
|
||||
@@ -1204,7 +1206,7 @@
|
||||
"shortcuts": "View shortcuts",
|
||||
"showWhiteboard": "Show whiteboard",
|
||||
"silence": "Silence",
|
||||
"speakerStats": "Speaker stats",
|
||||
"speakerStats": "Participants stats",
|
||||
"startScreenSharing": "Start screen sharing",
|
||||
"startSubtitles": "Subtitles • {{language}}",
|
||||
"stopAudioSharing": "Stop audio sharing",
|
||||
|
||||
@@ -1450,6 +1450,22 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application that the data channel has been closed.
|
||||
*
|
||||
* @param {number} code - The close code.
|
||||
* @param {string} reason - The close reason.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyDataChannelClosed(code: number, reason: string) {
|
||||
this._sendEvent({
|
||||
name: 'data-channel-closed',
|
||||
code,
|
||||
reason
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application that the data channel has been opened.
|
||||
*
|
||||
@@ -1920,6 +1936,27 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the external application that a PeerConnection lost connectivity. This event is fired only if
|
||||
* a PC `failed` but connectivity to the rtcstats server is still maintained signaling that there is a
|
||||
* problem establishing a link between the app and the JVB server or the remote peer in case of P2P.
|
||||
* Will only fire if rtcstats is enabled.
|
||||
*
|
||||
* @param {boolean} isP2P - Type of PC.
|
||||
* @param {boolean} wasConnected - Was this connection previously connected. If it was it could mean
|
||||
* that connectivity was disrupted, if not it most likely means that the app could not reach
|
||||
* the JVB server, or the other peer in case of P2P.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyPeerConnectionFailure(isP2P, wasConnected) {
|
||||
this._sendEvent({
|
||||
name: 'peer-connection-failure',
|
||||
isP2P,
|
||||
wasConnected
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the allocated resources.
|
||||
*
|
||||
|
||||
2
modules/API/external/external_api.js
vendored
@@ -106,6 +106,7 @@ const events = {
|
||||
'camera-error': 'cameraError',
|
||||
'chat-updated': 'chatUpdated',
|
||||
'content-sharing-participants-changed': 'contentSharingParticipantsChanged',
|
||||
'data-channel-closed': 'dataChannelClosed',
|
||||
'data-channel-opened': 'dataChannelOpened',
|
||||
'device-list-changed': 'deviceListChanged',
|
||||
'display-name-change': 'displayNameChange',
|
||||
@@ -134,6 +135,7 @@ const events = {
|
||||
'participant-role-changed': 'participantRoleChanged',
|
||||
'participants-pane-toggled': 'participantsPaneToggled',
|
||||
'password-required': 'passwordRequired',
|
||||
'peer-connection-failure': 'peerConnectionFailure',
|
||||
'prejoin-screen-loaded': 'prejoinScreenLoaded',
|
||||
'proxy-connection-event': 'proxyConnectionEvent',
|
||||
'raise-hand-updated': 'raiseHandUpdated',
|
||||
|
||||
66
package-lock.json
generated
@@ -31,7 +31,7 @@
|
||||
"@jitsi/js-utils": "2.0.4",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
"@jitsi/rtcstats": "9.4.1",
|
||||
"@jitsi/rtcstats": "9.5.0",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"@microsoft/microsoft-graph-client": "3.0.1",
|
||||
"@mui/material": "5.10.2",
|
||||
@@ -55,7 +55,7 @@
|
||||
"@types/w3c-image-capture": "1.0.6",
|
||||
"@vladmandic/human": "2.6.5",
|
||||
"@vladmandic/human-models": "2.5.9",
|
||||
"@xmldom/xmldom": "0.7.6",
|
||||
"@xmldom/xmldom": "0.7.9",
|
||||
"amplitude-js": "8.2.1",
|
||||
"base64-js": "1.3.1",
|
||||
"bc-css-flags": "3.0.0",
|
||||
@@ -74,7 +74,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1535.0.0+e6263e7c/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1539.0.0+eb4873d2/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -88,7 +88,7 @@
|
||||
"react-focus-lock": "2.5.1",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "0.68.4",
|
||||
"react-native": "0.68.5",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-callstats": "3.73.7",
|
||||
@@ -147,7 +147,7 @@
|
||||
"@types/js-md5": "0.4.3",
|
||||
"@types/lodash": "4.14.182",
|
||||
"@types/react": "17.0.14",
|
||||
"@types/react-native": "0.68.7",
|
||||
"@types/react-native": "0.68.9",
|
||||
"@types/react-redux": "7.1.24",
|
||||
"@types/react-window": "1.8.5",
|
||||
"@types/unorm": "1.3.28",
|
||||
@@ -3780,9 +3780,9 @@
|
||||
"integrity": "sha512-JujivPbOUvdRYa2xqByHYKfKGNGa7ZPyNLaNuh8hEp9XsiNfjaJAHdboq6M1VY9TP+765nyxC0LjpAw1VkikOQ=="
|
||||
},
|
||||
"node_modules/@jitsi/rtcstats": {
|
||||
"version": "9.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.4.1.tgz",
|
||||
"integrity": "sha512-JrRBk9xLAnRgBP9aqTjR41DBAQYMkupOfy8XMIumdjxlDqf8dQygvYRc253xdHejr/kSHCvnaFoVIM3hHfeooQ==",
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.5.0.tgz",
|
||||
"integrity": "sha512-jKB+1IzKuqynA2etmWAA4uDFF0oAFUZWxRq+m+rOt8FfBp6pXojWbWA7xblcjxerj/3njGc8nEQbcK9qck1How==",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "^2.0.0",
|
||||
"sdp": "^3.0.3",
|
||||
@@ -6493,9 +6493,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-native": {
|
||||
"version": "0.68.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.68.7.tgz",
|
||||
"integrity": "sha512-icGr0/iTPLsvIrUoLvu5uluDFBMZEir+DbwgrHnvazO3fJv1C/kpZZLGplQ3noYyDLjuBynwOUgoaZpGyFI4Iw==",
|
||||
"version": "0.68.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.68.9.tgz",
|
||||
"integrity": "sha512-/1nbdoynVMjNsudurxHiEf9rSdviKAkV+pVgwpNk21v7gKJfzoHyT3nFunPuMoDgvvdZR5ofUrOJuCPbzF2GUw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "^17"
|
||||
@@ -7349,9 +7349,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.6.tgz",
|
||||
"integrity": "sha512-HHXP9hskkFQHy8QxxUXkS7946FFIhYVfGqsk0WLwllmexN9x/+R4UBLvurHEuyXRfVEObVR8APuQehykLviwSQ==",
|
||||
"version": "0.7.9",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.9.tgz",
|
||||
"integrity": "sha512-yceMpm/xd4W2a85iqZyO09gTnHvXF6pyiWjD2jcOJs7hRoZtNNOO1eJlhHj1ixA+xip2hOyGn+LgcvLCMo5zXA==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
@@ -13497,8 +13497,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1535.0.0+e6263e7c/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-RgMoesoWyscWi2fL9Hxp8PUwDlUtHbo+GhXosD3GeKR0zmihu/kxTONMUifGQnF8XdtcjaZfL2jCJynLwYKlkw==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1539.0.0+eb4873d2/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-hEu5nmljbOVKPHIcCpW6GTFzZpWDOAfplKTkNdvrIyNgMiIjHhUgmMzF+IGicd7KPud3z8aQ+0HFnpcxO/T6ZA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
@@ -16128,9 +16128,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native": {
|
||||
"version": "0.68.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.68.4.tgz",
|
||||
"integrity": "sha512-Hp5qwztQ1XNnV43QTz1kUx33iZHmJqbbe7L19V9psaWtX/h9j6SEtZ3UHBrigIPlppkIP1E5x3CDr9FdD4d6CA==",
|
||||
"version": "0.68.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.68.5.tgz",
|
||||
"integrity": "sha512-t3kiQ/gumFV+0r/NRSIGtYxanjY4da0utFqHgkMcRPJVwXFWC0Fr8YiOeRGYO1dp8EfrSsOjtfWic/inqVYlbQ==",
|
||||
"dependencies": {
|
||||
"@jest/create-cache-key-function": "^27.0.1",
|
||||
"@react-native-community/cli": "^7.0.3",
|
||||
@@ -23208,9 +23208,9 @@
|
||||
"integrity": "sha512-JujivPbOUvdRYa2xqByHYKfKGNGa7ZPyNLaNuh8hEp9XsiNfjaJAHdboq6M1VY9TP+765nyxC0LjpAw1VkikOQ=="
|
||||
},
|
||||
"@jitsi/rtcstats": {
|
||||
"version": "9.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.4.1.tgz",
|
||||
"integrity": "sha512-JrRBk9xLAnRgBP9aqTjR41DBAQYMkupOfy8XMIumdjxlDqf8dQygvYRc253xdHejr/kSHCvnaFoVIM3hHfeooQ==",
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.5.0.tgz",
|
||||
"integrity": "sha512-jKB+1IzKuqynA2etmWAA4uDFF0oAFUZWxRq+m+rOt8FfBp6pXojWbWA7xblcjxerj/3njGc8nEQbcK9qck1How==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "^2.0.0",
|
||||
"sdp": "^3.0.3",
|
||||
@@ -25178,9 +25178,9 @@
|
||||
}
|
||||
},
|
||||
"@types/react-native": {
|
||||
"version": "0.68.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.68.7.tgz",
|
||||
"integrity": "sha512-icGr0/iTPLsvIrUoLvu5uluDFBMZEir+DbwgrHnvazO3fJv1C/kpZZLGplQ3noYyDLjuBynwOUgoaZpGyFI4Iw==",
|
||||
"version": "0.68.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.68.9.tgz",
|
||||
"integrity": "sha512-/1nbdoynVMjNsudurxHiEf9rSdviKAkV+pVgwpNk21v7gKJfzoHyT3nFunPuMoDgvvdZR5ofUrOJuCPbzF2GUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "^17"
|
||||
@@ -25794,9 +25794,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@xmldom/xmldom": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.6.tgz",
|
||||
"integrity": "sha512-HHXP9hskkFQHy8QxxUXkS7946FFIhYVfGqsk0WLwllmexN9x/+R4UBLvurHEuyXRfVEObVR8APuQehykLviwSQ=="
|
||||
"version": "0.7.9",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.9.tgz",
|
||||
"integrity": "sha512-yceMpm/xd4W2a85iqZyO09gTnHvXF6pyiWjD2jcOJs7hRoZtNNOO1eJlhHj1ixA+xip2hOyGn+LgcvLCMo5zXA=="
|
||||
},
|
||||
"@xobotyi/scrollbar-width": {
|
||||
"version": "1.9.5",
|
||||
@@ -30510,8 +30510,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1535.0.0+e6263e7c/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-RgMoesoWyscWi2fL9Hxp8PUwDlUtHbo+GhXosD3GeKR0zmihu/kxTONMUifGQnF8XdtcjaZfL2jCJynLwYKlkw==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1539.0.0+eb4873d2/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-hEu5nmljbOVKPHIcCpW6GTFzZpWDOAfplKTkNdvrIyNgMiIjHhUgmMzF+IGicd7KPud3z8aQ+0HFnpcxO/T6ZA==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
@@ -32535,9 +32535,9 @@
|
||||
}
|
||||
},
|
||||
"react-native": {
|
||||
"version": "0.68.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.68.4.tgz",
|
||||
"integrity": "sha512-Hp5qwztQ1XNnV43QTz1kUx33iZHmJqbbe7L19V9psaWtX/h9j6SEtZ3UHBrigIPlppkIP1E5x3CDr9FdD4d6CA==",
|
||||
"version": "0.68.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.68.5.tgz",
|
||||
"integrity": "sha512-t3kiQ/gumFV+0r/NRSIGtYxanjY4da0utFqHgkMcRPJVwXFWC0Fr8YiOeRGYO1dp8EfrSsOjtfWic/inqVYlbQ==",
|
||||
"requires": {
|
||||
"@jest/create-cache-key-function": "^27.0.1",
|
||||
"@react-native-community/cli": "^7.0.3",
|
||||
|
||||
10
package.json
@@ -36,7 +36,7 @@
|
||||
"@jitsi/js-utils": "2.0.4",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
"@jitsi/rtcstats": "9.4.1",
|
||||
"@jitsi/rtcstats": "9.5.0",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"@microsoft/microsoft-graph-client": "3.0.1",
|
||||
"@mui/material": "5.10.2",
|
||||
@@ -60,7 +60,7 @@
|
||||
"@types/w3c-image-capture": "1.0.6",
|
||||
"@vladmandic/human": "2.6.5",
|
||||
"@vladmandic/human-models": "2.5.9",
|
||||
"@xmldom/xmldom": "0.7.6",
|
||||
"@xmldom/xmldom": "0.7.9",
|
||||
"amplitude-js": "8.2.1",
|
||||
"base64-js": "1.3.1",
|
||||
"bc-css-flags": "3.0.0",
|
||||
@@ -79,7 +79,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1535.0.0+e6263e7c/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1539.0.0+eb4873d2/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -93,7 +93,7 @@
|
||||
"react-focus-lock": "2.5.1",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "0.68.4",
|
||||
"react-native": "0.68.5",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-callstats": "3.73.7",
|
||||
@@ -152,7 +152,7 @@
|
||||
"@types/js-md5": "0.4.3",
|
||||
"@types/lodash": "4.14.182",
|
||||
"@types/react": "17.0.14",
|
||||
"@types/react-native": "0.68.7",
|
||||
"@types/react-native": "0.68.9",
|
||||
"@types/react-redux": "7.1.24",
|
||||
"@types/react-window": "1.8.5",
|
||||
"@types/unorm": "1.3.28",
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { ColorPalette, getRGBAFormat } from '../styles';
|
||||
|
||||
/**
|
||||
@@ -14,21 +12,6 @@ export default {
|
||||
icon: 'rgb(28, 32, 37)',
|
||||
text: 'rgb(28, 32, 37)'
|
||||
},
|
||||
'Chat': {
|
||||
displayName: 'rgb(94, 109, 121)',
|
||||
localMsgBackground: 'rgb(215, 230, 249)',
|
||||
lobbyMsgBackground: 'rgb(106, 80, 211)',
|
||||
lobbyMsgNotice: 'rgb(16, 10, 41)',
|
||||
privateMsgBackground: 'rgb(250, 219, 219)',
|
||||
privateMsgNotice: 'rgb(186, 39, 58)',
|
||||
remoteMsgBackground: 'rgb(241, 242, 246)',
|
||||
replyBorder: 'rgb(219, 197, 200)',
|
||||
replyIcon: 'rgb(94, 109, 121)'
|
||||
},
|
||||
'Conference': {
|
||||
inviteButtonBackground: 'rgb(0, 119, 225)',
|
||||
onVideoText: 'white'
|
||||
},
|
||||
'Dialog': {},
|
||||
'Header': {
|
||||
background: ColorPalette.blue,
|
||||
|
||||
@@ -138,6 +138,18 @@ export const CONFERENCE_WILL_LEAVE = 'CONFERENCE_WILL_LEAVE';
|
||||
*/
|
||||
export const DATA_CHANNEL_OPENED = 'DATA_CHANNEL_OPENED';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that the data channel with the
|
||||
* bridge has been closed.
|
||||
*
|
||||
* {
|
||||
* type: DATA_CHANNEL_CLOSED,
|
||||
* code: number,
|
||||
* reason: string
|
||||
* }
|
||||
*/
|
||||
export const DATA_CHANNEL_CLOSED = 'DATA_CHANNEL_CLOSED';
|
||||
|
||||
/**
|
||||
* The type of action which signals that the user has been kicked out from
|
||||
* the conference.
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
CONFERENCE_UNIQUE_ID_SET,
|
||||
CONFERENCE_WILL_JOIN,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
DATA_CHANNEL_CLOSED,
|
||||
DATA_CHANNEL_OPENED,
|
||||
E2E_RTT_CHANGED,
|
||||
KICKED_OUT,
|
||||
@@ -581,6 +582,26 @@ export function dataChannelOpened() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals the data channel with the bridge was abruptly closed.
|
||||
*
|
||||
* @param {number} code - Close code.
|
||||
* @param {string} reason - Close reason.
|
||||
*
|
||||
* @returns {{
|
||||
* type: DATA_CHANNEL_CLOSED,
|
||||
* code: number,
|
||||
* reason: string
|
||||
* }}
|
||||
*/
|
||||
export function dataChannelClosed(code: number, reason: string) {
|
||||
return {
|
||||
type: DATA_CHANNEL_CLOSED,
|
||||
code,
|
||||
reason
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to end a conference for all participants.
|
||||
*
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { FaceLandmarks } from '../../face-landmarks/types';
|
||||
import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../room-lock/constants';
|
||||
import { ISpeakerStats } from '../../speaker-stats/reducer';
|
||||
import { CONNECTION_WILL_CONNECT, SET_LOCATION_URL } from '../connection/actionTypes';
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
import ReducerRegistry from '../redux/ReducerRegistry';
|
||||
@@ -53,6 +55,7 @@ export interface IJitsiConference {
|
||||
getMeetingUniqueId: Function;
|
||||
getParticipantById: Function;
|
||||
getParticipants: Function;
|
||||
getSpeakerStats: () => ISpeakerStats;
|
||||
grantOwner: Function;
|
||||
isAVModerationSupported: Function;
|
||||
isCallstatsEnabled: Function;
|
||||
@@ -74,6 +77,7 @@ export interface IJitsiConference {
|
||||
sendCommand: Function;
|
||||
sendCommandOnce: Function;
|
||||
sendEndpointMessage: Function;
|
||||
sendFaceLandmarks: (faceLandmarks: FaceLandmarks) => void;
|
||||
sendFeedback: Function;
|
||||
sendLobbyMessage: Function;
|
||||
sessionId: string;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AnyAction } from 'redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { getFeatureFlag } from '../flags/functions';
|
||||
import Platform from '../react/Platform';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { updateSettings } from '../settings/actions';
|
||||
|
||||
@@ -52,7 +53,7 @@ function _setConfig({ dispatch, getState }: IStore, next: Function, action: AnyA
|
||||
const settings = state['features/base/settings'];
|
||||
const config: IConfig = {};
|
||||
|
||||
if (typeof settings.disableP2P !== 'undefined') {
|
||||
if (Platform.OS !== 'android' && typeof settings.disableP2P !== 'undefined') {
|
||||
config.p2p = { enabled: !settings.disableP2P };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { CONFERENCE_INFO } from '../../conference/components/constants';
|
||||
import Platform from '../react/Platform';
|
||||
import ReducerRegistry from '../redux/ReducerRegistry';
|
||||
import { equals } from '../redux/functions';
|
||||
|
||||
@@ -48,6 +49,8 @@ const INITIAL_RN_STATE: IConfig = {
|
||||
disableAudioLevels: true,
|
||||
|
||||
p2p: {
|
||||
// Temporarily disable P2P on Android while we sort out some (codec?) issues.
|
||||
...(Platform.OS === 'android' ? { enabled: false } : {}), // eslint-disable-line no-extra-parens
|
||||
preferredCodec: 'h264'
|
||||
},
|
||||
|
||||
|
||||
10
react/features/base/icons/svg/emotions-angry.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="10" fill="url(#paint0_radial_72_1897)"/>
|
||||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M5.46845 5.14411C5.08781 4.88541 4.56951 4.98428 4.31081 5.36492C4.05212 5.74557 4.15098 6.26387 4.53163 6.52257L5.47766 7.16551C5.18224 7.46624 5.00002 7.87855 5.00002 8.33341C5.00002 9.25388 5.74622 10.0001 6.66669 10.0001C7.58716 10.0001 8.33336 9.25388 8.33336 8.33341C8.33336 8.23415 8.32468 8.13691 8.30804 8.04242C8.54426 7.66462 8.44124 7.16449 8.06956 6.91188L5.46845 5.14411ZM6.66305 14.7842C6.30373 14.5781 6.1795 14.1198 6.38556 13.7605C6.75032 13.1244 7.27645 12.5959 7.91081 12.2283C8.54518 11.8607 9.26532 11.6669 9.99852 11.6667C10.7317 11.6664 11.452 11.8596 12.0866 12.2268C12.7213 12.5939 13.2478 13.1221 13.613 13.7578C13.8193 14.117 13.6954 14.5754 13.3362 14.7818C12.9771 14.9881 12.5186 14.8642 12.3123 14.505C12.0786 14.0981 11.7416 13.7601 11.3354 13.5251C10.9293 13.2901 10.4683 13.1665 9.99906 13.1667C9.52981 13.1668 9.06892 13.2908 8.66293 13.5261C8.25693 13.7614 7.92021 14.0996 7.68677 14.5067C7.4807 14.866 7.02237 14.9902 6.66305 14.7842ZM15.7903 5.36492C15.5316 4.98428 15.0134 4.88541 14.6327 5.14411L12.0316 6.91188C11.7043 7.13434 11.5853 7.54876 11.7229 7.90254C11.6862 8.03998 11.6667 8.18441 11.6667 8.33341C11.6667 9.25388 12.4129 10.0001 13.3334 10.0001C14.2538 10.0001 15 9.25388 15 8.33341C15 7.89926 14.834 7.50388 14.562 7.20728L15.5695 6.52257C15.9502 6.26387 16.049 5.74557 15.7903 5.36492Z" fill="black"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_72_1897" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10 4.58333) rotate(90) scale(15.4167)">
|
||||
<stop offset="0.359375" stop-color="#F26325"/>
|
||||
<stop offset="1" stop-color="#F24A25"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
10
react/features/base/icons/svg/emotions-disgusted.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="10" fill="url(#paint0_radial_351_6183)"/>
|
||||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M4.16669 7.50001C4.16669 7.03977 4.53978 6.66667 5.00002 6.66667H7.50002C7.96026 6.66667 8.33335 7.03977 8.33335 7.50001C8.33335 7.96024 7.96026 8.33334 7.50002 8.33334H5.00002C4.53978 8.33334 4.16669 7.96024 4.16669 7.50001ZM6.66669 15C6.66669 13.1591 8.15907 11.6667 10 11.6667C11.841 11.6667 13.3334 13.1591 13.3334 15H6.66669ZM12.5 6.66667C12.0398 6.66667 11.6667 7.03977 11.6667 7.50001C11.6667 7.96024 12.0398 8.33334 12.5 8.33334H15C15.4603 8.33334 15.8334 7.96024 15.8334 7.50001C15.8334 7.03977 15.4603 6.66667 15 6.66667H12.5Z" fill="black"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_351_6183" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10 4.58333) rotate(90) scale(15.4167)">
|
||||
<stop offset="0.359375" stop-color="#98E791"/>
|
||||
<stop offset="1" stop-color="#3C9845"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
10
react/features/base/icons/svg/emotions-fearful.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="10" fill="url(#paint0_radial_72_1884)"/>
|
||||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M8.33333 7.49999C8.33333 8.42047 7.58714 9.16666 6.66667 9.16666C5.74619 9.16666 5 8.42047 5 7.49999C5 6.57952 5.74619 5.83333 6.66667 5.83333C7.58714 5.83333 8.33333 6.57952 8.33333 7.49999ZM15 7.49999C15 8.42047 14.2538 9.16666 13.3333 9.16666C12.4129 9.16666 11.6667 8.42047 11.6667 7.49999C11.6667 6.57952 12.4129 5.83333 13.3333 5.83333C14.2538 5.83333 15 6.57952 15 7.49999ZM10 11.6667C8.15905 11.6667 6.66667 13.159 6.66667 15H13.3333C13.3333 13.159 11.8409 11.6667 10 11.6667Z" fill="black"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_72_1884" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10 4.58333) rotate(90) scale(15.4167)">
|
||||
<stop offset="0.359375" stop-color="#6BEBD4"/>
|
||||
<stop offset="1" stop-color="#077EA4"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1018 B |
10
react/features/base/icons/svg/emotions-happy.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="10" fill="url(#paint0_radial_72_1844)"/>
|
||||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M8.33333 7.49999C8.33333 8.42047 7.58714 9.16666 6.66667 9.16666C5.74619 9.16666 5 8.42047 5 7.49999C5 6.57952 5.74619 5.83333 6.66667 5.83333C7.58714 5.83333 8.33333 6.57952 8.33333 7.49999ZM15 7.49999C15 8.42047 14.2538 9.16666 13.3333 9.16666C12.4129 9.16666 11.6667 8.42047 11.6667 7.49999C11.6667 6.57952 12.4129 5.83333 13.3333 5.83333C14.2538 5.83333 15 6.57952 15 7.49999ZM7.53238 12.6776C7.37535 12.2943 6.93734 12.1109 6.55404 12.2679C6.17075 12.4249 5.98732 12.8629 6.14435 13.2462C6.45676 14.0088 6.98828 14.6616 7.6717 15.1221C8.35513 15.5826 9.15976 15.8301 9.98384 15.8333C10.8079 15.8365 11.6144 15.5953 12.3014 15.1401C12.9884 14.6849 13.525 14.0362 13.8433 13.2761C14.0033 12.894 13.8233 12.4546 13.4412 12.2946C13.0591 12.1346 12.6197 12.3146 12.4597 12.6967C12.256 13.1832 11.9126 13.5983 11.4729 13.8896C11.0332 14.181 10.5171 14.3354 9.98966 14.3333C9.46224 14.3313 8.94728 14.1729 8.50989 13.8782C8.0725 13.5834 7.73232 13.1656 7.53238 12.6776Z" fill="black"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_72_1844" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10 4.58333) rotate(90) scale(15.4167)">
|
||||
<stop offset="0.359375" stop-color="#F2AD25"/>
|
||||
<stop offset="1" stop-color="#F27B25"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
10
react/features/base/icons/svg/emotions-neutral.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="10" fill="url(#paint0_radial_72_1850)"/>
|
||||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M8.33333 7.49999C8.33333 8.42047 7.58714 9.16666 6.66667 9.16666C5.74619 9.16666 5 8.42047 5 7.49999C5 6.57952 5.74619 5.83333 6.66667 5.83333C7.58714 5.83333 8.33333 6.57952 8.33333 7.49999ZM15 7.49999C15 8.42047 14.2538 9.16666 13.3333 9.16666C12.4129 9.16666 11.6667 8.42047 11.6667 7.49999C11.6667 6.57952 12.4129 5.83333 13.3333 5.83333C14.2538 5.83333 15 6.57952 15 7.49999ZM7.5 13.3333C7.03976 13.3333 6.66667 13.7064 6.66667 14.1667C6.66667 14.6269 7.03976 15 7.5 15H12.5C12.9602 15 13.3333 14.6269 13.3333 14.1667C13.3333 13.7064 12.9602 13.3333 12.5 13.3333H7.5Z" fill="black"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_72_1850" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10 4.58333) rotate(90) scale(15.4167)">
|
||||
<stop offset="0.359375" stop-color="#AAAAAA"/>
|
||||
<stop offset="1" stop-color="#5E5E5E"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
10
react/features/base/icons/svg/emotions-sad.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="10" fill="url(#paint0_radial_72_1862)"/>
|
||||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M8.33333 7.49999C8.33333 8.42047 7.58714 9.16666 6.66667 9.16666C5.74619 9.16666 5 8.42047 5 7.49999C5 6.57952 5.74619 5.83333 6.66667 5.83333C7.58714 5.83333 8.33333 6.57952 8.33333 7.49999ZM15 7.49999C15 8.42047 14.2538 9.16666 13.3333 9.16666C12.4129 9.16666 11.6667 8.42047 11.6667 7.49999C11.6667 6.57952 12.4129 5.83333 13.3333 5.83333C14.2538 5.83333 15 6.57952 15 7.49999ZM6.38554 13.7605C6.17948 14.1198 6.30371 14.5781 6.66303 14.7842C7.02235 14.9902 7.48068 14.866 7.68675 14.5067C7.92019 14.0996 8.25691 13.7614 8.66291 13.5261C9.0689 13.2908 9.52979 13.1668 9.99904 13.1667C10.4683 13.1665 10.9293 13.2901 11.3354 13.5251C11.7416 13.7601 12.0786 14.0981 12.3123 14.505C12.5186 14.8642 12.977 14.9881 13.3362 14.7818C13.6954 14.5754 13.8193 14.117 13.613 13.7578C13.2477 13.1221 12.7212 12.5939 12.0866 12.2268C11.452 11.8596 10.7317 11.6664 9.9985 11.6667C9.2653 11.6669 8.54516 11.8607 7.91079 12.2283C7.27643 12.5959 6.7503 13.1244 6.38554 13.7605Z" fill="black"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_72_1862" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10 4.58333) rotate(90) scale(15.4167)">
|
||||
<stop offset="0.359375" stop-color="#65B3FB"/>
|
||||
<stop offset="1" stop-color="#256BF2"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
10
react/features/base/icons/svg/emotions-surprised.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="10" fill="url(#paint0_radial_72_1873)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.33333 7.49999C8.33333 8.42047 7.58714 9.16666 6.66667 9.16666C5.74619 9.16666 5 8.42047 5 7.49999C5 6.57952 5.74619 5.83333 6.66667 5.83333C7.58714 5.83333 8.33333 6.57952 8.33333 7.49999ZM15 7.49999C15 8.42047 14.2538 9.16666 13.3333 9.16666C12.4129 9.16666 11.6667 8.42047 11.6667 7.49999C11.6667 6.57952 12.4129 5.83333 13.3333 5.83333C14.2538 5.83333 15 6.57952 15 7.49999ZM10 15C11.3807 15 12.5 14.403 12.5 13.6667C12.5 12.9303 11.3807 11.6667 10 11.6667C8.61929 11.6667 7.5 12.9303 7.5 13.6667C7.5 14.403 8.61929 15 10 15Z" fill="black"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_72_1873" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10 4.58333) rotate(90) scale(15.4167)">
|
||||
<stop offset="0.359375" stop-color="#CC86E4"/>
|
||||
<stop offset="1" stop-color="#933CD8"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -29,6 +29,13 @@ export { default as IconE2EE } from './e2ee.svg';
|
||||
export { default as IconEnlarge } from './enlarge.svg';
|
||||
export { default as IconEnterFullscreen } from './enter-fullscreen.svg';
|
||||
export { default as IconEnvelope } from './envelope.svg';
|
||||
export { default as IconEmotionsAngry } from './emotions-angry.svg';
|
||||
export { default as IconEmotionsDisgusted } from './emotions-disgusted.svg';
|
||||
export { default as IconEmotionsFearful } from './emotions-fearful.svg';
|
||||
export { default as IconEmotionsHappy } from './emotions-happy.svg';
|
||||
export { default as IconEmotionsNeutral } from './emotions-neutral.svg';
|
||||
export { default as IconEmotionsSad } from './emotions-sad.svg';
|
||||
export { default as IconEmotionsSurprised } from './emotions-surprised.svg';
|
||||
export { default as IconExclamationSolid } from './exclamation-solid.svg';
|
||||
export { default as IconExclamationTriangle } from './exclamation-triangle.svg';
|
||||
export { default as IconExitFullscreen } from './exit-fullscreen.svg';
|
||||
|
||||
@@ -53,9 +53,9 @@ const JitsiKeyboardAvoidingView = (
|
||||
addBottomPadding = true,
|
||||
children,
|
||||
contentContainerStyle,
|
||||
disableForcedKeyboardDismiss,
|
||||
hasTabNavigator,
|
||||
hasBottomTextInput,
|
||||
disableForcedKeyboardDismiss,
|
||||
style
|
||||
}: Props) => {
|
||||
const headerHeight = useHeaderHeight();
|
||||
|
||||
@@ -60,10 +60,10 @@ const JitsiScreen = ({
|
||||
addBottomPadding,
|
||||
contentContainerStyle,
|
||||
children,
|
||||
disableForcedKeyboardDismiss = false,
|
||||
footerComponent,
|
||||
hasTabNavigator = false,
|
||||
hasBottomTextInput = false,
|
||||
disableForcedKeyboardDismiss = false,
|
||||
safeAreaInsets = [ 'left', 'right' ],
|
||||
style
|
||||
}: Props) => {
|
||||
|
||||
@@ -19,7 +19,12 @@ type Props = {
|
||||
/**
|
||||
* The extra styles to be applied to links.
|
||||
*/
|
||||
linkStyle: StyleType
|
||||
linkStyle: StyleType,
|
||||
|
||||
/**
|
||||
* The extra styles to be applied to text.
|
||||
*/
|
||||
style?: StyleType
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -46,7 +51,9 @@ export default class Linkify extends Component<Props> {
|
||||
return (
|
||||
<ReactLinkify
|
||||
componentDecorator = { this._componentDecorator }>
|
||||
<Text selectable = { true }>
|
||||
<Text
|
||||
selectable = { true }
|
||||
style = { this.props.style }>
|
||||
{ this.props.children }
|
||||
</Text>
|
||||
</ReactLinkify>
|
||||
|
||||
@@ -66,12 +66,15 @@ const Button: React.FC<IProps> = ({
|
||||
}
|
||||
|
||||
if (type === TERTIARY) {
|
||||
buttonLabelStyles
|
||||
= disabled ? styles.buttonLabelTertiaryDisabled : styles.buttonLabelTertiary;
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
accessibilityLabel = { accessibilityLabel }
|
||||
disabled = { disabled }
|
||||
onPress = { onPress }
|
||||
rippleColor = 'transparent'
|
||||
rippleColor = { BaseTheme.palette.action03Active }
|
||||
style = { [
|
||||
buttonStyles,
|
||||
style
|
||||
|
||||
@@ -23,15 +23,15 @@ export default {
|
||||
...buttonLabel
|
||||
},
|
||||
|
||||
buttonContent: {
|
||||
height: BUTTON_HEIGHT
|
||||
},
|
||||
|
||||
buttonLabelDisabled: {
|
||||
...buttonLabel,
|
||||
color: BaseTheme.palette.text03
|
||||
},
|
||||
|
||||
buttonContent: {
|
||||
height: BUTTON_HEIGHT
|
||||
},
|
||||
|
||||
buttonDisabled: {
|
||||
...button,
|
||||
backgroundColor: BaseTheme.palette.actionDisabled
|
||||
@@ -54,6 +54,13 @@ export default {
|
||||
|
||||
buttonLabelTertiary: {
|
||||
...buttonLabel,
|
||||
color: BaseTheme.palette.text01
|
||||
color: BaseTheme.palette.text01,
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
buttonLabelTertiaryDisabled: {
|
||||
...buttonLabel,
|
||||
color: BaseTheme.palette.text03,
|
||||
textAlign: 'center'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import Icon from '../../../icons/components/Icon';
|
||||
|
||||
interface IProps {
|
||||
accessibilityLabel: string;
|
||||
className?: string;
|
||||
icon: Function;
|
||||
id?: string;
|
||||
onClick: () => void;
|
||||
@@ -40,14 +41,14 @@ const useStyles = makeStyles()(theme => {
|
||||
};
|
||||
});
|
||||
|
||||
const ClickableIcon = ({ accessibilityLabel, icon, id, onClick }: IProps) => {
|
||||
const ClickableIcon = ({ accessibilityLabel, className, icon, id, onClick }: IProps) => {
|
||||
const { classes: styles, cx } = useStyles();
|
||||
const isMobile = isMobileBrowser();
|
||||
|
||||
return (
|
||||
<button
|
||||
aria-label = { accessibilityLabel }
|
||||
className = { cx(styles.button, isMobile && 'is-mobile') }
|
||||
className = { cx(styles.button, isMobile && 'is-mobile', className) }
|
||||
id = { id }
|
||||
onClick = { onClick }>
|
||||
<Icon
|
||||
|
||||
@@ -67,7 +67,7 @@ const useStyles = makeStyles()(theme => {
|
||||
flexDirection: 'column',
|
||||
height: 'auto',
|
||||
minHeight: '200px',
|
||||
maxHeight: '560px',
|
||||
maxHeight: '760px',
|
||||
marginTop: '64px',
|
||||
animation: `${keyframes`
|
||||
0% {
|
||||
@@ -137,6 +137,12 @@ const useStyles = makeStyles()(theme => {
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
|
||||
closeIcon: {
|
||||
'&:focus': {
|
||||
boxShadow: 'none'
|
||||
}
|
||||
},
|
||||
|
||||
title: {
|
||||
color: theme.palette.text01,
|
||||
...withPixelLineHeight(theme.typography.heading5),
|
||||
@@ -278,6 +284,7 @@ const Dialog = ({
|
||||
{!hideCloseButton && (
|
||||
<ClickableIcon
|
||||
accessibilityLabel = { t('dialog.close') }
|
||||
className = { classes.closeIcon }
|
||||
icon = { IconCloseLarge }
|
||||
id = 'modal-header-close-button'
|
||||
onClick = { onClose } />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @flow
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
import React, { useEffect } from 'react';
|
||||
@@ -6,7 +6,8 @@ import React, { useEffect } from 'react';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { closeChat } from '../../actions.any';
|
||||
import { TabBarLabelCounter } from '../../../mobile/navigation/components/TabBarLabelCounter';
|
||||
import { closeChat } from '../../actions.native';
|
||||
import AbstractChat, {
|
||||
type Props as AbstractProps,
|
||||
_mapStateToProps
|
||||
@@ -17,14 +18,8 @@ import MessageContainer from './MessageContainer';
|
||||
import MessageRecipient from './MessageRecipient';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* Is this screen focused or not(React Navigation).
|
||||
*/
|
||||
isChatScreenFocused: boolean,
|
||||
|
||||
/**
|
||||
* Default prop for navigating between screen components(React Navigation).
|
||||
*/
|
||||
@@ -41,7 +36,6 @@ type Props = AbstractProps & {
|
||||
* the mobile client.
|
||||
*/
|
||||
class Chat extends AbstractChat<Props> {
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -49,7 +43,7 @@ class Chat extends AbstractChat<Props> {
|
||||
*/
|
||||
render() {
|
||||
const { _messages, route } = this.props;
|
||||
const privateMessageRecipient = route.params?.privateMessageRecipient;
|
||||
const privateMessageRecipient = route?.params?.privateMessageRecipient;
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
@@ -68,28 +62,26 @@ class Chat extends AbstractChat<Props> {
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(props => {
|
||||
const {
|
||||
_nbUnreadMessages,
|
||||
navigation,
|
||||
t
|
||||
} = props;
|
||||
const isChatScreenFocused = useIsFocused();
|
||||
const { _nbUnreadMessages, dispatch, navigation, t } = props;
|
||||
const unreadMessagesNr = _nbUnreadMessages > 0;
|
||||
|
||||
const nrUnreadMessages
|
||||
= !isChatScreenFocused && _nbUnreadMessages > 0
|
||||
? `(${_nbUnreadMessages})` : '';
|
||||
const isFocused = useIsFocused();
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
tabBarLabel: `${t('chat.tabs.chat')} ${nrUnreadMessages}`
|
||||
navigation?.setOptions({
|
||||
tabBarLabel: () => (
|
||||
<TabBarLabelCounter
|
||||
activeUnreadNr = { unreadMessagesNr }
|
||||
isFocused = { isFocused }
|
||||
label = { t('chat.tabs.chat') }
|
||||
nbUnread = { _nbUnreadMessages } />
|
||||
)
|
||||
});
|
||||
|
||||
return () => props.dispatch(closeChat());
|
||||
}, [ nrUnreadMessages ]);
|
||||
return () => isFocused && dispatch(closeChat());
|
||||
}, [ isFocused, _nbUnreadMessages ]);
|
||||
|
||||
return (
|
||||
<Chat
|
||||
{ ...props }
|
||||
isChatScreenFocused = { isChatScreenFocused } />
|
||||
<Chat { ...props } />
|
||||
);
|
||||
}));
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { CHAT_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconChatUnread, IconMessage } from '../../../base/icons';
|
||||
@@ -10,9 +8,9 @@ import {
|
||||
} from '../../../base/toolbox/components';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { getUnreadPollCount } from '../../../polls/functions';
|
||||
import { getUnreadCount } from '../../functions';
|
||||
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
@@ -72,7 +70,9 @@ function _mapStateToProps(state, ownProps) {
|
||||
|
||||
return {
|
||||
_isPollsDisabled: disablePolls,
|
||||
_unreadMessageCount: getUnreadCount(state),
|
||||
|
||||
// The toggled icon should also be available for new polls
|
||||
_unreadMessageCount: getUnreadCount(state) || getUnreadPollCount(state),
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class ChatInputBar extends Component<Props, State> {
|
||||
] }>
|
||||
<Input
|
||||
blurOnSubmit = { false }
|
||||
customStyles = {{ input: styles.customInput }}
|
||||
customStyles = {{ container: styles.customInputContainer }}
|
||||
multiline = { false }
|
||||
onBlur = { this._onFocused(false) }
|
||||
onChange = { this._onChangeText }
|
||||
|
||||
@@ -1,30 +1,19 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Linkify } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { type StyleType } from '../../../base/styles';
|
||||
import { isGifMessage } from '../../../gifs/functions';
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
import { replaceNonUnicodeEmojis } from '../../functions';
|
||||
import AbstractChatMessage, { type Props as AbstractProps } from '../AbstractChatMessage';
|
||||
import AbstractChatMessage, { type Props } from '../AbstractChatMessage';
|
||||
|
||||
import GifMessage from './GifMessage';
|
||||
import PrivateMessageButton from './PrivateMessageButton';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
@@ -36,7 +25,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _styles, message, knocking } = this.props;
|
||||
const { message, knocking } = this.props;
|
||||
const localMessage = message.messageType === MESSAGE_TYPE_LOCAL;
|
||||
const { privateMessage, lobbyChat } = message;
|
||||
|
||||
@@ -56,7 +45,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper);
|
||||
|
||||
// The bubble needs some additional styling
|
||||
messageBubbleStyle.push(_styles.localMessageBubble);
|
||||
messageBubbleStyle.push(styles.localMessageBubble);
|
||||
} else if (message.messageType === MESSAGE_TYPE_ERROR) {
|
||||
// This is a system message.
|
||||
|
||||
@@ -66,15 +55,15 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
// This is a remote message sent by a remote participant.
|
||||
|
||||
// The bubble needs some additional styling
|
||||
messageBubbleStyle.push(_styles.remoteMessageBubble);
|
||||
messageBubbleStyle.push(styles.remoteMessageBubble);
|
||||
}
|
||||
|
||||
if (privateMessage) {
|
||||
messageBubbleStyle.push(_styles.privateMessageBubble);
|
||||
messageBubbleStyle.push(styles.privateMessageBubble);
|
||||
}
|
||||
|
||||
if (lobbyChat && !knocking) {
|
||||
messageBubbleStyle.push(_styles.lobbyMessageBubble);
|
||||
messageBubbleStyle.push(styles.lobbyMessageBubble);
|
||||
}
|
||||
|
||||
const messageText = replaceNonUnicodeEmojis(this._getMessageText());
|
||||
@@ -86,11 +75,13 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
<View style = { messageBubbleStyle }>
|
||||
<View style = { styles.textWrapper } >
|
||||
{ this._renderDisplayName() }
|
||||
{isGifMessage(messageText)
|
||||
{ isGifMessage(messageText)
|
||||
? <GifMessage message = { messageText } />
|
||||
: (
|
||||
<Linkify linkStyle = { styles.chatLink }>
|
||||
{messageText}
|
||||
<Linkify
|
||||
linkStyle = { styles.chatLink }
|
||||
style = { styles.chatMessage }>
|
||||
{ messageText }
|
||||
</Linkify>
|
||||
)}
|
||||
{ this._renderPrivateNotice() }
|
||||
@@ -134,14 +125,14 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
* @returns {React$Element<*> | null}
|
||||
*/
|
||||
_renderDisplayName() {
|
||||
const { _styles, message, showDisplayName } = this.props;
|
||||
const { message, showDisplayName } = this.props;
|
||||
|
||||
if (!showDisplayName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { _styles.displayName }>
|
||||
<Text style = { styles.senderDisplayName }>
|
||||
{ message.displayName }
|
||||
</Text>
|
||||
);
|
||||
@@ -153,14 +144,14 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
* @returns {React$Element<*> | null}
|
||||
*/
|
||||
_renderPrivateNotice() {
|
||||
const { _styles, message, knocking } = this.props;
|
||||
const { message, knocking } = this.props;
|
||||
|
||||
if (!(message.privateMessage || (message.lobbyChat && !knocking))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { message.lobbyChat ? _styles.lobbyMsgNotice : _styles.privateNotice }>
|
||||
<Text style = { message.lobbyChat ? styles.lobbyMsgNotice : styles.privateNotice }>
|
||||
{ this._getPrivateNoticeMessage() }
|
||||
</Text>
|
||||
);
|
||||
@@ -172,7 +163,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
* @returns {React$Element<*> | null}
|
||||
*/
|
||||
_renderPrivateReplyButton() {
|
||||
const { _styles, message, knocking } = this.props;
|
||||
const { message, knocking } = this.props;
|
||||
const { messageType, privateMessage, lobbyChat } = message;
|
||||
|
||||
if (!(privateMessage || lobbyChat) || messageType === MESSAGE_TYPE_LOCAL || knocking) {
|
||||
@@ -180,13 +171,13 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { _styles.replyContainer }>
|
||||
<View style = { styles.replyContainer }>
|
||||
<PrivateMessageButton
|
||||
isLobbyMessage = { lobbyChat }
|
||||
participantID = { message.id }
|
||||
reply = { true }
|
||||
showLabel = { false }
|
||||
toggledStyles = { _styles.replyStyles } />
|
||||
toggledStyles = { styles.replyStyles } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -217,7 +208,6 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_styles: ColorSchemeRegistry.get(state, 'Chat'),
|
||||
knocking: state['features/lobby'].knocking
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React, { Component, ReactElement } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
|
||||
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
|
||||
@@ -60,7 +58,7 @@ export default class ChatMessageGroup extends Component<Props> {
|
||||
return `key_${index}`;
|
||||
}
|
||||
|
||||
_renderMessage: Object => React$Element<*>;
|
||||
_renderMessage: Object => ReactElement;
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { ConfirmDialog } from '../../../base/dialog';
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { FlatList, Text, View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import AbstractMessageContainer, { type Props as AbstractProps }
|
||||
from '../AbstractMessageContainer';
|
||||
|
||||
@@ -15,11 +11,6 @@ import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
@@ -82,7 +73,7 @@ class MessageContainer extends AbstractMessageContainer<Props> {
|
||||
return `key_${index}`;
|
||||
}
|
||||
|
||||
_renderListEmptyComponent: () => React$Element<any>;
|
||||
_renderListEmptyComponent: () => ReactElement;
|
||||
|
||||
/**
|
||||
* Renders a message when there are no messages in the chat yet.
|
||||
@@ -90,18 +81,18 @@ class MessageContainer extends AbstractMessageContainer<Props> {
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
_renderListEmptyComponent() {
|
||||
const { _styles, t } = this.props;
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.emptyComponentWrapper }>
|
||||
<Text style = { _styles.emptyComponentText }>
|
||||
<Text style = { styles.emptyComponentText }>
|
||||
{ t('chat.noMessagesMessage') }
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_renderMessageGroup: Object => React$Element<any>;
|
||||
_renderMessageGroup: Object => ReactElement;
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
@@ -114,16 +105,4 @@ class MessageContainer extends AbstractMessageContainer<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
export default translate(connect()(MessageContainer));
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text, TouchableHighlight, View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconCloseCircle } from '../../../base/icons';
|
||||
import { Icon, IconCloseLarge } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { type StyleType } from '../../../base/styles';
|
||||
import {
|
||||
setParams
|
||||
} from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
@@ -16,14 +12,11 @@ import AbstractMessageRecipient, {
|
||||
type Props as AbstractProps
|
||||
} from '../AbstractMessageRecipient';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
@@ -99,14 +92,17 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _styles, privateMessageRecipient, t,
|
||||
isLobbyChatActive, lobbyMessageRecipient } = this.props;
|
||||
|
||||
const {
|
||||
isLobbyChatActive,
|
||||
lobbyMessageRecipient,
|
||||
privateMessageRecipient,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
if (isLobbyChatActive) {
|
||||
return (
|
||||
<View style = { _styles.lobbyMessageRecipientContainer }>
|
||||
<Text style = { _styles.messageRecipientText }>
|
||||
<View style = { styles.lobbyMessageRecipientContainer }>
|
||||
<Text style = { styles.messageRecipientText }>
|
||||
{ t('chat.lobbyChatMessageTo', {
|
||||
recipient: lobbyMessageRecipient.name
|
||||
}) }
|
||||
@@ -114,8 +110,8 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
<TouchableHighlight
|
||||
onPress = { this._onResetLobbyMessageRecipient }>
|
||||
<Icon
|
||||
src = { IconCloseCircle }
|
||||
style = { _styles.messageRecipientCancelIcon } />
|
||||
src = { IconCloseLarge }
|
||||
style = { styles.messageRecipientCancelIcon } />
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
@@ -126,8 +122,8 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { _styles.messageRecipientContainer }>
|
||||
<Text style = { _styles.messageRecipientText }>
|
||||
<View style = { styles.messageRecipientContainer }>
|
||||
<Text style = { styles.messageRecipientText }>
|
||||
{ t('chat.messageTo', {
|
||||
recipient: privateMessageRecipient.name
|
||||
}) }
|
||||
@@ -136,8 +132,8 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
onPress = { this._onResetPrivateMessageRecipient }
|
||||
underlayColor = { 'transparent' }>
|
||||
<Icon
|
||||
src = { IconCloseCircle }
|
||||
style = { _styles.messageRecipientCancelIcon } />
|
||||
src = { IconCloseLarge }
|
||||
style = { styles.messageRecipientCancelIcon } />
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
@@ -154,7 +150,6 @@ function _mapStateToProps(state) {
|
||||
const { lobbyMessageRecipient, isLobbyChatActive } = state['features/chat'];
|
||||
|
||||
return {
|
||||
_styles: ColorSchemeRegistry.get(state, 'Chat'),
|
||||
isLobbyChatActive,
|
||||
lobbyMessageRecipient
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { CHAT_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconMessage, IconReply } from '../../../base/icons';
|
||||
@@ -10,7 +8,6 @@ import { handleLobbyChatInitialized, openChat } from '../../../chat/actions';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
|
||||
|
||||
export type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
@@ -103,7 +100,7 @@ class PrivateMessageButton extends AbstractButton<Props, any> {
|
||||
* @param {Props} ownProps - The own props of the component.
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function _mapStateToProps(state: Object, ownProps: Props): $Shape<Props> {
|
||||
export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
|
||||
const { disablePolls } = state['features/base/config'];
|
||||
const { visible = enabled, isLobbyMessage, participantID } = ownProps;
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
// @flow
|
||||
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
|
||||
import { BoxModel, ColorPalette } from '../../../base/styles';
|
||||
import { BoxModel } from '../../../base/styles';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
const BUBBLE_RADIUS = 8;
|
||||
|
||||
const recipientContainer = {
|
||||
alignItems: 'center',
|
||||
backgroundColor: BaseTheme.palette.support05,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
flexDirection: 'row',
|
||||
height: 48,
|
||||
marginBottom: BaseTheme.spacing[3],
|
||||
marginHorizontal: BaseTheme.spacing[3],
|
||||
padding: BaseTheme.spacing[2]
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the feature chat.
|
||||
*
|
||||
@@ -16,16 +24,72 @@ const BUBBLE_RADIUS = 8;
|
||||
*/
|
||||
export default {
|
||||
|
||||
/**
|
||||
* Background of the chat screen.
|
||||
*/
|
||||
backdrop: {
|
||||
backgroundColor: BaseTheme.palette.ui10,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
emptyComponentText: {
|
||||
color: BaseTheme.palette.text03,
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
lobbyMessageBubble: {
|
||||
backgroundColor: BaseTheme.palette.support06
|
||||
},
|
||||
|
||||
lobbyMsgNotice: {
|
||||
color: BaseTheme.palette.text04,
|
||||
fontSize: 11,
|
||||
marginTop: 6
|
||||
},
|
||||
|
||||
privateNotice: {
|
||||
...BaseTheme.palette.bodyShortRegular,
|
||||
color: BaseTheme.palette.text02
|
||||
},
|
||||
|
||||
privateMessageBubble: {
|
||||
backgroundColor: BaseTheme.palette.support05
|
||||
},
|
||||
|
||||
remoteMessageBubble: {
|
||||
backgroundColor: BaseTheme.palette.ui02,
|
||||
borderTopLeftRadius: 0
|
||||
},
|
||||
|
||||
replyContainer: {
|
||||
alignSelf: 'stretch',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
replyStyles: {
|
||||
iconStyle: {
|
||||
color: BaseTheme.palette.icon01,
|
||||
fontSize: 22,
|
||||
padding: BaseTheme.spacing[2]
|
||||
},
|
||||
underlayColor: 'transparent'
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper View for the avatar.
|
||||
*/
|
||||
avatarWrapper: {
|
||||
marginRight: 8,
|
||||
marginRight: BaseTheme.spacing[2],
|
||||
width: 32
|
||||
},
|
||||
|
||||
chatLink: {
|
||||
color: ColorPalette.blue
|
||||
color: BaseTheme.palette.link01
|
||||
},
|
||||
|
||||
chatMessage: {
|
||||
...BaseTheme.typography.bodyShortRegular,
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -60,8 +124,8 @@ export default {
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
customInput: {
|
||||
width: 280
|
||||
customInputContainer: {
|
||||
width: '75%'
|
||||
},
|
||||
|
||||
messageBubble: {
|
||||
@@ -117,7 +181,7 @@ export default {
|
||||
* Text node for the timestamp.
|
||||
*/
|
||||
timeText: {
|
||||
color: 'rgb(164, 184, 209)',
|
||||
color: BaseTheme.palette.text03,
|
||||
fontSize: 13
|
||||
},
|
||||
|
||||
@@ -154,97 +218,35 @@ export default {
|
||||
width: 250,
|
||||
height: undefined,
|
||||
flexGrow: 1
|
||||
}
|
||||
};
|
||||
|
||||
ColorSchemeRegistry.register('Chat', {
|
||||
/**
|
||||
* Background of the chat screen.
|
||||
*/
|
||||
backdrop: {
|
||||
backgroundColor: schemeColor('background'),
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* The text node for the display name.
|
||||
*/
|
||||
displayName: {
|
||||
color: schemeColor('displayName'),
|
||||
fontSize: 13
|
||||
},
|
||||
|
||||
emptyComponentText: {
|
||||
color: BaseTheme.palette.text03,
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
lobbyMessageBubble: {
|
||||
backgroundColor: schemeColor('lobbyMsgBackground')
|
||||
},
|
||||
|
||||
lobbyMsgNotice: {
|
||||
color: schemeColor('lobbyMsgNotice'),
|
||||
fontSize: 11,
|
||||
marginTop: 6
|
||||
},
|
||||
|
||||
lobbyMessageRecipientContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: schemeColor('lobbyMsgBackground'),
|
||||
flexDirection: 'row',
|
||||
padding: BoxModel.padding
|
||||
senderDisplayName: {
|
||||
...BaseTheme.typography.bodyShortBold,
|
||||
color: BaseTheme.palette.text02
|
||||
},
|
||||
|
||||
localMessageBubble: {
|
||||
backgroundColor: schemeColor('localMsgBackground'),
|
||||
backgroundColor: BaseTheme.palette.ui04,
|
||||
borderTopRightRadius: 0
|
||||
},
|
||||
|
||||
lobbyMessageRecipientContainer: {
|
||||
...recipientContainer,
|
||||
backgroundColor: BaseTheme.palette.support06
|
||||
},
|
||||
|
||||
messageRecipientCancelIcon: {
|
||||
color: schemeColor('icon'),
|
||||
color: BaseTheme.palette.icon01,
|
||||
fontSize: 18
|
||||
},
|
||||
|
||||
messageRecipientContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: schemeColor('privateMsgBackground'),
|
||||
flexDirection: 'row',
|
||||
padding: BoxModel.padding
|
||||
...recipientContainer
|
||||
},
|
||||
|
||||
messageRecipientText: {
|
||||
color: schemeColor('text'),
|
||||
...BaseTheme.typography.bodyShortRegular,
|
||||
color: BaseTheme.palette.text01,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
privateNotice: {
|
||||
color: schemeColor('privateMsgNotice'),
|
||||
fontSize: 11,
|
||||
marginTop: 6
|
||||
},
|
||||
|
||||
privateMessageBubble: {
|
||||
backgroundColor: schemeColor('privateMsgBackground')
|
||||
},
|
||||
|
||||
remoteMessageBubble: {
|
||||
backgroundColor: schemeColor('remoteMsgBackground'),
|
||||
borderTopLeftRadius: 0
|
||||
},
|
||||
|
||||
replyContainer: {
|
||||
alignSelf: 'stretch',
|
||||
borderLeftColor: schemeColor('replyBorder'),
|
||||
borderLeftWidth: 1,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
replyStyles: {
|
||||
iconStyle: {
|
||||
color: schemeColor('replyIcon'),
|
||||
fontSize: 22,
|
||||
padding: 8
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
|
||||
|
||||
@@ -100,7 +100,6 @@ class LonelyMeetingExperience extends PureComponent<Props> {
|
||||
icon = { this._renderAddPeopleIcon }
|
||||
labelKey = 'lonelyMeetingExperience.button'
|
||||
onClick = { this._onPress }
|
||||
style = { styles.lonelyButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
) }
|
||||
</View>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IconRaiseHand } from '../../../base/icons';
|
||||
import { Label } from '../../../base/label';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
@@ -66,14 +66,13 @@ const TitleBar = (props: IProps): JSX.Element => {
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
|
||||
</View>
|
||||
|
||||
{
|
||||
props._meetingNameEnabled
|
||||
&& <View style = { styles.roomNameView as StyleProp<ViewStyle> }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { styles.roomName }>
|
||||
{props._meetingName}
|
||||
{ props._meetingName }
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
|
||||
@@ -71,8 +71,7 @@ export default {
|
||||
},
|
||||
|
||||
qualityLabelContainer: {
|
||||
borderBottomLeftRadius: 3,
|
||||
borderTopLeftRadius: 3,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
flexShrink: 1,
|
||||
paddingHorizontal: 2,
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -4,7 +4,6 @@ import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
export const INSECURE_ROOM_NAME_LABEL_COLOR = BaseTheme.palette.actionDanger;
|
||||
|
||||
const TITLE_BAR_BUTTON_SIZE = 24;
|
||||
const HEADER_ACTION_BUTTON_SIZE = 17;
|
||||
|
||||
|
||||
/**
|
||||
@@ -35,29 +34,6 @@ export default {
|
||||
margin: 10
|
||||
},
|
||||
|
||||
headerNavigationButton: {
|
||||
height: BaseTheme.spacing[6],
|
||||
marginTop: 20,
|
||||
width: BaseTheme.spacing[6]
|
||||
},
|
||||
|
||||
headerNavigationIcon: {
|
||||
marginLeft: 12
|
||||
},
|
||||
|
||||
headerNavigationText: {
|
||||
color: BaseTheme.palette.text01,
|
||||
marginLeft: BaseTheme.spacing[3],
|
||||
fontSize: HEADER_ACTION_BUTTON_SIZE
|
||||
},
|
||||
|
||||
headerNavigationTextBold: {
|
||||
...BaseTheme.typography.labelButton,
|
||||
color: BaseTheme.palette.text01,
|
||||
marginRight: BaseTheme.spacing[3],
|
||||
fontSize: HEADER_ACTION_BUTTON_SIZE
|
||||
},
|
||||
|
||||
/**
|
||||
* View that contains the indicators.
|
||||
*/
|
||||
@@ -84,10 +60,6 @@ export default {
|
||||
underlayColor: 'transparent'
|
||||
},
|
||||
|
||||
lonelyButton: {
|
||||
borderRadius: BaseTheme.spacing[4]
|
||||
},
|
||||
|
||||
lonelyMeetingContainer: {
|
||||
alignSelf: 'stretch',
|
||||
alignItems: 'center',
|
||||
@@ -210,16 +182,17 @@ export default {
|
||||
},
|
||||
|
||||
insecureRoomNameLabel: {
|
||||
backgroundColor: INSECURE_ROOM_NAME_LABEL_COLOR
|
||||
backgroundColor: INSECURE_ROOM_NAME_LABEL_COLOR,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
height: 32
|
||||
},
|
||||
|
||||
raisedHandsCountLabel: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: BaseTheme.palette.warning02,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginLeft: BaseTheme.spacing[0],
|
||||
marginBottom: BaseTheme.spacing[0],
|
||||
marginRight: BaseTheme.spacing[1]
|
||||
marginBottom: BaseTheme.spacing[0]
|
||||
},
|
||||
|
||||
raisedHandsCountLabelText: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getJitsiMeetTransport } from '../../../modules/transport';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
DATA_CHANNEL_CLOSED,
|
||||
DATA_CHANNEL_OPENED,
|
||||
KICKED_OUT
|
||||
} from '../base/conference/actionTypes';
|
||||
@@ -124,6 +125,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case DATA_CHANNEL_CLOSED:
|
||||
APP.API.notifyDataChannelClosed(action.code, action.reason);
|
||||
break;
|
||||
|
||||
case DATA_CHANNEL_OPENED:
|
||||
APP.API.notifyDataChannelOpened();
|
||||
break;
|
||||
|
||||
@@ -5,20 +5,21 @@ import { getLocalVideoTrack } from '../base/tracks/functions';
|
||||
import { getBaseUrl } from '../base/util/helpers';
|
||||
|
||||
import {
|
||||
addFaceExpression,
|
||||
addFaceLandmarks,
|
||||
clearFaceExpressionBuffer,
|
||||
newFaceBox
|
||||
} from './actions';
|
||||
import {
|
||||
DETECTION_TYPES,
|
||||
DETECT_FACE,
|
||||
FACE_LANDMARK_DETECTION_ERROR_THRESHOLD,
|
||||
FACE_LANDMARKS_DETECTION_ERROR_THRESHOLD,
|
||||
INIT_WORKER,
|
||||
NO_DETECTION,
|
||||
NO_FACE_DETECTION_THRESHOLD,
|
||||
WEBHOOK_SEND_TIME_INTERVAL
|
||||
} from './constants';
|
||||
import {
|
||||
getDetectionInterval,
|
||||
getFaceExpressionDuration,
|
||||
sendFaceExpressionsWebhook
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
@@ -33,13 +34,14 @@ class FaceLandmarksDetector {
|
||||
private worker: Worker | null = null;
|
||||
private lastFaceExpression: string | null = null;
|
||||
private lastFaceExpressionTimestamp: number | null = null;
|
||||
private duplicateConsecutiveExpressions = 0;
|
||||
private webhookSendInterval: number | null = null;
|
||||
private detectionInterval: number | null = null;
|
||||
private recognitionActive = false;
|
||||
private canvas?: HTMLCanvasElement;
|
||||
private context?: CanvasRenderingContext2D | null;
|
||||
private errorCount = 0;
|
||||
private noDetectionCount = 0;
|
||||
private noDetectionStartTimestamp: number | null = null;
|
||||
|
||||
/**
|
||||
* Constructor for class, checks if the environment supports OffscreenCanvas.
|
||||
@@ -97,27 +99,48 @@ class FaceLandmarksDetector {
|
||||
|
||||
// @ts-ignore
|
||||
const workerBlob = new Blob([ `importScripts("${workerUrl}");` ], { type: 'application/javascript' });
|
||||
const state = getState();
|
||||
const addToBuffer = Boolean(state['features/base/config'].webhookProxyUrl);
|
||||
|
||||
// @ts-ignore
|
||||
workerUrl = window.URL.createObjectURL(workerBlob);
|
||||
this.worker = new Worker(workerUrl, { name: 'Face Recognition Worker' });
|
||||
this.worker = new Worker(workerUrl, { name: 'Face Landmarks Worker' });
|
||||
this.worker.onmessage = ({ data }: MessageEvent<any>) => {
|
||||
const { faceExpression, faceBox } = data;
|
||||
const { faceExpression, faceBox, faceCount } = data;
|
||||
const messageTimestamp = Date.now();
|
||||
|
||||
if (faceExpression) {
|
||||
if (faceExpression === this.lastFaceExpression) {
|
||||
this.duplicateConsecutiveExpressions++;
|
||||
} else {
|
||||
if (this.lastFaceExpression && this.lastFaceExpressionTimestamp) {
|
||||
dispatch(addFaceExpression(
|
||||
this.lastFaceExpression,
|
||||
getFaceExpressionDuration(getState(), this.duplicateConsecutiveExpressions + 1),
|
||||
this.lastFaceExpressionTimestamp
|
||||
));
|
||||
}
|
||||
this.lastFaceExpression = faceExpression;
|
||||
this.lastFaceExpressionTimestamp = Date.now();
|
||||
this.duplicateConsecutiveExpressions = 0;
|
||||
// if the number of faces detected is different from 1 we do not take into consideration that detection
|
||||
if (faceCount !== 1) {
|
||||
if (this.noDetectionCount === 0) {
|
||||
this.noDetectionStartTimestamp = messageTimestamp;
|
||||
}
|
||||
this.noDetectionCount++;
|
||||
|
||||
if (this.noDetectionCount === NO_FACE_DETECTION_THRESHOLD && this.noDetectionStartTimestamp) {
|
||||
this.addFaceLandmarks(
|
||||
dispatch,
|
||||
this.noDetectionStartTimestamp,
|
||||
NO_DETECTION,
|
||||
addToBuffer
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (this.noDetectionCount > 0) {
|
||||
this.noDetectionCount = 0;
|
||||
this.noDetectionStartTimestamp = null;
|
||||
}
|
||||
|
||||
if (faceExpression?.expression) {
|
||||
const { expression } = faceExpression;
|
||||
|
||||
if (expression !== this.lastFaceExpression) {
|
||||
this.addFaceLandmarks(
|
||||
dispatch,
|
||||
messageTimestamp,
|
||||
expression,
|
||||
addToBuffer
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +151,7 @@ class FaceLandmarksDetector {
|
||||
APP.API.notifyFaceLandmarkDetected(faceBox, faceExpression);
|
||||
};
|
||||
|
||||
const { faceLandmarks } = getState()['features/base/config'];
|
||||
const { faceLandmarks } = state['features/base/config'];
|
||||
const detectionTypes = [
|
||||
faceLandmarks?.enableFaceCentering && DETECTION_TYPES.FACE_BOX,
|
||||
faceLandmarks?.enableFaceExpressionsDetection && DETECTION_TYPES.FACE_EXPRESSIONS
|
||||
@@ -162,7 +185,7 @@ class FaceLandmarksDetector {
|
||||
}
|
||||
|
||||
if (this.recognitionActive) {
|
||||
logger.log('Face detection already active.');
|
||||
logger.log('Face landmarks detection already active.');
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -179,7 +202,7 @@ class FaceLandmarksDetector {
|
||||
|
||||
this.imageCapture = new ImageCapture(firstVideoTrack);
|
||||
this.recognitionActive = true;
|
||||
logger.log('Start face detection');
|
||||
logger.log('Start face landmarks detection');
|
||||
|
||||
const { faceLandmarks } = state['features/base/config'];
|
||||
|
||||
@@ -191,7 +214,7 @@ class FaceLandmarksDetector {
|
||||
).then(status => {
|
||||
if (status) {
|
||||
this.errorCount = 0;
|
||||
} else if (++this.errorCount > FACE_LANDMARK_DETECTION_ERROR_THRESHOLD) {
|
||||
} else if (++this.errorCount > FACE_LANDMARKS_DETECTION_ERROR_THRESHOLD) {
|
||||
/* this prevents the detection from stopping immediately after occurring an error
|
||||
* sometimes due to the small detection interval when starting the detection some errors
|
||||
* might occur due to the track not being ready
|
||||
@@ -228,18 +251,11 @@ class FaceLandmarksDetector {
|
||||
if (!this.recognitionActive || !this.isInitialized()) {
|
||||
return;
|
||||
}
|
||||
const stopTimestamp = Date.now();
|
||||
const addToBuffer = Boolean(getState()['features/base/config'].webhookProxyUrl);
|
||||
|
||||
if (this.lastFaceExpression && this.lastFaceExpressionTimestamp) {
|
||||
dispatch(
|
||||
addFaceExpression(
|
||||
this.lastFaceExpression,
|
||||
getFaceExpressionDuration(getState(), this.duplicateConsecutiveExpressions + 1),
|
||||
this.lastFaceExpressionTimestamp
|
||||
)
|
||||
);
|
||||
this.duplicateConsecutiveExpressions = 0;
|
||||
this.lastFaceExpression = null;
|
||||
this.lastFaceExpressionTimestamp = null;
|
||||
this.addFaceLandmarks(dispatch, stopTimestamp, null, addToBuffer);
|
||||
}
|
||||
|
||||
this.webhookSendInterval && window.clearInterval(this.webhookSendInterval);
|
||||
@@ -248,7 +264,36 @@ class FaceLandmarksDetector {
|
||||
this.detectionInterval = null;
|
||||
this.imageCapture = null;
|
||||
this.recognitionActive = false;
|
||||
logger.log('Stop face detection');
|
||||
logger.log('Stop face landmarks detection');
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the action for adding new face landmarks and changes the state of the class.
|
||||
*
|
||||
* @param {IStore.dispatch} dispatch - The redux dispatch function.
|
||||
* @param {number} endTimestamp - The timestamp when the face landmarks ended.
|
||||
* @param {string} newFaceExpression - The new face expression.
|
||||
* @param {boolean} addToBuffer - Flag for adding the face landmarks to the buffer.
|
||||
* @returns {void}
|
||||
*/
|
||||
private addFaceLandmarks(
|
||||
dispatch: IStore['dispatch'],
|
||||
endTimestamp: number,
|
||||
newFaceExpression: string | null,
|
||||
addToBuffer = false) {
|
||||
if (this.lastFaceExpression && this.lastFaceExpressionTimestamp) {
|
||||
dispatch(addFaceLandmarks(
|
||||
{
|
||||
duration: endTimestamp - this.lastFaceExpressionTimestamp,
|
||||
faceExpression: this.lastFaceExpression,
|
||||
timestamp: this.lastFaceExpressionTimestamp
|
||||
},
|
||||
addToBuffer
|
||||
));
|
||||
}
|
||||
|
||||
this.lastFaceExpression = newFaceExpression;
|
||||
this.lastFaceExpressionTimestamp = endTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@ import { setWasmPaths } from '@tensorflow/tfjs-backend-wasm';
|
||||
import { Config, FaceResult, Human } from '@vladmandic/human';
|
||||
|
||||
import { DETECTION_TYPES, FACE_DETECTION_SCORE_THRESHOLD, FACE_EXPRESSIONS_NAMING_MAPPING } from './constants';
|
||||
import { DetectInput, DetectOutput, FaceBox, InitInput } from './types';
|
||||
import { DetectInput, DetectOutput, FaceBox, FaceExpression, InitInput } from './types';
|
||||
|
||||
export interface IFaceLandmarksHelper {
|
||||
detect: ({ image, threshold }: DetectInput) => Promise<DetectOutput>;
|
||||
@@ -10,7 +10,7 @@ export interface IFaceLandmarksHelper {
|
||||
getDetections: (image: ImageBitmap | ImageData) => Promise<Array<FaceResult>>;
|
||||
getFaceBox: (detections: Array<FaceResult>, threshold: number) => FaceBox | undefined;
|
||||
getFaceCount: (detections: Array<FaceResult>) => number;
|
||||
getFaceExpression: (detections: Array<FaceResult>) => string | undefined;
|
||||
getFaceExpression: (detections: Array<FaceResult>) => FaceExpression | undefined;
|
||||
init: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -144,13 +144,18 @@ export class HumanHelper implements IFaceLandmarksHelper {
|
||||
* @param {Array<FaceResult>} detections - The array with the detections.
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
getFaceExpression(detections: Array<FaceResult>): string | undefined {
|
||||
getFaceExpression(detections: Array<FaceResult>): FaceExpression | undefined {
|
||||
if (this.getFaceCount(detections) !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (detections[0].emotion) {
|
||||
return FACE_EXPRESSIONS_NAMING_MAPPING[detections[0].emotion[0].emotion];
|
||||
const detection = detections[0];
|
||||
|
||||
if (detection.emotion) {
|
||||
return {
|
||||
expression: FACE_EXPRESSIONS_NAMING_MAPPING[detection.emotion[0].emotion],
|
||||
score: detection.emotion[0].score
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
/**
|
||||
* Redux action type dispatched in order to add a face expression.
|
||||
* Redux action type dispatched in order to add real-time faceLandmarks to timeline.
|
||||
*
|
||||
* {
|
||||
* type: ADD_FACE_EXPRESSION,
|
||||
* faceExpression: string,
|
||||
* duration: number
|
||||
* type: ADD_FACE_LANDMARKS,
|
||||
* faceLandmarks: FaceLandmarks
|
||||
* }
|
||||
*/
|
||||
export const ADD_FACE_EXPRESSION = 'ADD_FACE_EXPRESSION';
|
||||
export const ADD_FACE_LANDMARKS = 'ADD_FACE_LANDMARKS';
|
||||
|
||||
/**
|
||||
* Redux action type dispatched in order to add a expression to the face expressions buffer.
|
||||
* Redux action type dispatched in order to clear the faceLandmarks buffer for webhook in the state.
|
||||
*
|
||||
* {
|
||||
* type: ADD_TO_FACE_EXPRESSIONS_BUFFER,
|
||||
* faceExpression: string
|
||||
* type: CLEAR_FACE_LANDMARKS_BUFFER
|
||||
* }
|
||||
*/
|
||||
export const ADD_TO_FACE_EXPRESSIONS_BUFFER = 'ADD_TO_FACE_EXPRESSIONS_BUFFER';
|
||||
|
||||
/**
|
||||
* Redux action type dispatched in order to clear the face expressions buffer in the state.
|
||||
*
|
||||
* {
|
||||
* type: CLEAR_FACE_EXPRESSIONS_BUFFER
|
||||
* }
|
||||
*/
|
||||
export const CLEAR_FACE_EXPRESSIONS_BUFFER = 'CLEAR_FACE_EXPRESSIONS_BUFFER';
|
||||
export const CLEAR_FACE_LANDMARKS_BUFFER = 'CLEAR_FACE_LANDMARKS_BUFFER';
|
||||
|
||||
/**
|
||||
* Redux action type dispatched in order to update coordinates of a detected face.
|
||||
|
||||
@@ -3,56 +3,35 @@ import './createImageBitmap';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import {
|
||||
ADD_FACE_EXPRESSION,
|
||||
ADD_TO_FACE_EXPRESSIONS_BUFFER,
|
||||
CLEAR_FACE_EXPRESSIONS_BUFFER,
|
||||
ADD_FACE_LANDMARKS,
|
||||
CLEAR_FACE_LANDMARKS_BUFFER,
|
||||
NEW_FACE_COORDINATES
|
||||
} from './actionTypes';
|
||||
import { FaceBox } from './types';
|
||||
import { FaceBox, FaceLandmarks } from './types';
|
||||
|
||||
/**
|
||||
* Adds a new face expression and its duration.
|
||||
* Adds new face landmarks to the timeline.
|
||||
*
|
||||
* @param {string} faceExpression - Face expression to be added.
|
||||
* @param {number} duration - Duration in seconds of the face expression.
|
||||
* @param {number} timestamp - Duration in seconds of the face expression.
|
||||
* @param {FaceLandmarks} faceLandmarks - The new face landmarks to timeline.
|
||||
* @param {boolean} addToBuffer - If true adds the face landmarks to a buffer in the reducer for webhook.
|
||||
* @returns {AnyAction}
|
||||
*/
|
||||
export function addFaceExpression(faceExpression: string, duration: number, timestamp: number): AnyAction {
|
||||
export function addFaceLandmarks(faceLandmarks: FaceLandmarks, addToBuffer: boolean): AnyAction {
|
||||
return {
|
||||
type: ADD_FACE_EXPRESSION,
|
||||
faceExpression,
|
||||
duration,
|
||||
timestamp
|
||||
type: ADD_FACE_LANDMARKS,
|
||||
faceLandmarks,
|
||||
addToBuffer
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a face expression with its timestamp to the face expression buffer.
|
||||
* Clears the face landmarks array in the state.
|
||||
*
|
||||
* @param {Object} faceExpression - Object containing face expression string and its timestamp.
|
||||
* @returns {AnyAction}
|
||||
*/
|
||||
export function addToFaceExpressionsBuffer(
|
||||
faceExpression: {
|
||||
emotion: string;
|
||||
timestamp: number;
|
||||
}
|
||||
): AnyAction {
|
||||
export function clearFaceExpressionBuffer(): AnyAction {
|
||||
return {
|
||||
type: ADD_TO_FACE_EXPRESSIONS_BUFFER,
|
||||
faceExpression
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the face expressions array in the state.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function clearFaceExpressionBuffer() {
|
||||
return {
|
||||
type: CLEAR_FACE_EXPRESSIONS_BUFFER
|
||||
type: CLEAR_FACE_LANDMARKS_BUFFER
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@ export const INIT_WORKER = 'INIT_WORKER';
|
||||
*/
|
||||
export const FACE_BOX_EVENT_TYPE = 'face-box';
|
||||
|
||||
/**
|
||||
* Type of event sent on the data channel.
|
||||
*/
|
||||
export const FACE_LANDMARKS_EVENT_TYPE = 'face-landmarks';
|
||||
|
||||
/**
|
||||
* Milliseconds interval value for sending new image data to the worker.
|
||||
*/
|
||||
@@ -64,4 +69,15 @@ export const FACE_DETECTION_SCORE_THRESHOLD = 0.75;
|
||||
/**
|
||||
* Threshold for stopping detection after a certain number of consecutive errors have occurred.
|
||||
*/
|
||||
export const FACE_LANDMARK_DETECTION_ERROR_THRESHOLD = 4;
|
||||
export const FACE_LANDMARKS_DETECTION_ERROR_THRESHOLD = 4;
|
||||
|
||||
/**
|
||||
* Threshold for number of consecutive detections with no face,
|
||||
* so that when achieved there will be dispatched an action.
|
||||
*/
|
||||
export const NO_FACE_DETECTION_THRESHOLD = 5;
|
||||
|
||||
/**
|
||||
* Constant type used for signaling that no valid face detection is found.
|
||||
*/
|
||||
export const NO_DETECTION = 'no-detection';
|
||||
|
||||
@@ -12,10 +12,9 @@ onmessage = async function({ data }: MessageEvent<any>) {
|
||||
|
||||
const detections = await helper.detect(data);
|
||||
|
||||
if (detections && (detections.faceBox || detections.faceExpression || detections.faceCount)) {
|
||||
if (detections) {
|
||||
self.postMessage(detections);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +1,27 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { IJitsiConference } from '../base/conference/reducer';
|
||||
import { getLocalParticipant } from '../base/participants/functions';
|
||||
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
|
||||
|
||||
import { DETECT_FACE, FACE_BOX_EVENT_TYPE, SEND_IMAGE_INTERVAL_MS } from './constants';
|
||||
import { FACE_BOX_EVENT_TYPE, FACE_LANDMARKS_EVENT_TYPE, SEND_IMAGE_INTERVAL_MS } from './constants';
|
||||
import logger from './logger';
|
||||
import { FaceBox } from './types';
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
let context: CanvasRenderingContext2D | null;
|
||||
|
||||
if (typeof OffscreenCanvas === 'undefined') {
|
||||
canvas = document.createElement('canvas');
|
||||
context = canvas.getContext('2d');
|
||||
}
|
||||
import { FaceBox, FaceLandmarks } from './types';
|
||||
|
||||
/**
|
||||
* Sends the face expression with its duration to all the other participants.
|
||||
* Sends the face landmarks to other participants via the data channel.
|
||||
*
|
||||
* @param {any} conference - The current conference.
|
||||
* @param {string} faceExpression - Face expression to be sent.
|
||||
* @param {number} duration - The duration of the face expression in seconds.
|
||||
* @param {FaceLandmarks} faceLandmarks - Face landmarks to be sent.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function sendFaceExpressionToParticipants(
|
||||
conference: any,
|
||||
faceExpression: string,
|
||||
duration: number
|
||||
): void {
|
||||
export function sendFaceExpressionToParticipants(conference: any, faceLandmarks: FaceLandmarks): void {
|
||||
try {
|
||||
conference.sendEndpointMessage('', {
|
||||
type: 'face_landmark',
|
||||
faceExpression,
|
||||
duration
|
||||
type: FACE_LANDMARKS_EVENT_TYPE,
|
||||
faceLandmarks
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn('Could not broadcast the face expression to the other participants', err);
|
||||
logger.warn('Could not broadcast the face landmarks to the other participants', err);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -61,30 +48,22 @@ export function sendFaceBoxToParticipants(
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the face expression with its duration to xmpp server.
|
||||
* Sends the face landmarks to prosody.
|
||||
*
|
||||
* @param {any} conference - The current conference.
|
||||
* @param {string} faceExpression - Face expression to be sent.
|
||||
* @param {number} duration - The duration of the face expression in seconds.
|
||||
* @param {FaceLandmarks} faceLandmarks - Face landmarks to be sent.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function sendFaceExpressionToServer(
|
||||
conference: any,
|
||||
faceExpression: string,
|
||||
duration: number
|
||||
): void {
|
||||
export function sendFaceExpressionToServer(conference: IJitsiConference, faceLandmarks: FaceLandmarks): void {
|
||||
try {
|
||||
conference.sendFaceLandmarks({
|
||||
faceExpression,
|
||||
duration
|
||||
});
|
||||
conference.sendFaceLandmarks(faceLandmarks);
|
||||
} catch (err) {
|
||||
logger.warn('Could not send the face expression to xmpp server', err);
|
||||
logger.warn('Could not send the face landmarks to prosody', err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends face expression to backend.
|
||||
* Sends face landmarks to backend.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {boolean} - True if sent, false otherwise.
|
||||
@@ -96,9 +75,9 @@ export async function sendFaceExpressionsWebhook(state: IReduxState) {
|
||||
const { connection } = state['features/base/connection'];
|
||||
const jid = connection?.getJid();
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const { faceExpressionsBuffer } = state['features/face-landmarks'];
|
||||
const { faceLandmarksBuffer } = state['features/face-landmarks'];
|
||||
|
||||
if (faceExpressionsBuffer.length === 0) {
|
||||
if (faceLandmarksBuffer.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -111,7 +90,7 @@ export async function sendFaceExpressionsWebhook(state: IReduxState) {
|
||||
meetingFqn: extractFqnFromPath(),
|
||||
sessionId: conference?.sessionId,
|
||||
submitted: Date.now(),
|
||||
emotions: faceExpressionsBuffer,
|
||||
emotions: faceLandmarksBuffer,
|
||||
participantId: localParticipant?.jwtId,
|
||||
participantName: localParticipant?.name,
|
||||
participantJid: jid
|
||||
@@ -138,55 +117,6 @@ export async function sendFaceExpressionsWebhook(state: IReduxState) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends the image data a canvas from the track in the image capture to the face recognition worker.
|
||||
*
|
||||
* @param {Worker} worker - Face recognition worker.
|
||||
* @param {Object} imageCapture - Image capture that contains the current track.
|
||||
* @param {number} threshold - Movement threshold as percentage for sharing face coordinates.
|
||||
* @returns {Promise<boolean>} - True if sent, false otherwise.
|
||||
*/
|
||||
export async function sendDataToWorker(
|
||||
worker: Worker,
|
||||
imageCapture: ImageCapture,
|
||||
threshold = 10
|
||||
): Promise<boolean> {
|
||||
if (imageCapture === null || imageCapture === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let imageBitmap;
|
||||
let image;
|
||||
|
||||
try {
|
||||
imageBitmap = await imageCapture.grabFrame();
|
||||
} catch (err) {
|
||||
logger.warn(err);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof OffscreenCanvas === 'undefined') {
|
||||
canvas.width = imageBitmap.width;
|
||||
canvas.height = imageBitmap.height;
|
||||
context?.drawImage(imageBitmap, 0, 0);
|
||||
|
||||
image = context?.getImageData(0, 0, imageBitmap.width, imageBitmap.height);
|
||||
} else {
|
||||
image = imageBitmap;
|
||||
}
|
||||
|
||||
worker.postMessage({
|
||||
type: DETECT_FACE,
|
||||
image,
|
||||
threshold
|
||||
});
|
||||
|
||||
imageBitmap.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets face box for a participant id.
|
||||
*
|
||||
@@ -230,14 +160,3 @@ export function getDetectionInterval(state: IReduxState) {
|
||||
|
||||
return Math.max(faceLandmarks?.captureInterval || SEND_IMAGE_INTERVAL_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration in seconds of a face expression.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @param {number} faceExpressionCount - The number of consecutive face expressions.
|
||||
* @returns {number} - Duration of face expression in seconds.
|
||||
*/
|
||||
export function getFaceExpressionDuration(state: IReduxState, faceExpressionCount: number) {
|
||||
return faceExpressionCount * (getDetectionInterval(state) / 1000);
|
||||
}
|
||||
|
||||
@@ -11,18 +11,15 @@ import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from '../base/tracks/actionTypes';
|
||||
|
||||
import FaceLandmarksDetector from './FaceLandmarksDetector';
|
||||
import { ADD_FACE_EXPRESSION, NEW_FACE_COORDINATES, UPDATE_FACE_COORDINATES } from './actionTypes';
|
||||
import {
|
||||
addToFaceExpressionsBuffer
|
||||
} from './actions';
|
||||
import { ADD_FACE_LANDMARKS, NEW_FACE_COORDINATES, UPDATE_FACE_COORDINATES } from './actionTypes';
|
||||
import { FACE_BOX_EVENT_TYPE } from './constants';
|
||||
import { sendFaceBoxToParticipants, sendFaceExpressionToParticipants, sendFaceExpressionToServer } from './functions';
|
||||
|
||||
|
||||
MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: any) => {
|
||||
const { dispatch, getState } = store;
|
||||
const { faceLandmarks } = getState()['features/base/config'];
|
||||
const isEnabled = faceLandmarks?.enableFaceCentering || faceLandmarks?.enableFaceExpressionsDetection;
|
||||
const { faceLandmarks: faceLandmarksConfig } = getState()['features/base/config'];
|
||||
const isEnabled = faceLandmarksConfig?.enableFaceCentering || faceLandmarksConfig?.enableFaceExpressionsDetection;
|
||||
|
||||
if (action.type === CONFERENCE_JOINED) {
|
||||
if (isEnabled) {
|
||||
@@ -99,19 +96,16 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: any)
|
||||
|
||||
return next(action);
|
||||
}
|
||||
case ADD_FACE_EXPRESSION: {
|
||||
case ADD_FACE_LANDMARKS: {
|
||||
const state = getState();
|
||||
const { faceExpression, duration, timestamp } = action;
|
||||
const { faceLandmarks } = action;
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (getParticipantCount(state) > 1) {
|
||||
sendFaceExpressionToParticipants(conference, faceExpression, duration);
|
||||
sendFaceExpressionToParticipants(conference, faceLandmarks);
|
||||
}
|
||||
sendFaceExpressionToServer(conference, faceExpression, duration);
|
||||
dispatch(addToFaceExpressionsBuffer({
|
||||
emotion: faceExpression,
|
||||
timestamp
|
||||
}));
|
||||
|
||||
sendFaceExpressionToServer(conference, faceLandmarks);
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
@@ -1,42 +1,25 @@
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
|
||||
import {
|
||||
ADD_FACE_EXPRESSION,
|
||||
ADD_TO_FACE_EXPRESSIONS_BUFFER,
|
||||
CLEAR_FACE_EXPRESSIONS_BUFFER,
|
||||
ADD_FACE_LANDMARKS,
|
||||
CLEAR_FACE_LANDMARKS_BUFFER,
|
||||
UPDATE_FACE_COORDINATES
|
||||
} from './actionTypes';
|
||||
import { FaceBox } from './types';
|
||||
import { FaceBox, FaceLandmarks } from './types';
|
||||
|
||||
const defaultState = {
|
||||
faceBoxes: {},
|
||||
faceExpressions: {
|
||||
happy: 0,
|
||||
neutral: 0,
|
||||
surprised: 0,
|
||||
angry: 0,
|
||||
fearful: 0,
|
||||
disgusted: 0,
|
||||
sad: 0
|
||||
},
|
||||
faceExpressionsBuffer: [],
|
||||
faceLandmarks: [],
|
||||
faceLandmarksBuffer: [],
|
||||
recognitionActive: false
|
||||
};
|
||||
|
||||
export interface IFaceLandmarksState {
|
||||
faceBoxes: { [key: string]: FaceBox; };
|
||||
faceExpressions: {
|
||||
angry: number;
|
||||
disgusted: number;
|
||||
fearful: number;
|
||||
happy: number;
|
||||
neutral: number;
|
||||
sad: number;
|
||||
surprised: number;
|
||||
};
|
||||
faceExpressionsBuffer: Array<{
|
||||
faceLandmarks: Array<FaceLandmarks>;
|
||||
faceLandmarksBuffer: Array<{
|
||||
emotion: string;
|
||||
timestamp: string;
|
||||
timestamp: number;
|
||||
}>;
|
||||
recognitionActive: boolean;
|
||||
}
|
||||
@@ -44,26 +27,23 @@ export interface IFaceLandmarksState {
|
||||
ReducerRegistry.register<IFaceLandmarksState>('features/face-landmarks',
|
||||
(state = defaultState, action): IFaceLandmarksState => {
|
||||
switch (action.type) {
|
||||
case ADD_FACE_EXPRESSION: {
|
||||
case ADD_FACE_LANDMARKS: {
|
||||
const { addToBuffer, faceLandmarks }: { addToBuffer: boolean; faceLandmarks: FaceLandmarks; } = action;
|
||||
|
||||
return {
|
||||
...state,
|
||||
faceExpressions: {
|
||||
...state.faceExpressions,
|
||||
[action.faceExpression]: state.faceExpressions[
|
||||
action.faceExpression as keyof typeof state.faceExpressions] + action.duration
|
||||
}
|
||||
faceLandmarks: [ ...state.faceLandmarks, faceLandmarks ],
|
||||
faceLandmarksBuffer: addToBuffer ? [ ...state.faceLandmarksBuffer,
|
||||
{
|
||||
emotion: faceLandmarks.faceExpression,
|
||||
timestamp: faceLandmarks.timestamp
|
||||
} ] : state.faceLandmarksBuffer
|
||||
};
|
||||
}
|
||||
case ADD_TO_FACE_EXPRESSIONS_BUFFER: {
|
||||
case CLEAR_FACE_LANDMARKS_BUFFER: {
|
||||
return {
|
||||
...state,
|
||||
faceExpressionsBuffer: [ ...state.faceExpressionsBuffer, action.faceExpression ]
|
||||
};
|
||||
}
|
||||
case CLEAR_FACE_EXPRESSIONS_BUFFER: {
|
||||
return {
|
||||
...state,
|
||||
faceExpressionsBuffer: []
|
||||
faceLandmarksBuffer: []
|
||||
};
|
||||
}
|
||||
case UPDATE_FACE_COORDINATES: {
|
||||
|
||||
@@ -19,5 +19,21 @@ export type InitInput = {
|
||||
export type DetectOutput = {
|
||||
faceBox?: FaceBox;
|
||||
faceCount: number;
|
||||
faceExpression?: string;
|
||||
faceExpression?: FaceExpression;
|
||||
};
|
||||
|
||||
export type FaceExpression = {
|
||||
expression: string;
|
||||
score: number;
|
||||
};
|
||||
|
||||
export type FaceLandmarks = {
|
||||
|
||||
// duration in milliseconds of the face landmarks
|
||||
duration: number;
|
||||
faceExpression: string;
|
||||
score?: number;
|
||||
|
||||
// the start timestamp of the expression
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Image, View } from 'react-native';
|
||||
import type { Dispatch } from 'redux';
|
||||
@@ -226,11 +224,11 @@ class Thumbnail extends PureComponent<Props> {
|
||||
] }>
|
||||
{ !_isVirtualScreenshare && <ConnectionIndicator participantId = { participantId } /> }
|
||||
{ !_isVirtualScreenshare && <RaisedHandIndicator participantId = { participantId } /> }
|
||||
{tileView && isScreenShare && (
|
||||
{ tileView && isScreenShare && (
|
||||
<View style = { styles.indicatorContainer }>
|
||||
<ScreenShareIndicator />
|
||||
</View>
|
||||
)}
|
||||
) }
|
||||
</View>);
|
||||
indicators.push(<Container
|
||||
key = 'bottom-indicators'
|
||||
@@ -369,7 +367,7 @@ class Thumbnail extends PureComponent<Props> {
|
||||
_renderDominantSpeakerIndicator && !_isVirtualScreenshare ? styles.thumbnailDominantSpeaker : null
|
||||
] }
|
||||
touchFeedback = { false }>
|
||||
{_gifSrc ? <Image
|
||||
{ _gifSrc ? <Image
|
||||
source = {{ uri: _gifSrc }}
|
||||
style = { styles.thumbnailGif } />
|
||||
: <>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
import { SMALL_THUMBNAIL_SIZE } from '../../constants';
|
||||
|
||||
@@ -10,7 +8,7 @@ export const AVATAR_SIZE = 50;
|
||||
|
||||
const indicatorContainer = {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
borderRadius: 4,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
margin: 2,
|
||||
padding: 2
|
||||
};
|
||||
@@ -119,7 +117,16 @@ export default {
|
||||
width: SMALL_THUMBNAIL_SIZE
|
||||
},
|
||||
|
||||
indicatorContainer,
|
||||
indicatorContainer: {
|
||||
...indicatorContainer,
|
||||
flexShrink: 1,
|
||||
height: 32,
|
||||
justifyContent: 'center',
|
||||
marginBottom: BaseTheme.spacing[0],
|
||||
marginHorizontal: BaseTheme.spacing[1],
|
||||
marginTop: BaseTheme.spacing[2],
|
||||
width: 32
|
||||
},
|
||||
|
||||
/**
|
||||
* The thumbnails indicator container.
|
||||
|
||||
@@ -198,6 +198,11 @@ export interface IProps {
|
||||
*/
|
||||
_raisedHand: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not to display a tint background over tile.
|
||||
*/
|
||||
_shouldDisplayTintBackground: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the current layout is stage filmstrip layout.
|
||||
*/
|
||||
@@ -360,6 +365,15 @@ const defaultStyles = (theme: Theme) => {
|
||||
objectFit: 'contain',
|
||||
flexGrow: '1'
|
||||
}
|
||||
},
|
||||
|
||||
tintBackground: {
|
||||
position: 'absolute' as const,
|
||||
zIndex: 1,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: `${theme.palette.uiBackground}`,
|
||||
opacity: 0.8
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -965,6 +979,7 @@ class Thumbnail extends Component<IProps, IState> {
|
||||
_isTestModeEnabled,
|
||||
_localFlipX,
|
||||
_participant,
|
||||
_shouldDisplayTintBackground,
|
||||
_thumbnailType,
|
||||
_videoTrack,
|
||||
classes,
|
||||
@@ -1043,6 +1058,7 @@ class Thumbnail extends Component<IProps, IState> {
|
||||
showPopover = { this._showPopover }
|
||||
thumbnailType = { _thumbnailType } />
|
||||
</div>
|
||||
{_shouldDisplayTintBackground && <div className = { classes.tintBackground } />}
|
||||
<div
|
||||
className = { clsx(classes.indicatorsContainer,
|
||||
classes.indicatorsBottomContainer,
|
||||
@@ -1090,7 +1106,12 @@ class Thumbnail extends Component<IProps, IState> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _participant, _isTestModeEnabled, _isVirtualScreenshareParticipant } = this.props;
|
||||
const {
|
||||
_isTestModeEnabled,
|
||||
_isVirtualScreenshareParticipant,
|
||||
_participant,
|
||||
_shouldDisplayTintBackground
|
||||
} = this.props;
|
||||
const videoEventListeners: any = {};
|
||||
|
||||
if (!_participant) {
|
||||
@@ -1136,6 +1157,7 @@ class Thumbnail extends Component<IProps, IState> {
|
||||
onTouchMove = { this._onTouchMove }
|
||||
onTouchStart = { this._onTouchStart }
|
||||
participantId = { _participant.id }
|
||||
shouldDisplayTintBackground = { _shouldDisplayTintBackground }
|
||||
styles = { this._getStyles() }
|
||||
thumbnailType = { _thumbnailType }
|
||||
videoTrack = { _videoTrack } />
|
||||
@@ -1264,6 +1286,11 @@ function _mapStateToProps(state: IReduxState, ownProps: any): Object {
|
||||
const { gifUrl: gifSrc } = getGifForParticipant(state, id ?? '');
|
||||
const mode = getGifDisplayMode(state);
|
||||
const participantId = isLocal ? getLocalParticipant(state)?.id : participantID;
|
||||
const isActiveParticipant = activeParticipants.find((pId: string) => pId === participantId);
|
||||
const participantCurrentlyOnLargeVideo = state['features/large-video']?.participantId === id;
|
||||
const shouldDisplayTintBackground
|
||||
= _currentLayout !== LAYOUTS.TILE_VIEW && filmstripType === FILMSTRIP_TYPE.MAIN
|
||||
&& (isActiveParticipant || participantCurrentlyOnLargeVideo);
|
||||
|
||||
return {
|
||||
_audioTrack,
|
||||
@@ -1271,10 +1298,10 @@ function _mapStateToProps(state: IReduxState, ownProps: any): Object {
|
||||
_defaultLocalDisplayName: defaultLocalDisplayName,
|
||||
_disableLocalVideoFlip: Boolean(disableLocalVideoFlip),
|
||||
_disableTileEnlargement: Boolean(disableTileEnlargement),
|
||||
_isActiveParticipant: activeParticipants.find((pId: string) => pId === participantId),
|
||||
_isActiveParticipant: isActiveParticipant,
|
||||
_isHidden: isLocal && iAmRecorder && !iAmSipGateway,
|
||||
_isAudioOnly: Boolean(state['features/base/audio-only'].enabled),
|
||||
_isCurrentlyOnLargeVideo: state['features/large-video']?.participantId === id,
|
||||
_isCurrentlyOnLargeVideo: participantCurrentlyOnLargeVideo,
|
||||
_isDominantSpeakerDisabled: interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR,
|
||||
_isMobile,
|
||||
_isMobilePortrait,
|
||||
@@ -1287,6 +1314,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any): Object {
|
||||
_raisedHand: hasRaisedHand(participant),
|
||||
_stageFilmstripLayout: isStageFilmstripAvailable(state),
|
||||
_stageParticipantsVisible: _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW,
|
||||
_shouldDisplayTintBackground: shouldDisplayTintBackground,
|
||||
_thumbnailType: tileType,
|
||||
_videoObjectPosition: getVideoObjectPosition(state, participant?.id),
|
||||
_videoTrack,
|
||||
|
||||
@@ -77,6 +77,11 @@ type Props = {
|
||||
*/
|
||||
participantId: string,
|
||||
|
||||
/**
|
||||
* Whether or not to display a tint background over tile.
|
||||
*/
|
||||
shouldDisplayTintBackground: boolean,
|
||||
|
||||
/**
|
||||
* An object with the styles for thumbnail.
|
||||
*/
|
||||
@@ -107,6 +112,7 @@ const VirtualScreenshareParticipant = ({
|
||||
onTouchMove,
|
||||
onTouchStart,
|
||||
participantId,
|
||||
shouldDisplayTintBackground,
|
||||
styles,
|
||||
videoTrack,
|
||||
thumbnailType
|
||||
@@ -150,6 +156,7 @@ const VirtualScreenshareParticipant = ({
|
||||
participantId = { participantId }
|
||||
thumbnailType = { thumbnailType } />
|
||||
</div>
|
||||
{shouldDisplayTintBackground && <div className = { classes.tintBackground } />}
|
||||
<div
|
||||
className = { clsx(classes.indicatorsContainer,
|
||||
classes.indicatorsBottomContainer,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text, TouchableRipple } from 'react-native-paper';
|
||||
|
||||
import { Icon } from '../../../base/icons';
|
||||
import type { StyleType } from '../../../base/styles';
|
||||
import styles from '../../../conference/components/native/styles';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
import { navigationStyles } from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -55,25 +55,25 @@ const HeaderNavigationButton
|
||||
src ? (
|
||||
<TouchableRipple
|
||||
onPress = { onPress }
|
||||
rippleColor = { 'transparent' }
|
||||
style = { [
|
||||
buttonStyle,
|
||||
styles.headerNavigationButton ] } >
|
||||
navigationStyles.headerNavigationButtonIcon ] } >
|
||||
<Icon
|
||||
color = { BaseTheme.palette.link01Active }
|
||||
size = { 24 }
|
||||
src = { src }
|
||||
style = { styles.headerNavigationIcon } />
|
||||
style = { navigationStyles.headerNavigationIcon } />
|
||||
</TouchableRipple>
|
||||
) : (
|
||||
<TouchableRipple
|
||||
disabled = { disabled }
|
||||
onPress = { onPress }
|
||||
rippleColor = { 'transparent' }>
|
||||
style = { navigationStyles.headerNavigationButtonText } >
|
||||
<Text
|
||||
style = {
|
||||
twoActions
|
||||
? styles.headerNavigationTextBold
|
||||
: styles.headerNavigationText
|
||||
? navigationStyles.headerNavigationTextBold
|
||||
: navigationStyles.headerNavigationText
|
||||
}>
|
||||
{ label }
|
||||
</Text>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// @ts-ignore
|
||||
import React from 'react';
|
||||
import { StyleProp, Text, TextStyle, View } from 'react-native';
|
||||
|
||||
// @ts-ignore
|
||||
import { navigationStyles } from './styles';
|
||||
|
||||
interface ITabBarLabelCounterProps {
|
||||
activeUnreadNr: boolean;
|
||||
isFocused: boolean;
|
||||
label: string;
|
||||
nbUnread?: number;
|
||||
}
|
||||
|
||||
export const TabBarLabelCounter = ({ activeUnreadNr, isFocused, label, nbUnread }: ITabBarLabelCounterProps) => {
|
||||
const labelStyles = isFocused
|
||||
? navigationStyles.unreadCounterDescriptionFocused
|
||||
: navigationStyles.unreadCounterDescription;
|
||||
|
||||
return (
|
||||
<View
|
||||
style = {
|
||||
navigationStyles.unreadCounterContainer as StyleProp<TextStyle> }>
|
||||
<Text
|
||||
style = { labelStyles }>
|
||||
{ label && label }
|
||||
</Text>
|
||||
{
|
||||
activeUnreadNr && (
|
||||
<View
|
||||
style = { navigationStyles.unreadCounterCircle as StyleProp<TextStyle> }>
|
||||
<Text
|
||||
style = { navigationStyles.unreadCounter as StyleProp<TextStyle> }>
|
||||
{ nbUnread }
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,11 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
|
||||
// @ts-ignore
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../../app/types';
|
||||
import {
|
||||
getClientHeight,
|
||||
getClientWidth
|
||||
@@ -11,6 +13,8 @@ import {
|
||||
} from '../../../../../base/modal/components/functions';
|
||||
// @ts-ignore
|
||||
import { Chat } from '../../../../../chat';
|
||||
import { setIsPollsTabFocused } from '../../../../../chat/actions.native';
|
||||
import { resetNbUnreadPollsMessages } from '../../../../../polls/actions';
|
||||
// @ts-ignore
|
||||
import { PollsPane } from '../../../../../polls/components';
|
||||
// @ts-ignore
|
||||
@@ -23,6 +27,11 @@ const ChatTab = createMaterialTopTabNavigator();
|
||||
const ChatAndPolls = () => {
|
||||
const clientHeight = useSelector(getClientHeight);
|
||||
const clientWidth = useSelector(getClientWidth);
|
||||
const dispatch = useDispatch();
|
||||
const { isPollsTabFocused } = useSelector((state: IReduxState) => state['features/chat']);
|
||||
const initialRouteName = isPollsTabFocused
|
||||
? screen.conference.chatandpolls.tab.polls
|
||||
: screen.conference.chatandpolls.tab.chat;
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
@@ -32,12 +41,24 @@ const ChatAndPolls = () => {
|
||||
height: clientHeight,
|
||||
width: clientWidth
|
||||
}}
|
||||
initialRouteName = { initialRouteName }
|
||||
screenOptions = { chatTabBarOptions }>
|
||||
<ChatTab.Screen
|
||||
component = { Chat }
|
||||
listeners = {{
|
||||
tabPress: () => {
|
||||
dispatch(setIsPollsTabFocused(false));
|
||||
}
|
||||
}}
|
||||
name = { screen.conference.chatandpolls.tab.chat } />
|
||||
<ChatTab.Screen
|
||||
component = { PollsPane }
|
||||
listeners = {{
|
||||
tabPress: () => {
|
||||
dispatch(setIsPollsTabFocused(true));
|
||||
dispatch(resetNbUnreadPollsMessages);
|
||||
}
|
||||
}}
|
||||
name = { screen.conference.chatandpolls.tab.polls } />
|
||||
</ChatTab.Navigator>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
import { BoxModel } from '../../../base/styles';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
|
||||
export const TEXT_COLOR = BaseTheme.palette.text01;
|
||||
|
||||
const HEADER_ACTION_BUTTON_SIZE = 17;
|
||||
|
||||
const headerNavigationButton = {
|
||||
alignContent: 'center',
|
||||
height: '100%',
|
||||
justifyItems: 'center'
|
||||
};
|
||||
|
||||
const headerNavigationText = {
|
||||
color: BaseTheme.palette.link01,
|
||||
fontSize: HEADER_ACTION_BUTTON_SIZE
|
||||
};
|
||||
|
||||
const unreadCounterDescription = {
|
||||
...BaseTheme.typography.bodyShortBoldLarge,
|
||||
color: BaseTheme.palette.text03
|
||||
};
|
||||
|
||||
/**
|
||||
* Styles of the navigation feature.
|
||||
*/
|
||||
@@ -27,5 +45,62 @@ export const navigationStyles = {
|
||||
|
||||
connectingScreenText: {
|
||||
color: TEXT_COLOR
|
||||
},
|
||||
|
||||
headerNavigationButtonIcon: {
|
||||
...headerNavigationButton,
|
||||
paddingTop: 18,
|
||||
width: BaseTheme.spacing[7]
|
||||
},
|
||||
|
||||
headerNavigationButtonText: {
|
||||
...headerNavigationButton,
|
||||
paddingTop: 10,
|
||||
width: BaseTheme.spacing[10]
|
||||
},
|
||||
|
||||
headerNavigationIcon: {
|
||||
marginLeft: 12
|
||||
},
|
||||
|
||||
headerNavigationText: {
|
||||
...headerNavigationText,
|
||||
marginLeft: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
headerNavigationTextBold: {
|
||||
...headerNavigationText,
|
||||
...BaseTheme.typography.labelButton,
|
||||
marginRight: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
unreadCounterContainer: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
unreadCounterDescription: {
|
||||
...unreadCounterDescription
|
||||
},
|
||||
|
||||
unreadCounterDescriptionFocused: {
|
||||
...unreadCounterDescription,
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
unreadCounterCircle: {
|
||||
backgroundColor: BaseTheme.palette.warning01,
|
||||
borderRadius: BaseTheme.spacing[4] / 2,
|
||||
height: BaseTheme.spacing[4],
|
||||
justifyContent: 'center',
|
||||
marginLeft: BaseTheme.spacing[2],
|
||||
width: BaseTheme.spacing[4]
|
||||
},
|
||||
|
||||
unreadCounter: {
|
||||
...BaseTheme.typography.bodyShortBold,
|
||||
alignSelf: 'center',
|
||||
color: BaseTheme.palette.text04
|
||||
}
|
||||
};
|
||||
|
||||
@@ -67,19 +67,14 @@ export const conferenceScreenOptions = fullScreenOptions;
|
||||
* Tab bar options for chat screen.
|
||||
*/
|
||||
export const chatTabBarOptions = {
|
||||
tabBarActiveTintColor: BaseTheme.palette.field02,
|
||||
tabBarLabelStyle: {
|
||||
fontSize: BaseTheme.typography.labelRegular.fontSize,
|
||||
textTransform: 'capitalize'
|
||||
},
|
||||
tabBarInactiveTintColor: BaseTheme.palette.text03,
|
||||
swipeEnabled: false,
|
||||
tabBarIndicatorStyle: {
|
||||
backgroundColor: BaseTheme.palette.field02
|
||||
backgroundColor: BaseTheme.palette.link01Active
|
||||
},
|
||||
tabBarStyle: {
|
||||
backgroundColor: BaseTheme.palette.ui01,
|
||||
borderBottomColor: BaseTheme.palette.border05,
|
||||
borderBottomWidth: 1
|
||||
borderBottomWidth: 0.4
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -56,6 +56,13 @@ export const NOTIFICATION_ICON = {
|
||||
PARTICIPANTS: 'participants'
|
||||
};
|
||||
|
||||
/**
|
||||
* The identifier of the disable self view notification.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const DATA_CHANNEL_CLOSED_NOTIFICATION_ID = 'DATA_CHANNEL_CLOSED_NOTIFICATION_ID';
|
||||
|
||||
/**
|
||||
* The identifier of the disable self view notification.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import type { Node } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -121,7 +119,7 @@ function ParticipantItem({
|
||||
{
|
||||
!isKnockingParticipant
|
||||
&& <>
|
||||
{raisedHand && <RaisedHandIndicator />}
|
||||
{ raisedHand && <RaisedHandIndicator /> }
|
||||
<View style = { styles.participantStatesContainer }>
|
||||
<View style = { styles.participantStateVideo }>{VideoStateIcons[videoMediaState]}</View>
|
||||
<View>{AudioStateIcons[audioMediaState]}</View>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FlatList, View } from 'react-native';
|
||||
import { Text } from 'react-native-paper';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { Icon, IconChatUnread } from '../../../base/icons';
|
||||
import { Icon, IconMessage } from '../../../base/icons';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ const PollsList = () => {
|
||||
<Icon
|
||||
color = { BaseTheme.palette.icon03 }
|
||||
size = { 160 }
|
||||
src = { IconChatUnread } />
|
||||
src = { IconMessage } />
|
||||
<Text style = { chatStyles.noPollText } >
|
||||
{
|
||||
t('polls.results.empty')
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* eslint-disable react-native/no-color-literals */
|
||||
// @flow
|
||||
|
||||
import { useIsFocused, useNavigation } from '@react-navigation/native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { getUnreadPollCount } from '../../functions';
|
||||
import { TabBarLabelCounter }
|
||||
from '../../../mobile/navigation/components/TabBarLabelCounter';
|
||||
import AbstractPollsPane from '../AbstractPollsPane';
|
||||
import type { AbstractProps } from '../AbstractPollsPane';
|
||||
|
||||
@@ -19,19 +19,25 @@ import { chatStyles } from './styles';
|
||||
|
||||
const PollsPane = (props: AbstractProps) => {
|
||||
const { createMode, onCreate, setCreateMode, t } = props;
|
||||
const isPollsScreenFocused = useIsFocused();
|
||||
const navigation = useNavigation();
|
||||
const nbUnreadPolls = useSelector(getUnreadPollCount);
|
||||
|
||||
const nrUnreadPolls = !isPollsScreenFocused && nbUnreadPolls > 0
|
||||
? `(${nbUnreadPolls})`
|
||||
: '';
|
||||
const { isPollsTabFocused } = useSelector(state => state['features/chat']);
|
||||
const { nbUnreadPolls } = useSelector(state => state['features/polls']);
|
||||
|
||||
useEffect(() => {
|
||||
const activeUnreadPollsNr = !isPollsTabFocused && nbUnreadPolls > 0;
|
||||
|
||||
navigation.setOptions({
|
||||
tabBarLabel: `${t('chat.tabs.polls')} ${nrUnreadPolls}`
|
||||
// eslint-disable-next-line react/no-multi-comp
|
||||
tabBarLabel: () => (
|
||||
<TabBarLabelCounter
|
||||
activeUnreadNr = { activeUnreadPollsNr }
|
||||
isFocused = { isPollsTabFocused }
|
||||
label = { t('chat.tabs.polls') }
|
||||
nbUnread = { nbUnreadPolls } />
|
||||
)
|
||||
});
|
||||
}, [ nrUnreadPolls ]);
|
||||
|
||||
}, [ isPollsTabFocused, nbUnreadPolls ]);
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
|
||||
@@ -110,10 +110,12 @@ export const chatStyles = createStyleSheet({
|
||||
|
||||
noPollContent: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
paddingTop: '4%'
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: '25%'
|
||||
},
|
||||
|
||||
noPollText: {
|
||||
@@ -149,7 +151,7 @@ export const chatStyles = createStyleSheet({
|
||||
|
||||
pollCreateButton: {
|
||||
flex: 1,
|
||||
marginHorizontal: BaseTheme.spacing[2]
|
||||
marginHorizontal: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
pollSendLabel: {
|
||||
@@ -191,7 +193,8 @@ export const chatStyles = createStyleSheet({
|
||||
},
|
||||
|
||||
pollCreateAddButton: {
|
||||
margin: BaseTheme.spacing[2]
|
||||
marginHorizontal: BaseTheme.spacing[1],
|
||||
marginVertical: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
toggleText: {
|
||||
@@ -200,8 +203,8 @@ export const chatStyles = createStyleSheet({
|
||||
},
|
||||
|
||||
createPollButton: {
|
||||
marginHorizontal: BaseTheme.spacing[4],
|
||||
marginVertical: '8%'
|
||||
marginHorizontal: BaseTheme.spacing[3],
|
||||
marginVertical: 34
|
||||
},
|
||||
|
||||
pollPane: {
|
||||
@@ -218,5 +221,28 @@ export const chatStyles = createStyleSheet({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginHorizontal: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
unreadPollsCounterContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
unreadPollsCounterDescription: {
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
unreadPollsCounterCircle: {
|
||||
backgroundColor: BaseTheme.palette.warning01,
|
||||
borderRadius: BaseTheme.spacing[3] / 2,
|
||||
height: BaseTheme.spacing[3],
|
||||
justifyContent: 'center',
|
||||
marginLeft: BaseTheme.spacing[2],
|
||||
width: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
unreadPollsCounter: {
|
||||
alignSelf: 'center',
|
||||
color: BaseTheme.palette.text04
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
// @ts-ignore
|
||||
import React from 'react';
|
||||
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
|
||||
@@ -107,7 +107,7 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
};
|
||||
|
||||
const { PRIMARY, TERTIARY } = BUTTON_TYPES;
|
||||
const joinButtonDisabled = isJoining || (!displayName && isDisplayNameMandatory);
|
||||
const joinButtonDisabled = !displayName && isDisplayNameMandatory;
|
||||
|
||||
useEffect(() => {
|
||||
BackHandler.addEventListener('hardwareBackPress', goBack);
|
||||
@@ -143,7 +143,7 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
return (
|
||||
<JitsiScreen
|
||||
addBottomPadding = { false }
|
||||
safeAreaInsets = { [ 'left', 'right' ] }
|
||||
safeAreaInsets = { [ 'right' ] }
|
||||
style = { contentWrapperStyles }>
|
||||
<BrandingImageBackground />
|
||||
{
|
||||
@@ -156,6 +156,7 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
{ roomName }
|
||||
</Text>
|
||||
</View>
|
||||
{/* @ts-ignore */}
|
||||
<LargeVideo />
|
||||
</View>
|
||||
}
|
||||
@@ -179,15 +180,16 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
accessibilityLabel = 'prejoin.joinMeeting'
|
||||
disabled = { joinButtonDisabled }
|
||||
labelKey = 'prejoin.joinMeeting'
|
||||
onClick = { onJoin }
|
||||
// @ts-ignore
|
||||
onClick = { !isJoining && onJoin }
|
||||
style = { styles.joinButton }
|
||||
type = { PRIMARY } />
|
||||
<Button
|
||||
accessibilityLabel = 'prejoin.joinMeetingInLowBandwidthMode'
|
||||
disabled = { joinButtonDisabled }
|
||||
labelKey = 'prejoin.joinMeetingInLowBandwidthMode'
|
||||
labelStyle = { styles.joinLowBandwidthLabel }
|
||||
onClick = { onJoinLowBandwidth }
|
||||
style = { styles.joinButton }
|
||||
type = { TERTIARY } />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -3,13 +3,8 @@ import BaseTheme from '../../base/ui/components/BaseTheme.native';
|
||||
|
||||
export default {
|
||||
joinButton: {
|
||||
marginTop: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
joinLowBandwidthLabel: {
|
||||
color: BaseTheme.palette.text01,
|
||||
marginTop: BaseTheme.spacing[3],
|
||||
textAlign: 'center'
|
||||
width: 352
|
||||
},
|
||||
|
||||
buttonStylesBorderless: {
|
||||
@@ -44,33 +39,33 @@ export default {
|
||||
height: '100%',
|
||||
marginRight: 'auto',
|
||||
position: 'absolute',
|
||||
width: '60%'
|
||||
width: '50%'
|
||||
},
|
||||
|
||||
contentContainer: {
|
||||
alignSelf: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: BaseTheme.palette.uiBackground,
|
||||
bottom: 0,
|
||||
display: 'flex',
|
||||
height: 284,
|
||||
height: 316,
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
width: 390,
|
||||
width: '100%',
|
||||
zIndex: 1
|
||||
},
|
||||
|
||||
contentContainerWide: {
|
||||
alignSelf: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
left: '60%',
|
||||
left: '50%',
|
||||
padding: BaseTheme.spacing[3],
|
||||
position: 'absolute',
|
||||
width: '40%'
|
||||
width: '50%'
|
||||
},
|
||||
|
||||
toolboxContainer: {
|
||||
alignSelf: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: BaseTheme.palette.ui01,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
display: 'flex',
|
||||
@@ -88,13 +83,13 @@ export default {
|
||||
},
|
||||
|
||||
formWrapper: {
|
||||
alignSelf: 'stretch',
|
||||
justifyContent: 'center',
|
||||
marginHorizontal: BaseTheme.spacing[3]
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
customInput: {
|
||||
textAlign: 'center'
|
||||
textAlign: 'center',
|
||||
width: 352
|
||||
},
|
||||
|
||||
preJoinRoomName: {
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
// @ts-ignore
|
||||
import { PC_CON_STATE_CHANGE,
|
||||
PC_STATE_CONNECTED,
|
||||
PC_STATE_FAILED
|
||||
|
||||
// @ts-ignore
|
||||
} from '@jitsi/rtcstats/events';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import rtcstatsInit from '@jitsi/rtcstats/rtcstats';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import traceInit from '@jitsi/rtcstats/trace-ws';
|
||||
|
||||
|
||||
import { createRTCStatsTraceCloseEvent } from '../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../analytics/functions';
|
||||
|
||||
@@ -39,6 +48,7 @@ function connectionFilter(config: any) {
|
||||
class RTCStats {
|
||||
trace: any;
|
||||
initialized = false;
|
||||
connStateEvents: any = [];
|
||||
|
||||
/**
|
||||
* Initialize the rtcstats components. First off we initialize the trace, which is a wrapped websocket
|
||||
@@ -71,7 +81,8 @@ class RTCStats {
|
||||
connectionFilter,
|
||||
pollInterval,
|
||||
useLegacy,
|
||||
sendSdp
|
||||
sendSdp,
|
||||
eventCallback: this.handleRtcstatsEvent.bind(this)
|
||||
};
|
||||
|
||||
this.trace = traceInit(traceOptions);
|
||||
@@ -185,6 +196,40 @@ class RTCStats {
|
||||
this.trace?.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* RTCStats client can notify the APP of any PeerConnection related event that occurs.
|
||||
*
|
||||
* @param {Object} event - Rtcstats event.
|
||||
* @returns {void}
|
||||
*/
|
||||
handleRtcstatsEvent(event: any) {
|
||||
switch (event.type) {
|
||||
case PC_CON_STATE_CHANGE: {
|
||||
const { body: { isP2P, state } = { state: null,
|
||||
isP2P: null } } = event;
|
||||
|
||||
this.connStateEvents.push(event.body);
|
||||
|
||||
// We only report PC related connection issues. If the rtcstats websocket is not connected at this point
|
||||
// it usually means that none of our services can be reached i.e. there's problem with the internet
|
||||
// connection and not necessarily with reaching the JVB (due to a firewall or other reasons).
|
||||
if (state === PC_STATE_FAILED && this.trace.isConnected()) {
|
||||
const connectionType = isP2P ? 'P2P' : 'JVB';
|
||||
const wasConnected = this.connStateEvents.some((connectionEvent: { isP2P: any; state: string; }) =>
|
||||
(connectionEvent.isP2P === isP2P) && (connectionEvent.state === PC_STATE_CONNECTED));
|
||||
|
||||
logger.info(`${connectionType} PeerConnection failed, previously connected: ${wasConnected}`);
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyPeerConnectionFailure(isP2P, wasConnected);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -14,7 +14,8 @@ import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { TRACK_ADDED, TRACK_UPDATED } from '../base/tracks/actionTypes';
|
||||
import { getCurrentRoomId, isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
|
||||
import { ADD_FACE_EXPRESSION } from '../face-landmarks/actionTypes';
|
||||
import { ADD_FACE_LANDMARKS } from '../face-landmarks/actionTypes';
|
||||
import { FaceLandmarks } from '../face-landmarks/types';
|
||||
|
||||
import RTCStats from './RTCStats';
|
||||
import {
|
||||
@@ -164,17 +165,19 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ADD_FACE_EXPRESSION:
|
||||
case ADD_FACE_LANDMARKS: {
|
||||
if (canSendFaceLandmarksRtcstatsData(state)) {
|
||||
const { duration, faceExpression, timestamp } = action;
|
||||
const { duration, faceExpression, timestamp } = action.faceLandmarks as FaceLandmarks;
|
||||
const durationSeconds = Math.round(duration / 1000);
|
||||
|
||||
RTCStats.sendFaceLandmarksData({
|
||||
duration,
|
||||
duration: durationSeconds,
|
||||
faceLandmarks: faceExpression,
|
||||
timestamp
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONFERENCE_TIMESTAMP_CHANGED: {
|
||||
if (canSendRtcstatsData(state)) {
|
||||
const { conferenceTimestamp } = action;
|
||||
|
||||
@@ -63,3 +63,20 @@ export const RESET_SEARCH_CRITERIA = 'RESET_SEARCH_CRITERIA'
|
||||
*/
|
||||
export const TOGGLE_FACE_EXPRESSIONS = 'SHOW_FACE_EXPRESSIONS';
|
||||
|
||||
|
||||
export const INCREASE_ZOOM = 'INCREASE_ZOOM';
|
||||
|
||||
export const DECREASE_ZOOM = 'DECREASE_ZOOM';
|
||||
|
||||
export const ADD_TO_OFFSET = 'ADD_TO_OFFSET';
|
||||
|
||||
export const SET_OFFSET = 'RESET_OFFSET';
|
||||
|
||||
export const ADD_TO_OFFSET_LEFT = 'ADD_TO_OFFSET_LEFT';
|
||||
|
||||
export const ADD_TO_OFFSET_RIGHT = 'ADD_TO_OFFSET_RIGHT';
|
||||
|
||||
export const SET_TIMELINE_BOUNDARY = 'SET_TIMELINE_BOUNDARY';
|
||||
|
||||
export const SET_PANNING = 'SET_PANNING';
|
||||
|
||||
|
||||
231
react/features/speaker-stats/actions.any.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import { IStore } from '../app/types';
|
||||
|
||||
import {
|
||||
ADD_TO_OFFSET,
|
||||
ADD_TO_OFFSET_LEFT,
|
||||
ADD_TO_OFFSET_RIGHT,
|
||||
INIT_REORDER_STATS,
|
||||
INIT_SEARCH,
|
||||
INIT_UPDATE_STATS,
|
||||
RESET_SEARCH_CRITERIA,
|
||||
SET_PANNING,
|
||||
SET_TIMELINE_BOUNDARY,
|
||||
TOGGLE_FACE_EXPRESSIONS,
|
||||
UPDATE_SORTED_SPEAKER_STATS_IDS,
|
||||
UPDATE_STATS
|
||||
} from './actionTypes';
|
||||
import { MINIMUM_INTERVAL } from './constants';
|
||||
import { getCurrentDuration, getTimelineBoundaries } from './functions';
|
||||
import { ISpeakerStats } from './reducer';
|
||||
|
||||
/**
|
||||
* Starts a search by criteria.
|
||||
*
|
||||
* @param {string} criteria - The search criteria.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function initSearch(criteria: string) {
|
||||
return {
|
||||
type: INIT_SEARCH,
|
||||
criteria
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the new stats and triggers update.
|
||||
*
|
||||
* @param {Function} getSpeakerStats - Function to get the speaker stats.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function initUpdateStats(getSpeakerStats: () => ISpeakerStats) {
|
||||
return {
|
||||
type: INIT_UPDATE_STATS,
|
||||
getSpeakerStats
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the stats with new stats.
|
||||
*
|
||||
* @param {Object} stats - The new stats.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function updateStats(stats: Object) {
|
||||
return {
|
||||
type: UPDATE_STATS,
|
||||
stats
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the speaker stats order.
|
||||
*
|
||||
* @param {Array<string>} participantIds - Participant ids.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function updateSortedSpeakerStatsIds(participantIds: Array<string>) {
|
||||
return {
|
||||
type: UPDATE_SORTED_SPEAKER_STATS_IDS,
|
||||
participantIds
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates reordering of the stats.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function initReorderStats() {
|
||||
return {
|
||||
type: INIT_REORDER_STATS
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the search criteria.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function resetSearchCriteria() {
|
||||
return {
|
||||
type: RESET_SEARCH_CRITERIA
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the face expressions grid.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function toggleFaceExpressions() {
|
||||
return {
|
||||
type: TOGGLE_FACE_EXPRESSIONS
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to the boundary offset of the timeline.
|
||||
*
|
||||
* @param {number} value - The value to be added.
|
||||
* @param {number} left - The left boundary.
|
||||
* @param {number} right - The right boundary.
|
||||
* @param {number} currentDuration - The currentDuration of the conference.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function addToOffset(value: number) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const { left, right } = getTimelineBoundaries(state);
|
||||
const currentDuration = getCurrentDuration(state) ?? 0;
|
||||
const newLeft = left + value;
|
||||
const newRight = right + value;
|
||||
|
||||
if (newLeft >= 0 && newRight <= currentDuration) {
|
||||
dispatch({
|
||||
type: ADD_TO_OFFSET,
|
||||
value
|
||||
});
|
||||
} else if (newLeft < 0) {
|
||||
dispatch({
|
||||
type: ADD_TO_OFFSET,
|
||||
value: -left
|
||||
});
|
||||
} else if (newRight > currentDuration) {
|
||||
dispatch({
|
||||
type: ADD_TO_OFFSET,
|
||||
value: currentDuration - right
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the value to the offset of the left boundary for the timeline.
|
||||
*
|
||||
* @param {number} value - The new value for the offset.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function addToOffsetLeft(value: number) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const { left, right } = getTimelineBoundaries(state);
|
||||
const newLeft = left + value;
|
||||
|
||||
if (newLeft >= 0 && right - newLeft > MINIMUM_INTERVAL) {
|
||||
dispatch({
|
||||
type: ADD_TO_OFFSET_LEFT,
|
||||
value
|
||||
});
|
||||
} else if (newLeft < 0) {
|
||||
dispatch({
|
||||
type: ADD_TO_OFFSET_LEFT,
|
||||
value: -left
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the value to the offset of the right boundary for the timeline.
|
||||
*
|
||||
* @param {number} value - The new value for the offset.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function addToOffsetRight(value: number) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const { left, right } = getTimelineBoundaries(state);
|
||||
const currentDuration = getCurrentDuration(state) ?? 0;
|
||||
const newRight = right + value;
|
||||
|
||||
if (newRight <= currentDuration && newRight - left > MINIMUM_INTERVAL) {
|
||||
dispatch({
|
||||
type: ADD_TO_OFFSET_RIGHT,
|
||||
value
|
||||
});
|
||||
} else if (newRight > currentDuration) {
|
||||
dispatch({
|
||||
type: ADD_TO_OFFSET_RIGHT,
|
||||
value: currentDuration - right
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current time boundary of the timeline, when zoomed in.
|
||||
*
|
||||
* @param {number} boundary - The current time boundary.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setTimelineBoundary(boundary: number) {
|
||||
return {
|
||||
type: SET_TIMELINE_BOUNDARY,
|
||||
boundary
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current time boundary of the timeline, when zoomed out full.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function clearTimelineBoundary() {
|
||||
return {
|
||||
type: SET_TIMELINE_BOUNDARY,
|
||||
boundary: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the timeline panning.
|
||||
*
|
||||
* @param {Object} panning - The state of the timeline panning.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setTimelinePanning(panning: { active: boolean; x: number; }) {
|
||||
return {
|
||||
type: SET_PANNING,
|
||||
panning
|
||||
};
|
||||
}
|
||||
1
react/features/speaker-stats/actions.native.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './actions.any';
|
||||
@@ -1,94 +0,0 @@
|
||||
import {
|
||||
INIT_REORDER_STATS,
|
||||
INIT_SEARCH,
|
||||
INIT_UPDATE_STATS,
|
||||
RESET_SEARCH_CRITERIA,
|
||||
TOGGLE_FACE_EXPRESSIONS,
|
||||
UPDATE_SORTED_SPEAKER_STATS_IDS,
|
||||
UPDATE_STATS
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Starts a search by criteria.
|
||||
*
|
||||
* @param {string | null} criteria - The search criteria.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function initSearch(criteria: string | null) {
|
||||
return {
|
||||
type: INIT_SEARCH,
|
||||
criteria
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the new stats and triggers update.
|
||||
*
|
||||
* @param {Function} getSpeakerStats - Function to get the speaker stats.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function initUpdateStats(getSpeakerStats: Function) {
|
||||
return {
|
||||
type: INIT_UPDATE_STATS,
|
||||
getSpeakerStats
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the stats with new stats.
|
||||
*
|
||||
* @param {Object} stats - The new stats.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function updateStats(stats: Object) {
|
||||
return {
|
||||
type: UPDATE_STATS,
|
||||
stats
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the speaker stats order.
|
||||
*
|
||||
* @param {Object} participantIds - Participant ids.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function updateSortedSpeakerStatsIds(participantIds?: Array<string>) {
|
||||
return {
|
||||
type: UPDATE_SORTED_SPEAKER_STATS_IDS,
|
||||
participantIds
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates reordering of the stats.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function initReorderStats() {
|
||||
return {
|
||||
type: INIT_REORDER_STATS
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the search criteria.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function resetSearchCriteria() {
|
||||
return {
|
||||
type: RESET_SEARCH_CRITERIA
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the face expressions grid.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function toggleFaceExpressions() {
|
||||
return {
|
||||
type: TOGGLE_FACE_EXPRESSIONS
|
||||
};
|
||||
}
|
||||
1
react/features/speaker-stats/actions.web.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './actions.any';
|
||||
@@ -1,24 +1,22 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { IconConnection } from '../../base/icons';
|
||||
import { AbstractButton } from '../../base/toolbox/components';
|
||||
import type { AbstractButtonProps } from '../../base/toolbox/components';
|
||||
import { IStore } from '../../app/types';
|
||||
import { IconConnection } from '../../base/icons/svg';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* True if the navigation bar should be visible.
|
||||
*/
|
||||
dispatch: Dispatch<any>
|
||||
dispatch: IStore['dispatch'];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of a button for opening speaker stats dialog.
|
||||
*/
|
||||
class AbstractSpeakerStatsButton extends AbstractButton<Props, *> {
|
||||
class AbstractSpeakerStatsButton extends AbstractButton<Props, any, any> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.speakerStats';
|
||||
icon = IconConnection;
|
||||
label = 'toolbar.speakerStats';
|
||||
@@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { getLocalParticipant } from '../../base/participants';
|
||||
import { initUpdateStats } from '../actions';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { getLocalParticipant } from '../../base/participants/functions';
|
||||
import { initUpdateStats } from '../actions.any';
|
||||
import {
|
||||
SPEAKER_STATS_RELOAD_INTERVAL
|
||||
} from '../constants';
|
||||
@@ -17,21 +16,22 @@ import {
|
||||
* @param {Object} itemStyles - Styles for the speaker stats item.
|
||||
* @returns {Function}
|
||||
*/
|
||||
const abstractSpeakerStatsList = (speakerStatsItem: Function, itemStyles?: Object): Function[] => {
|
||||
const abstractSpeakerStatsList = (speakerStatsItem: Function): Function[] => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const conference = useSelector(state => state['features/base/conference'].conference);
|
||||
const { conference } = useSelector((state: IReduxState) => state['features/base/conference']);
|
||||
const {
|
||||
stats: speakerStats,
|
||||
showFaceExpressions,
|
||||
sortedSpeakerStatsIds
|
||||
} = useSelector(state => state['features/speaker-stats']);
|
||||
} = useSelector((state: IReduxState) => state['features/speaker-stats']);
|
||||
const localParticipant = useSelector(getLocalParticipant);
|
||||
const { defaultRemoteDisplayName } = useSelector(
|
||||
state => state['features/base/config']) || {};
|
||||
const { faceLandmarks } = useSelector(state => state['features/base/config']) || {};
|
||||
const { faceExpressions } = useSelector(state => state['features/face-landmarks']) || {};
|
||||
const reloadInterval = useRef(null);
|
||||
(state: IReduxState) => state['features/base/config']) || {};
|
||||
const { faceLandmarks: faceLandmarksConfig } = useSelector((state: IReduxState) =>
|
||||
state['features/base/config']) || {};
|
||||
const { faceLandmarks } = useSelector((state: IReduxState) => state['features/face-landmarks']) || {};
|
||||
const reloadInterval = useRef<number>();
|
||||
|
||||
/**
|
||||
* Update the internal state with the latest speaker stats.
|
||||
@@ -40,7 +40,7 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function, itemStyles?: Objec
|
||||
* @private
|
||||
*/
|
||||
const getSpeakerStats = useCallback(() => {
|
||||
const stats = conference.getSpeakerStats();
|
||||
const stats = conference?.getSpeakerStats();
|
||||
|
||||
for (const userId in stats) {
|
||||
if (stats[userId]) {
|
||||
@@ -48,40 +48,42 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function, itemStyles?: Objec
|
||||
const meString = t('me');
|
||||
|
||||
stats[userId].setDisplayName(
|
||||
localParticipant.name
|
||||
localParticipant?.name
|
||||
? `${localParticipant.name} (${meString})`
|
||||
: meString
|
||||
);
|
||||
if (faceLandmarks?.enableDisplayFaceExpressions) {
|
||||
stats[userId].setFaceExpressions(faceExpressions);
|
||||
|
||||
if (faceLandmarksConfig?.enableDisplayFaceExpressions) {
|
||||
stats[userId].setFaceLandmarks(faceLandmarks);
|
||||
}
|
||||
}
|
||||
|
||||
if (!stats[userId].getDisplayName()) {
|
||||
stats[userId].setDisplayName(
|
||||
conference.getParticipantById(userId)?.name
|
||||
conference?.getParticipantById(userId)?.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}, [ faceExpressions ]);
|
||||
return stats ?? {};
|
||||
}, [ faceLandmarks ]);
|
||||
|
||||
const updateStats = useCallback(
|
||||
() => dispatch(initUpdateStats(getSpeakerStats)),
|
||||
[ dispatch, initUpdateStats, getSpeakerStats ]);
|
||||
|
||||
useEffect(() => {
|
||||
if (reloadInterval.current) {
|
||||
clearInterval(reloadInterval.current);
|
||||
}
|
||||
reloadInterval.current = setInterval(() => {
|
||||
reloadInterval.current = window.setInterval(() => {
|
||||
updateStats();
|
||||
}, SPEAKER_STATS_RELOAD_INTERVAL);
|
||||
|
||||
return () => clearInterval(reloadInterval.current);
|
||||
}, [ faceExpressions ]);
|
||||
return () => {
|
||||
if (reloadInterval.current) {
|
||||
clearInterval(reloadInterval.current);
|
||||
}
|
||||
};
|
||||
}, [ faceLandmarks ]);
|
||||
|
||||
const localSpeakerStats = Object.keys(speakerStats).length === 0 ? getSpeakerStats() : speakerStats;
|
||||
const localSortedSpeakerStatsIds
|
||||
@@ -91,22 +93,17 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function, itemStyles?: Objec
|
||||
|
||||
return userIds.map(userId => {
|
||||
const statsModel = localSpeakerStats[userId];
|
||||
const props = {};
|
||||
|
||||
props.isDominantSpeaker = statsModel.isDominantSpeaker();
|
||||
props.dominantSpeakerTime = statsModel.getTotalDominantSpeakerTime();
|
||||
props.participantId = userId;
|
||||
props.hasLeft = statsModel.hasLeft();
|
||||
if (showFaceExpressions) {
|
||||
props.faceExpressions = statsModel.getFaceExpressions();
|
||||
}
|
||||
props.hidden = statsModel.hidden;
|
||||
props.showFaceExpressions = showFaceExpressions;
|
||||
props.displayName = statsModel.getDisplayName() || defaultRemoteDisplayName;
|
||||
if (itemStyles) {
|
||||
props.styles = itemStyles;
|
||||
}
|
||||
props.t = t;
|
||||
const props = {
|
||||
isDominantSpeaker: statsModel.isDominantSpeaker(),
|
||||
dominantSpeakerTime: statsModel.getTotalDominantSpeakerTime(),
|
||||
participantId: userId,
|
||||
hasLeft: statsModel.hasLeft(),
|
||||
faceLandmarks: showFaceExpressions ? statsModel.getFaceLandmarks() : undefined,
|
||||
hidden: statsModel.hidden,
|
||||
showFaceExpressions,
|
||||
displayName: statsModel.getDisplayName() || defaultRemoteDisplayName,
|
||||
t
|
||||
};
|
||||
|
||||
return speakerStatsItem(props);
|
||||
});
|
||||
@@ -1 +1,2 @@
|
||||
// @ts-ignore
|
||||
export * from './native';
|
||||
@@ -1 +1,2 @@
|
||||
// @ts-ignore
|
||||
export * from './_';
|
||||
@@ -7,7 +7,7 @@
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
function getHoursCount(milliseconds) {
|
||||
function getHoursCount(milliseconds: number) {
|
||||
return Math.floor(milliseconds / (60 * 60 * 1000));
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ function getHoursCount(milliseconds) {
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
function getMinutesCount(milliseconds) {
|
||||
function getMinutesCount(milliseconds: number) {
|
||||
return Math.floor(milliseconds / (60 * 1000) % 60);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ function getMinutesCount(milliseconds) {
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
function getSecondsCount(milliseconds) {
|
||||
function getSecondsCount(milliseconds: number) {
|
||||
return Math.floor(milliseconds / 1000 % 60);
|
||||
}
|
||||
|
||||
@@ -85,6 +85,6 @@ export function createLocalizedTime(time: number, t: Function) {
|
||||
* key for react to iterate upon.
|
||||
* @returns {string}
|
||||
*/
|
||||
function createTimeDisplay(count, countNounKey, t) {
|
||||
function createTimeDisplay(count: number, countNounKey: string, t: Function) {
|
||||
return t(countNounKey, { count });
|
||||
}
|
||||
@@ -1,15 +1,28 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import {
|
||||
IconEmotionsAngry,
|
||||
IconEmotionsDisgusted,
|
||||
IconEmotionsFearful,
|
||||
IconEmotionsHappy,
|
||||
IconEmotionsNeutral,
|
||||
IconEmotionsSad,
|
||||
IconEmotionsSurprised
|
||||
} from '../../../base/icons/svg';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { Tooltip } from '../../../base/tooltip';
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
import { escapeRegexp } from '../../../base/util/helpers';
|
||||
import { initSearch, resetSearchCriteria, toggleFaceExpressions } from '../../actions';
|
||||
import { initSearch, resetSearchCriteria, toggleFaceExpressions } from '../../actions.any';
|
||||
import {
|
||||
DISPLAY_SWITCH_BREAKPOINT,
|
||||
MOBILE_BREAKPOINT,
|
||||
RESIZE_SEARCH_SWITCH_CONTAINER_BREAKPOINT
|
||||
MOBILE_BREAKPOINT
|
||||
} from '../../constants';
|
||||
|
||||
import FaceExpressionsSwitch from './FaceExpressionsSwitch';
|
||||
@@ -20,69 +33,171 @@ import SpeakerStatsSearch from './SpeakerStatsSearch';
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
speakerStats: {
|
||||
'& .header': {
|
||||
position: 'fixed',
|
||||
backgroundColor: theme.palette.ui01,
|
||||
paddingLeft: theme.spacing(4),
|
||||
paddingRight: theme.spacing(4),
|
||||
marginLeft: `-${theme.spacing(4)}`,
|
||||
'&.large': {
|
||||
width: '616px'
|
||||
},
|
||||
'&.medium': {
|
||||
width: '352px'
|
||||
},
|
||||
'@media (max-width: 448px)': {
|
||||
width: 'calc(100% - 48px) !important'
|
||||
},
|
||||
'& .upper-header': {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
'& .search-switch-container': {
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
'& .search-container': {
|
||||
width: 175,
|
||||
marginRight: theme.spacing(3)
|
||||
},
|
||||
'& .search-container-full-width': {
|
||||
width: '100%'
|
||||
}
|
||||
},
|
||||
'& .emotions-icons': {
|
||||
display: 'flex',
|
||||
'& svg': {
|
||||
fill: '#000'
|
||||
},
|
||||
'&>div': {
|
||||
marginRight: theme.spacing(3)
|
||||
},
|
||||
'&>div:last-child': {
|
||||
marginRight: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'& .row': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
|
||||
'& .avatar': {
|
||||
width: '32px',
|
||||
marginRight: theme.spacing(3)
|
||||
},
|
||||
|
||||
'& .name-time': {
|
||||
width: 'calc(100% - 48px)',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
'&.expressions-on': {
|
||||
width: 'calc(47% - 48px)',
|
||||
marginRight: theme.spacing(4)
|
||||
}
|
||||
},
|
||||
|
||||
'& .name-time_expressions-on': {
|
||||
width: 'calc(47% - 48px)'
|
||||
},
|
||||
|
||||
'& .expressions': {
|
||||
width: 'calc(53% - 29px)',
|
||||
'& .timeline-container': {
|
||||
height: '100%',
|
||||
width: `calc(53% - ${theme.spacing(4)})`,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
|
||||
'& .expression': {
|
||||
width: '30px',
|
||||
textAlign: 'center'
|
||||
alignItems: 'center',
|
||||
borderLeftWidth: 1,
|
||||
borderLeftColor: theme.palette.ui02,
|
||||
borderLeftStyle: 'solid',
|
||||
'& .timeline': {
|
||||
height: theme.spacing(2),
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
'&>div': {
|
||||
marginRight: theme.spacing(1),
|
||||
borderRadius: 5
|
||||
},
|
||||
'&>div:first-child': {
|
||||
borderRadius: '0 5px 5px 0'
|
||||
},
|
||||
'&>div:last-child': {
|
||||
marginRight: 0,
|
||||
borderRadius: '5px 0 0 5px'
|
||||
}
|
||||
}
|
||||
},
|
||||
'& .axis-container': {
|
||||
height: '100%',
|
||||
width: `calc(53% - ${theme.spacing(6)})`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: theme.spacing(3),
|
||||
'& div': {
|
||||
borderRadius: 5
|
||||
},
|
||||
'& .axis': {
|
||||
height: theme.spacing(1),
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
backgroundColor: theme.palette.ui03,
|
||||
position: 'relative',
|
||||
'& .left-bound': {
|
||||
position: 'absolute',
|
||||
bottom: 10,
|
||||
left: 0
|
||||
},
|
||||
'& .right-bound': {
|
||||
position: 'absolute',
|
||||
bottom: 10,
|
||||
right: 0
|
||||
},
|
||||
'& .handler': {
|
||||
position: 'absolute',
|
||||
backgroundColor: theme.palette.ui09,
|
||||
height: 12,
|
||||
marginTop: -4,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
'& .resize': {
|
||||
height: '100%',
|
||||
width: 5,
|
||||
cursor: 'col-resize'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'& .separator': {
|
||||
width: 'calc(100% + 48px)',
|
||||
height: 1,
|
||||
marginLeft: -24,
|
||||
backgroundColor: theme.palette.ui02
|
||||
}
|
||||
},
|
||||
labelsContainer: {
|
||||
position: 'relative'
|
||||
},
|
||||
separator: {
|
||||
position: 'absolute',
|
||||
width: 'calc(100% + 48px)',
|
||||
height: 1,
|
||||
left: -24,
|
||||
backgroundColor: theme.palette.ui05
|
||||
},
|
||||
searchSwitchContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%'
|
||||
},
|
||||
searchSwitchContainerExpressionsOn: {
|
||||
width: '58.5%',
|
||||
[theme.breakpoints.down(RESIZE_SEARCH_SWITCH_CONTAINER_BREAKPOINT)]: {
|
||||
width: '100%'
|
||||
}
|
||||
},
|
||||
searchContainer: {
|
||||
width: '50%'
|
||||
},
|
||||
searchContainerFullWidth: {
|
||||
width: '100%'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const EMOTIONS_LEGEND = [
|
||||
{
|
||||
translationKey: 'speakerStats.neutral',
|
||||
icon: IconEmotionsNeutral
|
||||
},
|
||||
{
|
||||
translationKey: 'speakerStats.happy',
|
||||
icon: IconEmotionsHappy
|
||||
},
|
||||
{
|
||||
translationKey: 'speakerStats.surprised',
|
||||
icon: IconEmotionsSurprised
|
||||
},
|
||||
{
|
||||
translationKey: 'speakerStats.sad',
|
||||
icon: IconEmotionsSad
|
||||
},
|
||||
{
|
||||
translationKey: 'speakerStats.fearful',
|
||||
icon: IconEmotionsFearful
|
||||
},
|
||||
{
|
||||
translationKey: 'speakerStats.angry',
|
||||
icon: IconEmotionsAngry
|
||||
},
|
||||
{
|
||||
translationKey: 'speakerStats.disgusted',
|
||||
icon: IconEmotionsDisgusted
|
||||
}
|
||||
];
|
||||
|
||||
const SpeakerStats = () => {
|
||||
const { faceLandmarks } = useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const { showFaceExpressions } = useSelector((state: IReduxState) => state['features/speaker-stats']);
|
||||
@@ -91,6 +206,7 @@ const SpeakerStats = () => {
|
||||
const displayLabels = clientWidth > MOBILE_BREAKPOINT;
|
||||
const dispatch = useDispatch();
|
||||
const { classes } = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onToggleFaceExpressions = useCallback(() =>
|
||||
dispatch(toggleFaceExpressions())
|
||||
@@ -104,9 +220,9 @@ const SpeakerStats = () => {
|
||||
useEffect(() => {
|
||||
showFaceExpressions && !displaySwitch && dispatch(toggleFaceExpressions());
|
||||
}, [ clientWidth ]);
|
||||
useEffect(() => () => {
|
||||
dispatch(resetSearchCriteria());
|
||||
}, []);
|
||||
|
||||
// @ts-ignore
|
||||
useEffect(() => () => dispatch(resetSearchCriteria()), []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -115,33 +231,49 @@ const SpeakerStats = () => {
|
||||
size = { showFaceExpressions ? 'large' : 'medium' }
|
||||
titleKey = 'speakerStats.speakerStats'>
|
||||
<div className = { classes.speakerStats }>
|
||||
<div
|
||||
className = {
|
||||
`${classes.searchSwitchContainer}
|
||||
${showFaceExpressions ? classes.searchSwitchContainerExpressionsOn : ''}`
|
||||
}>
|
||||
<div
|
||||
className = {
|
||||
displaySwitch
|
||||
? classes.searchContainer
|
||||
: classes.searchContainerFullWidth }>
|
||||
<SpeakerStatsSearch
|
||||
onSearch = { onSearch } />
|
||||
</div>
|
||||
<div className = { `header ${showFaceExpressions ? 'large' : 'medium'}` }>
|
||||
<div className = 'upper-header'>
|
||||
<div
|
||||
className = {
|
||||
`search-switch-container
|
||||
${showFaceExpressions ? 'expressions-on' : ''}`
|
||||
}>
|
||||
<div
|
||||
className = {
|
||||
displaySwitch
|
||||
? 'search-container'
|
||||
: 'search-container-full-width' }>
|
||||
<SpeakerStatsSearch
|
||||
onSearch = { onSearch } />
|
||||
</div>
|
||||
|
||||
{ displaySwitch
|
||||
{ displaySwitch
|
||||
&& <FaceExpressionsSwitch
|
||||
onChange = { onToggleFaceExpressions }
|
||||
showFaceExpressions = { showFaceExpressions } />
|
||||
}
|
||||
</div>
|
||||
{ displayLabels && (
|
||||
<div className = { classes.labelsContainer }>
|
||||
|
||||
}
|
||||
</div>
|
||||
{ showFaceExpressions && <div className = 'emotions-icons'>
|
||||
{
|
||||
EMOTIONS_LEGEND.map(emotion => (
|
||||
<Tooltip
|
||||
content = { t(emotion.translationKey) }
|
||||
key = { emotion.translationKey }
|
||||
position = { 'top' }>
|
||||
<Icon
|
||||
size = { 20 }
|
||||
src = { emotion.icon } />
|
||||
</Tooltip>
|
||||
))
|
||||
}
|
||||
</div>}
|
||||
</div>
|
||||
{ displayLabels && (
|
||||
<SpeakerStatsLabels
|
||||
showFaceExpressions = { showFaceExpressions ?? false } />
|
||||
<div className = { classes.separator } />
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
<SpeakerStatsList />
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../../analytics';
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../analytics/functions';
|
||||
import { openDialog } from '../../../base/dialog/actions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { connect } from '../../../base/redux/functions';
|
||||
import AbstractSpeakerStatsButton from '../AbstractSpeakerStatsButton';
|
||||
|
||||
import { SpeakerStats } from './';
|
||||
import SpeakerStats from './SpeakerStats';
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of a button for opening speaker stats dialog.
|
||||
@@ -20,6 +20,7 @@ class SpeakerStatsButton extends AbstractSpeakerStatsButton {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
// @ts-ignore
|
||||
const { dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('speaker.stats'));
|
||||
@@ -27,4 +28,5 @@ class SpeakerStatsButton extends AbstractSpeakerStatsButton {
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
export default translate(connect()(SpeakerStatsButton));
|
||||
@@ -1,136 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Avatar, StatelessAvatar } from '../../../base/avatar';
|
||||
import { getInitials } from '../../../base/avatar/functions';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme';
|
||||
import { FACE_EXPRESSIONS } from '../../../face-landmarks/constants';
|
||||
|
||||
import TimeElapsed from './TimeElapsed';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link SpeakerStatsItem}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The name of the participant.
|
||||
*/
|
||||
displayName: string,
|
||||
|
||||
/**
|
||||
* The object that has as keys the face expressions of the
|
||||
* participant and as values a number that represents the count .
|
||||
*/
|
||||
faceExpressions: Object,
|
||||
|
||||
/**
|
||||
* True if the face expressions detection is not disabled.
|
||||
*/
|
||||
showFaceExpressions: boolean,
|
||||
|
||||
/**
|
||||
* The total milliseconds the participant has been dominant speaker.
|
||||
*/
|
||||
dominantSpeakerTime: number,
|
||||
|
||||
/**
|
||||
* The id of the user.
|
||||
*/
|
||||
participantId: string,
|
||||
|
||||
/**
|
||||
* True if the participant is no longer in the meeting.
|
||||
*/
|
||||
hasLeft: boolean,
|
||||
|
||||
/**
|
||||
* True if the participant is not shown in speaker stats.
|
||||
*/
|
||||
hidden: boolean,
|
||||
|
||||
/**
|
||||
* True if the participant is currently the dominant speaker.
|
||||
*/
|
||||
isDominantSpeaker: boolean,
|
||||
|
||||
/**
|
||||
* Styles for the item.
|
||||
*/
|
||||
styles: Object,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
}
|
||||
|
||||
const SpeakerStatsItem = (props: Props) => {
|
||||
const hasLeftClass = props.hasLeft ? props.styles.hasLeft : '';
|
||||
const rowDisplayClass = `row ${hasLeftClass} ${props.styles.item}`;
|
||||
const expressionClass = 'expression';
|
||||
const nameTimeClass = `name-time${
|
||||
props.showFaceExpressions ? ' name-time_expressions-on' : ''
|
||||
}`;
|
||||
const timeClass = `${props.styles.time} ${props.isDominantSpeaker ? props.styles.dominant : ''}`;
|
||||
|
||||
|
||||
const FaceExpressions = () => FACE_EXPRESSIONS.map(
|
||||
expression => (
|
||||
<div
|
||||
aria-label = { props.t(`speakerStats.${expression}`) }
|
||||
className = {
|
||||
`${expressionClass} ${
|
||||
props.faceExpressions[expression] === 0 ? props.styles.hasLeft : ''
|
||||
}`
|
||||
}
|
||||
key = { expression }>
|
||||
{ props.faceExpressions[expression] }
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { rowDisplayClass }
|
||||
key = { props.participantId } >
|
||||
<div className = { `avatar ${props.styles.avatar}` }>
|
||||
{
|
||||
props.hasLeft ? (
|
||||
<StatelessAvatar
|
||||
className = 'userAvatar'
|
||||
color = { BaseTheme.palette.ui04 }
|
||||
id = 'avatar'
|
||||
initials = { getInitials(props.displayName) } />
|
||||
) : (
|
||||
<Avatar
|
||||
className = 'userAvatar'
|
||||
participantId = { props.participantId } />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className = { nameTimeClass }>
|
||||
<div
|
||||
aria-label = { props.t('speakerStats.speakerStats') }
|
||||
className = { props.styles.displayName }>
|
||||
{ props.displayName }
|
||||
</div>
|
||||
<div
|
||||
aria-label = { props.t('speakerStats.speakerTime') }
|
||||
className = { timeClass }>
|
||||
<TimeElapsed
|
||||
time = { props.dominantSpeakerTime } />
|
||||
</div>
|
||||
</div>
|
||||
{ props.showFaceExpressions
|
||||
&& (
|
||||
<div className = { `expressions ${props.styles.expressions}` }>
|
||||
<FaceExpressions />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeakerStatsItem;
|
||||
115
react/features/speaker-stats/components/web/SpeakerStatsItem.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
import React from 'react';
|
||||
|
||||
// @ts-ignore
|
||||
import Avatar from '../../../base/avatar/components/Avatar';
|
||||
import StatelessAvatar from '../../../base/avatar/components/web/StatelessAvatar';
|
||||
import { getInitials } from '../../../base/avatar/functions';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.web';
|
||||
import { FaceLandmarks } from '../../../face-landmarks/types';
|
||||
|
||||
import TimeElapsed from './TimeElapsed';
|
||||
import Timeline from './Timeline';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link SpeakerStatsItem}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The name of the participant.
|
||||
*/
|
||||
displayName: string;
|
||||
|
||||
/**
|
||||
* The total milliseconds the participant has been dominant speaker.
|
||||
*/
|
||||
dominantSpeakerTime: number;
|
||||
|
||||
/**
|
||||
* The object that has as keys the face expressions of the
|
||||
* participant and as values a number that represents the count .
|
||||
*/
|
||||
faceLandmarks?: FaceLandmarks[];
|
||||
|
||||
/**
|
||||
* True if the participant is no longer in the meeting.
|
||||
*/
|
||||
hasLeft: boolean;
|
||||
|
||||
/**
|
||||
* True if the participant is not shown in speaker stats.
|
||||
*/
|
||||
hidden: boolean;
|
||||
|
||||
/**
|
||||
* True if the participant is currently the dominant speaker.
|
||||
*/
|
||||
isDominantSpeaker: boolean;
|
||||
|
||||
/**
|
||||
* The id of the user.
|
||||
*/
|
||||
participantId: string;
|
||||
|
||||
/**
|
||||
* True if the face expressions detection is not disabled.
|
||||
*/
|
||||
showFaceExpressions: boolean;
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function;
|
||||
};
|
||||
|
||||
const SpeakerStatsItem = (props: Props) => {
|
||||
const rowDisplayClass = `row item ${props.hasLeft ? 'has-left' : ''}`;
|
||||
const nameTimeClass = `name-time${
|
||||
props.showFaceExpressions ? ' expressions-on' : ''
|
||||
}`;
|
||||
const timeClass = `time ${props.isDominantSpeaker ? 'dominant' : ''}`;
|
||||
|
||||
return (
|
||||
<div key = { props.participantId }>
|
||||
<div className = { rowDisplayClass } >
|
||||
<div className = 'avatar' >
|
||||
{
|
||||
props.hasLeft ? (
|
||||
<StatelessAvatar
|
||||
className = 'userAvatar'
|
||||
color = { BaseTheme.palette.ui04 }
|
||||
initials = { getInitials(props.displayName) } />
|
||||
) : (
|
||||
<Avatar
|
||||
|
||||
// @ts-ignore
|
||||
className = 'userAvatar'
|
||||
participantId = { props.participantId } />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className = { nameTimeClass }>
|
||||
<div
|
||||
aria-label = { props.t('speakerStats.speakerStats') }
|
||||
className = 'display-name'>
|
||||
{ props.displayName }
|
||||
</div>
|
||||
<div
|
||||
aria-label = { props.t('speakerStats.speakerTime') }
|
||||
className = { timeClass }>
|
||||
<TimeElapsed
|
||||
time = { props.dominantSpeakerTime } />
|
||||
</div>
|
||||
</div>
|
||||
{ props.showFaceExpressions
|
||||
&& <Timeline faceLandmarks = { props.faceLandmarks } />
|
||||
}
|
||||
|
||||
</div>
|
||||
<div className = 'separator' />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeakerStatsItem;
|
||||
@@ -2,21 +2,18 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { Tooltip } from '../../../base/tooltip';
|
||||
import { FACE_EXPRESSIONS_EMOJIS } from '../../../face-landmarks/constants';
|
||||
import TimelineAxis from './TimelineAxis';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
labels: {
|
||||
padding: '22px 0 7px 0',
|
||||
height: 20
|
||||
},
|
||||
emojis: {
|
||||
paddingLeft: 27,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
height: 20,
|
||||
'& .avatar-placeholder': {
|
||||
width: '32px',
|
||||
marginRight: theme.spacing(3)
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -36,12 +33,12 @@ const SpeakerStatsLabels = (props: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { classes } = useStyles();
|
||||
const nameTimeClass = `name-time${
|
||||
props.showFaceExpressions ? ' name-time_expressions-on' : ''
|
||||
props.showFaceExpressions ? ' expressions-on' : ''
|
||||
}`;
|
||||
|
||||
return (
|
||||
<div className = { `row ${classes.labels}` }>
|
||||
<div className = 'avatar' />
|
||||
<div className = 'avatar-placeholder' />
|
||||
|
||||
<div className = { nameTimeClass }>
|
||||
<div>
|
||||
@@ -51,27 +48,7 @@ const SpeakerStatsLabels = (props: IProps) => {
|
||||
{ t('speakerStats.speakerTime') }
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
props.showFaceExpressions
|
||||
&& <div className = { `expressions ${classes.emojis}` }>
|
||||
{Object.keys(FACE_EXPRESSIONS_EMOJIS).map(
|
||||
expression => (
|
||||
<div
|
||||
className = 'expression'
|
||||
key = { expression }>
|
||||
<Tooltip
|
||||
content = { t(`speakerStats.${expression}`) }
|
||||
position = { 'top' } >
|
||||
<div>
|
||||
{FACE_EXPRESSIONS_EMOJIS[expression as keyof typeof FACE_EXPRESSIONS_EMOJIS]}
|
||||
</div>
|
||||
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
{props.showFaceExpressions && <TimelineAxis />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,40 +13,40 @@ import SpeakerStatsItem from './SpeakerStatsItem';
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
list: {
|
||||
marginTop: theme.spacing(3),
|
||||
marginBottom: theme.spacing(3)
|
||||
},
|
||||
item: {
|
||||
height: theme.spacing(7),
|
||||
[theme.breakpoints.down(MOBILE_BREAKPOINT)]: {
|
||||
height: theme.spacing(8)
|
||||
paddingTop: 90,
|
||||
'& .item': {
|
||||
height: theme.spacing(7),
|
||||
[theme.breakpoints.down(MOBILE_BREAKPOINT)]: {
|
||||
height: theme.spacing(8)
|
||||
},
|
||||
'& .has-left': {
|
||||
color: theme.palette.text03
|
||||
},
|
||||
'& .avatar': {
|
||||
width: '32px',
|
||||
marginRight: theme.spacing(3),
|
||||
height: theme.spacing(5)
|
||||
},
|
||||
'& .time': {
|
||||
padding: '2px 4px',
|
||||
borderRadius: '4px',
|
||||
...withPixelLineHeight(theme.typography.labelBold),
|
||||
[theme.breakpoints.down(MOBILE_BREAKPOINT)]: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
},
|
||||
backgroundColor: theme.palette.ui02
|
||||
},
|
||||
'& .display-name': {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
[theme.breakpoints.down(MOBILE_BREAKPOINT)]: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
}
|
||||
},
|
||||
'& .dominant': {
|
||||
backgroundColor: theme.palette.success02
|
||||
}
|
||||
}
|
||||
},
|
||||
avatar: {
|
||||
height: theme.spacing(5)
|
||||
},
|
||||
expressions: {
|
||||
paddingLeft: 29
|
||||
},
|
||||
hasLeft: {
|
||||
color: theme.palette.text03
|
||||
},
|
||||
displayName: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
[theme.breakpoints.down(MOBILE_BREAKPOINT)]: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
}
|
||||
},
|
||||
time: {
|
||||
padding: '2px 4px',
|
||||
borderRadius: '4px',
|
||||
...withPixelLineHeight(theme.typography.labelBold),
|
||||
[theme.breakpoints.down(MOBILE_BREAKPOINT)]: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
}
|
||||
},
|
||||
dominant: {
|
||||
backgroundColor: theme.palette.success02
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -58,10 +58,11 @@ const useStyles = makeStyles()(theme => {
|
||||
*/
|
||||
const SpeakerStatsList = () => {
|
||||
const { classes } = useStyles();
|
||||
const items = abstractSpeakerStatsList(SpeakerStatsItem, classes);
|
||||
const items = abstractSpeakerStatsList(SpeakerStatsItem);
|
||||
|
||||
return (
|
||||
<div className = { classes.list }>
|
||||
<div className = 'separator' />
|
||||
{items}
|
||||
</div>
|
||||
);
|
||||
|
||||