mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-09-03 09:17:46 +00:00
Compare commits
1 Commits
join-muc-t
...
visitors-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a24ddf205c |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -103,7 +103,7 @@ jobs:
|
||||
android-sdk-build:
|
||||
name: Build mobile SDK (Android)
|
||||
runs-on: ubuntu-latest
|
||||
container: reactnativecommunity/react-native-android:v18.0
|
||||
container: reactnativecommunity/react-native-android:v13.0
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
// Crashlytics integration is done as part of Firebase now, so it gets
|
||||
// automagically activated with google-services.json
|
||||
@@ -67,18 +66,9 @@ android {
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility rootProject.ext.javaVersion
|
||||
targetCompatibility rootProject.ext.javaVersion
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = rootProject.ext.jvmTargetVersion
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(rootProject.ext.jvmToolchainVersion)
|
||||
}
|
||||
|
||||
namespace 'org.jitsi.meet'
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,3 @@
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-keep public class * extends java.lang.Exception
|
||||
|
||||
# R8 missing classes - suppress warnings
|
||||
-dontwarn com.facebook.fresco.ui.common.LoggingListener
|
||||
-dontwarn com.facebook.memory.config.MemorySpikeConfig
|
||||
-dontwarn kotlinx.parcelize.Parcelize
|
||||
|
||||
@@ -5,52 +5,46 @@ import org.gradle.util.VersionNumber
|
||||
// sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
kotlinVersion = "2.0.21"
|
||||
gradlePluginVersion = "8.4.2"
|
||||
buildToolsVersion = "34.0.0"
|
||||
compileSdkVersion = 34
|
||||
minSdkVersion = 26
|
||||
targetSdkVersion = 34
|
||||
supportLibVersion = "28.0.0"
|
||||
ndkVersion = "27.1.12297006"
|
||||
|
||||
// The Maven artifact groupId of the third-party react-native modules which
|
||||
// Jitsi Meet SDK for Android depends on and which are not available in
|
||||
// third-party Maven repositories so we have to deploy to a Maven repository
|
||||
// of ours.
|
||||
moduleGroupId = 'com.facebook.react'
|
||||
|
||||
// Maven repo where artifacts will be published
|
||||
mavenRepo = System.env.MVN_REPO ?: ""
|
||||
mavenUser = System.env.MVN_USER ?: ""
|
||||
mavenPassword = System.env.MVN_PASSWORD ?: ""
|
||||
|
||||
// Libre build
|
||||
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
|
||||
|
||||
googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild
|
||||
|
||||
//React Native and Hermes Version
|
||||
rnVersion = "0.77.2"
|
||||
|
||||
// Java dependencies
|
||||
javaVersion = JavaVersion.VERSION_17
|
||||
jvmToolchainVersion = 17
|
||||
jvmTargetVersion = '17'
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.kotlinVersion"
|
||||
classpath "com.android.tools.build:gradle:$rootProject.ext.gradlePluginVersion"
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.google.gms:google-services:4.4.0'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
kotlinVersion = "1.9.24"
|
||||
buildToolsVersion = "34.0.0"
|
||||
compileSdkVersion = 34
|
||||
minSdkVersion = 26
|
||||
targetSdkVersion = 34
|
||||
supportLibVersion = "28.0.0"
|
||||
ndkVersion = "26.1.10909125"
|
||||
|
||||
// The Maven artifact groupId of the third-party react-native modules which
|
||||
// Jitsi Meet SDK for Android depends on and which are not available in
|
||||
// third-party Maven repositories so we have to deploy to a Maven repository
|
||||
// of ours.
|
||||
moduleGroupId = 'com.facebook.react'
|
||||
|
||||
// Maven repo where artifacts will be published
|
||||
mavenRepo = System.env.MVN_REPO ?: ""
|
||||
mavenUser = System.env.MVN_USER ?: ""
|
||||
mavenPassword = System.env.MVN_PASSWORD ?: ""
|
||||
|
||||
// Libre build
|
||||
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
|
||||
|
||||
googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild
|
||||
|
||||
//React Native and Hermes Version
|
||||
rnVersion = "0.75.5"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@@ -140,7 +134,7 @@ allprojects {
|
||||
project.version = "${json.version}-jitsi-${versionQualifierNumber}"
|
||||
|
||||
task jitsiAndroidSourcesJar(type: Jar) {
|
||||
archiveClassifier = 'sources'
|
||||
classifier = 'sources'
|
||||
from android.sourceSets.main.java.source
|
||||
}
|
||||
|
||||
@@ -191,46 +185,16 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
// Force the version of the Android build tools we have chosen on all subprojects.
|
||||
// Force the version of the Android build tools we have chosen on all
|
||||
// subprojects. The forcing was introduced for react-native and the third-party
|
||||
// modules that we utilize such as react-native-background-timer.
|
||||
subprojects { subproject ->
|
||||
afterEvaluate{
|
||||
if ((subproject.plugins.hasPlugin('android')
|
||||
|| subproject.plugins.hasPlugin('android-library'))
|
||||
&& rootProject.ext.has('buildToolsVersion')) {
|
||||
|
||||
android {
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
// Set JVM target across all subprojects
|
||||
compileOptions {
|
||||
sourceCompatibility rootProject.ext.javaVersion
|
||||
targetCompatibility rootProject.ext.javaVersion
|
||||
}
|
||||
|
||||
// Disable lint errors for problematic third-party modules
|
||||
// react-native-background-timer
|
||||
// react-native-calendar-events
|
||||
lint {
|
||||
abortOnError = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add Kotlin configuration for subprojects that use Kotlin
|
||||
if (subproject.plugins.hasPlugin('kotlin-android')) {
|
||||
subproject.kotlin {
|
||||
jvmToolchain(rootProject.ext.jvmToolchainVersion)
|
||||
}
|
||||
|
||||
// Set Kotlin JVM target
|
||||
subproject.android {
|
||||
kotlinOptions {
|
||||
jvmTarget = rootProject.ext.jvmTargetVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,26 +11,20 @@
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx1024m -XX:MaxPermSize=256m
|
||||
|
||||
org.gradle.jvmargs=-Xmx4048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# This one fixes a weird WebRTC runtime problem on some devices.
|
||||
# https://github.com/jitsi/jitsi-meet/issues/7911#issuecomment-714323255
|
||||
android.enableDexingArtifactTransform.desugaring=false
|
||||
|
||||
android.useAndroidX=true
|
||||
|
||||
android.enableJetifier=true
|
||||
|
||||
# Use this property to enable support to the new architecture.
|
||||
# This will allow you to use TurboModules and the Fabric render in
|
||||
# your application. You should enable this flag either if you want
|
||||
# to write custom TurboModules/Fabric components OR use libraries that
|
||||
# are providing them.
|
||||
newArchEnabled=false
|
||||
|
||||
# Use this property to enable or disable the Hermes JS engine.
|
||||
hermesEnabled=true
|
||||
android.bundle.enableUncompressedNativeLibs=false
|
||||
|
||||
appVersion=99.0.0
|
||||
sdkVersion=0.0.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -50,7 +50,6 @@ dependencies {
|
||||
implementation 'com.squareup.duktape:duktape-android:1.3.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'androidx.startup:startup-runtime:1.1.0'
|
||||
implementation 'com.google.j2objc:j2objc-annotations:3.0.0'
|
||||
|
||||
// Only add these packages if we are NOT doing a LIBRE_BUILD
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
@@ -84,7 +83,7 @@ dependencies {
|
||||
implementation project(':react-native-screens')
|
||||
implementation project(':react-native-slider')
|
||||
implementation project(':react-native-sound')
|
||||
implementation project(':react-native-splash-view')
|
||||
implementation project(':react-native-splash-screen')
|
||||
implementation project(':react-native-svg')
|
||||
implementation project(':react-native-video')
|
||||
implementation project(':react-native-webview')
|
||||
@@ -138,16 +137,8 @@ android.libraryVariants.all { def variant ->
|
||||
def devEnabled = !targetName.toLowerCase().contains("release")
|
||||
|
||||
// Run the bundler
|
||||
// Use full path to node to avoid PATH issues in Gradle
|
||||
def nodePath = System.getenv('NVM_BIN') ? "${System.getenv('NVM_BIN')}/node" : "node"
|
||||
|
||||
// Debug: Print the node path and environment
|
||||
println "Using node path: ${nodePath}"
|
||||
println "NVM_BIN: ${System.getenv('NVM_BIN')}"
|
||||
println "Working directory: ${reactRoot}"
|
||||
|
||||
commandLine(
|
||||
nodePath,
|
||||
"node",
|
||||
"node_modules/react-native/scripts/bundle.js",
|
||||
"--platform", "android",
|
||||
"--dev", "${devEnabled}",
|
||||
@@ -160,70 +151,6 @@ android.libraryVariants.all { def variant ->
|
||||
enabled !devEnabled
|
||||
}
|
||||
|
||||
// GRADLE REQUIREMENTS (Gradle 8.7+ / AGP 8.5.0+):
|
||||
|
||||
// This task requires explicit dependencies on resource tasks from all React Native modules
|
||||
// due to Gradle's strict validation of task dependencies.
|
||||
|
||||
// Without these dependencies,
|
||||
// builds will fail with errors like:
|
||||
// "Task ':sdk:bundleReleaseJsAndAssets' uses the output of task ':react-native-amplitude:packageReleaseResources'
|
||||
// without declaring a dependency on it."
|
||||
|
||||
// The automatic dependency resolution below ensures all required resource tasks are properly
|
||||
// declared as dependencies before this task executes.
|
||||
|
||||
if (variant.name.toLowerCase().contains("release")) {
|
||||
rootProject.subprojects.each { subproject ->
|
||||
if (
|
||||
subproject.name.startsWith("react-native-") ||
|
||||
subproject.name.startsWith("@react-native-") ||
|
||||
subproject.name.startsWith("@giphy/")
|
||||
) {
|
||||
[
|
||||
"packageReleaseResources",
|
||||
"generateReleaseResValues",
|
||||
"generateReleaseResources",
|
||||
"generateReleaseBuildConfig",
|
||||
"processReleaseManifest",
|
||||
"writeReleaseAarMetadata",
|
||||
"generateReleaseRFile",
|
||||
"compileReleaseLibraryResources",
|
||||
"compileReleaseJavaWithJavac",
|
||||
"javaPreCompileRelease",
|
||||
"bundleLibCompileToJarRelease",
|
||||
"exportReleaseConsumerProguardFiles",
|
||||
"mergeReleaseGeneratedProguardFiles",
|
||||
"mergeReleaseJniLibFolders",
|
||||
"mergeReleaseShaders",
|
||||
"packageReleaseAssets",
|
||||
"processReleaseJavaRes",
|
||||
"prepareReleaseArtProfile",
|
||||
"copyReleaseJniLibsProjectOnly",
|
||||
"extractDeepLinksRelease",
|
||||
"createFullJarRelease",
|
||||
"generateReleaseLintModel",
|
||||
"writeReleaseLintModelMetadata",
|
||||
"generateReleaseLintVitalModel",
|
||||
"lintVitalAnalyzeRelease",
|
||||
"lintReportRelease",
|
||||
"lintAnalyzeRelease",
|
||||
"lintReportDebug",
|
||||
"lintAnalyzeDebug"
|
||||
].each { taskName ->
|
||||
if (subproject.tasks.findByName(taskName)) {
|
||||
currentBundleTask.dependsOn(subproject.tasks.named(taskName))
|
||||
}
|
||||
}
|
||||
|
||||
// Also depend on the main build task to ensure all sub-tasks are completed
|
||||
if (subproject.tasks.findByName("build")) {
|
||||
currentBundleTask.dependsOn(subproject.tasks.named("build"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
|
||||
currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
|
||||
variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
|
||||
|
||||
@@ -23,10 +23,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.startup.Initializer;
|
||||
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.facebook.react.soloader.OpenSourceMergedSoMapping;
|
||||
import org.wonday.orientation.OrientationActivityLifecycle;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -37,11 +35,7 @@ public class JitsiInitializer implements Initializer<Boolean> {
|
||||
public Boolean create(@NonNull Context context) {
|
||||
Log.d(this.getClass().getCanonicalName(), "create");
|
||||
|
||||
try {
|
||||
SoLoader.init(context, OpenSourceMergedSoMapping.INSTANCE);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
SoLoader.init(context, /* native exopackage */ false);
|
||||
|
||||
// Register our uncaught exception handler.
|
||||
JitsiMeetUncaughtExceptionHandler.register();
|
||||
@@ -49,10 +43,6 @@ public class JitsiInitializer implements Initializer<Boolean> {
|
||||
// Register activity lifecycle handler for the orientation locker module.
|
||||
((Application) context).registerActivityLifecycleCallbacks(OrientationActivityLifecycle.getInstance());
|
||||
|
||||
// Initialize ReactInstanceManager during application startup
|
||||
// This ensures it's ready before any Activity onCreate is called
|
||||
ReactInstanceManagerHolder.initReactInstanceManager((Application) context);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
|
||||
import com.splashview.SplashView;
|
||||
import org.devio.rn.splashscreen.SplashScreen;
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
public class JitsiMeet {
|
||||
@@ -92,7 +92,7 @@ public class JitsiMeet {
|
||||
*/
|
||||
public static void showSplashScreen(Activity activity) {
|
||||
try {
|
||||
SplashView.INSTANCE.showSplashView(activity);
|
||||
SplashScreen.show(activity);
|
||||
} catch (Exception e) {
|
||||
JitsiMeetLogger.e(e, "Failed to show splash screen");
|
||||
}
|
||||
|
||||
@@ -102,10 +102,6 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// ReactInstanceManager is now initialized by JitsiInitializer during application startup
|
||||
// Just call onHostResume since the manager is already ready
|
||||
JitsiMeetActivityDelegate.onHostResume(this);
|
||||
|
||||
setContentView(R.layout.activity_jitsi_meet);
|
||||
this.jitsiView = findViewById(R.id.jitsiView);
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
@@ -197,6 +196,8 @@ public class JitsiMeetView extends FrameLayout {
|
||||
}
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
ReactInstanceManagerHolder.initReactInstanceManager((Activity) context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -34,6 +33,7 @@ import com.facebook.react.uimanager.ViewManager;
|
||||
import com.oney.WebRTCModule.EglUtils;
|
||||
import com.oney.WebRTCModule.WebRTCModuleOptions;
|
||||
|
||||
import org.devio.rn.splashscreen.SplashScreenModule;
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
import org.webrtc.EglBase;
|
||||
|
||||
@@ -68,6 +68,7 @@ class ReactInstanceManagerHolder {
|
||||
new JavaScriptSandboxModule(reactContext),
|
||||
new LocaleDetector(reactContext),
|
||||
new LogBridgeModule(reactContext),
|
||||
new SplashScreenModule(reactContext),
|
||||
new PictureInPictureModule(reactContext),
|
||||
new ProximityModule(reactContext),
|
||||
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)));
|
||||
@@ -89,7 +90,7 @@ class ReactInstanceManagerHolder {
|
||||
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
|
||||
new com.ocetnik.timer.BackgroundTimerPackage(),
|
||||
new com.calendarevents.RNCalendarEventsPackage(),
|
||||
new com.sayem.keepawake.KCKeepAwakePackage(),
|
||||
new com.corbt.keepawake.KCKeepAwakePackage(),
|
||||
new com.facebook.react.shell.MainReactPackage(),
|
||||
new com.reactnativecommunity.clipboard.ClipboardPackage(),
|
||||
new com.reactnativecommunity.netinfo.NetInfoPackage(),
|
||||
@@ -109,7 +110,6 @@ class ReactInstanceManagerHolder {
|
||||
new com.th3rdwave.safeareacontext.SafeAreaContextPackage(),
|
||||
new com.horcrux.svg.SvgPackage(),
|
||||
new org.wonday.orientation.OrientationPackage(),
|
||||
new com.splashview.SplashViewPackage(),
|
||||
new ReactPackageAdapter() {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
@@ -133,7 +133,7 @@ class ReactInstanceManagerHolder {
|
||||
|
||||
// GiphyReactNativeSdkPackage
|
||||
try {
|
||||
Class<?> giphyPackageClass = Class.forName("com.giphyreactnativesdk.RTNGiphySdkPackage");
|
||||
Class<?> giphyPackageClass = Class.forName("com.giphyreactnativesdk.GiphyReactNativeSdkPackage");
|
||||
Constructor<?> constructor = giphyPackageClass.getConstructor();
|
||||
packages.add((ReactPackage)constructor.newInstance());
|
||||
} catch (Exception e) {
|
||||
@@ -208,9 +208,9 @@ class ReactInstanceManagerHolder {
|
||||
* time. All {@code ReactRootView} instances will be tied to the one and
|
||||
* only {@code ReactInstanceManager}.
|
||||
*
|
||||
* @param app {@code Application}
|
||||
* @param activity {@code Activity} current running Activity.
|
||||
*/
|
||||
static void initReactInstanceManager(Application app) {
|
||||
static void initReactInstanceManager(Activity activity) {
|
||||
if (reactInstanceManager != null) {
|
||||
return;
|
||||
}
|
||||
@@ -232,14 +232,14 @@ class ReactInstanceManagerHolder {
|
||||
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
.setApplication(app)
|
||||
.setCurrentActivity(null)
|
||||
.setApplication(activity.getApplication())
|
||||
.setCurrentActivity(activity)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.setJavaScriptExecutorFactory(new HermesExecutorFactory())
|
||||
.addPackages(getReactNativePackages())
|
||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
|
||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
rootProject.name = 'jitsi-meet'
|
||||
|
||||
include ':app', ':sdk'
|
||||
|
||||
include ':react-native-amplitude'
|
||||
@@ -27,7 +29,7 @@ project(':react-native-google-signin').projectDir = new File(rootProject.project
|
||||
include ':react-native-immersive-mode'
|
||||
project(':react-native-immersive-mode').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive-mode/android')
|
||||
include ':react-native-keep-awake'
|
||||
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/@sayem314/react-native-keep-awake/android')
|
||||
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
|
||||
include ':react-native-orientation-locker'
|
||||
project(':react-native-orientation-locker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-orientation-locker/android')
|
||||
include ':react-native-pager-view'
|
||||
@@ -42,8 +44,8 @@ include ':react-native-slider'
|
||||
project(':react-native-slider').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/slider/android')
|
||||
include ':react-native-sound'
|
||||
project(':react-native-sound').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sound/android')
|
||||
include ':react-native-splash-view'
|
||||
project(':react-native-splash-view').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-view/android')
|
||||
include ':react-native-splash-screen'
|
||||
project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android')
|
||||
include ':react-native-svg'
|
||||
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
|
||||
include ':react-native-video'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [ 'module:@react-native/babel-preset' ],
|
||||
presets: [ 'module:metro-react-native-babel-preset' ],
|
||||
env: {
|
||||
production: {
|
||||
plugins: [ 'react-native-paper/babel' ]
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
maybeRedirectToWelcomePage,
|
||||
reloadWithStoredParams
|
||||
} from './react/features/app/actions';
|
||||
import { showModeratedNotification } from './react/features/av-moderation/actions';
|
||||
import { shouldShowModeratedNotification } from './react/features/av-moderation/functions';
|
||||
import {
|
||||
_conferenceWillJoin,
|
||||
authStatusChanged,
|
||||
@@ -50,8 +52,7 @@ import {
|
||||
commonUserJoinedHandling,
|
||||
commonUserLeftHandling,
|
||||
getConferenceOptions,
|
||||
sendLocalParticipant,
|
||||
updateTrackMuteState
|
||||
sendLocalParticipant
|
||||
} from './react/features/base/conference/functions';
|
||||
import { getReplaceParticipant, getSsrcRewritingFeatureFlag } from './react/features/base/config/functions';
|
||||
import { connect } from './react/features/base/connection/actions.web';
|
||||
@@ -152,6 +153,7 @@ import {
|
||||
DATA_CHANNEL_CLOSED_NOTIFICATION_ID,
|
||||
NOTIFICATION_TIMEOUT_TYPE
|
||||
} from './react/features/notifications/constants';
|
||||
import { isModerationNotificationDisplayed } from './react/features/notifications/functions';
|
||||
import { suspendDetected } from './react/features/power-monitor/actions';
|
||||
import { initPrejoin, isPrejoinPageVisible } from './react/features/prejoin/functions';
|
||||
import { disableReceiver, stopReceiver } from './react/features/remote-control/actions';
|
||||
@@ -702,6 +704,15 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for A/V Moderation when trying to unmute
|
||||
if (!mute && shouldShowModeratedNotification(MEDIA_TYPE.AUDIO, state)) {
|
||||
if (!isModerationNotificationDisplayed(MEDIA_TYPE.AUDIO, state)) {
|
||||
APP.store.dispatch(showModeratedNotification(MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await APP.store.dispatch(setAudioMuted(mute, true));
|
||||
},
|
||||
|
||||
@@ -735,6 +746,12 @@ export default {
|
||||
* dialogs in case of media permissions error.
|
||||
*/
|
||||
muteVideo(mute) {
|
||||
if (this.videoSwitchInProgress) {
|
||||
logger.warn('muteVideo - unable to perform operations while video switch is in progress');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const state = APP.store.getState();
|
||||
|
||||
if (!mute
|
||||
@@ -744,6 +761,11 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for A/V Moderation when trying to unmute and return early
|
||||
if (!mute && shouldShowModeratedNotification(MEDIA_TYPE.VIDEO, state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(setVideoMuted(mute, VIDEO_MUTISM_AUTHORITY.USER, true));
|
||||
},
|
||||
|
||||
@@ -997,6 +1019,7 @@ export default {
|
||||
|
||||
// Restore initial state.
|
||||
this._localTracksInitialized = false;
|
||||
this.isSharingScreen = false;
|
||||
this.roomName = roomName;
|
||||
|
||||
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(options);
|
||||
@@ -1175,6 +1198,8 @@ export default {
|
||||
return Boolean(APP.store.getState()['features/base/audio-only'].enabled);
|
||||
},
|
||||
|
||||
videoSwitchInProgress: false,
|
||||
|
||||
/**
|
||||
* This fields stores a handler which will create a Promise which turns off
|
||||
* the screen sharing and restores the previous video state (was there
|
||||
@@ -1203,6 +1228,7 @@ export default {
|
||||
*/
|
||||
async _turnScreenSharingOff(didHaveVideo, ignoreDidHaveVideo) {
|
||||
this._untoggleScreenSharing = null;
|
||||
this.videoSwitchInProgress = true;
|
||||
|
||||
APP.store.dispatch(stopReceiver());
|
||||
|
||||
@@ -1254,11 +1280,13 @@ export default {
|
||||
|
||||
return promise.then(
|
||||
() => {
|
||||
this.videoSwitchInProgress = false;
|
||||
sendAnalytics(createScreenSharingEvent('stopped',
|
||||
duration === 0 ? null : duration));
|
||||
logger.info('Screen sharing stopped.');
|
||||
},
|
||||
error => {
|
||||
this.videoSwitchInProgress = false;
|
||||
logger.error(`_turnScreenSharingOff failed: ${error}`);
|
||||
|
||||
throw error;
|
||||
@@ -1288,13 +1316,14 @@ export default {
|
||||
this._untoggleScreenSharing
|
||||
= this._turnScreenSharingOff.bind(this, didHaveVideo);
|
||||
|
||||
const desktopVideoStream = desktopStreams.find(stream => stream.getType() === MEDIA_TYPE.VIDEO);
|
||||
const desktopAudioStream = desktopStreams.find(stream => stream.getType() === MEDIA_TYPE.AUDIO);
|
||||
|
||||
if (desktopAudioStream) {
|
||||
desktopAudioStream.on(
|
||||
JitsiTrackEvents.LOCAL_TRACK_STOPPED,
|
||||
() => {
|
||||
logger.debug('Local screensharing audio track stopped.');
|
||||
logger.debug(`Local screensharing audio track stopped. ${this.isSharingScreen}`);
|
||||
|
||||
// Handle case where screen share was stopped from the browsers 'screen share in progress'
|
||||
// window. If audio screen sharing is stopped via the normal UX flow this point shouldn't
|
||||
@@ -1306,6 +1335,21 @@ export default {
|
||||
);
|
||||
}
|
||||
|
||||
if (desktopVideoStream) {
|
||||
desktopVideoStream.on(
|
||||
JitsiTrackEvents.LOCAL_TRACK_STOPPED,
|
||||
() => {
|
||||
logger.debug(`Local screensharing track stopped. ${this.isSharingScreen}`);
|
||||
|
||||
// If the stream was stopped during screen sharing
|
||||
// session then we should switch back to video.
|
||||
this.isSharingScreen
|
||||
&& this._untoggleScreenSharing
|
||||
&& this._untoggleScreenSharing();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return desktopStreams;
|
||||
}, error => {
|
||||
throw error;
|
||||
@@ -1453,6 +1497,10 @@ export default {
|
||||
room.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED, (track, participantThatMutedUs) => {
|
||||
if (participantThatMutedUs) {
|
||||
APP.store.dispatch(participantMutedUs(participantThatMutedUs, track));
|
||||
if (this.isSharingScreen && track.isVideoTrack()) {
|
||||
logger.debug('TRACK_MUTE_CHANGED while screen sharing');
|
||||
this._turnScreenSharingOff(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1664,12 +1712,8 @@ export default {
|
||||
room.on(
|
||||
JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
|
||||
({ audio, video }) => {
|
||||
APP.store.dispatch(onStartMutedPolicyChanged(audio, video));
|
||||
|
||||
const state = APP.store.getState();
|
||||
|
||||
updateTrackMuteState(state, APP.store.dispatch, true);
|
||||
updateTrackMuteState(state, APP.store.dispatch, false);
|
||||
APP.store.dispatch(
|
||||
onStartMutedPolicyChanged(audio, video));
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ target 'JitsiMeetSDK' do
|
||||
:path => config[:reactNativePath],
|
||||
:hermes_enabled => true,
|
||||
:fabric_enabled => false,
|
||||
:new_arch_enabled => false,
|
||||
# An absolute path to your application root.
|
||||
:app_path => "#{Pod::Config.instance.installation_root}/.."
|
||||
)
|
||||
@@ -67,7 +66,6 @@ target 'JitsiMeetSDKLite' do
|
||||
:path => config[:reactNativePath],
|
||||
:hermes_enabled => true,
|
||||
:fabric_enabled => false,
|
||||
:new_arch_enabled => false,
|
||||
# An absolute path to your application root.
|
||||
:app_path => "#{Pod::Config.instance.installation_root}/.."
|
||||
)
|
||||
@@ -79,12 +77,10 @@ target 'JitsiMeetSDKLite' do
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
|
||||
react_native_post_install(
|
||||
installer,
|
||||
use_native_modules![:reactNativePath],
|
||||
:mac_catalyst_enabled => false,
|
||||
# :ccache_enabled => true
|
||||
:mac_catalyst_enabled => false
|
||||
)
|
||||
installer.pods_project.targets.each do |target|
|
||||
# https://github.com/CocoaPods/CocoaPods/issues/11402
|
||||
@@ -103,5 +99,4 @@ post_install do |installer|
|
||||
|
||||
# Patch SocketRocket to support TLS 1.3
|
||||
%x(patch Pods/SocketRocket/SocketRocket/SRSecurityPolicy.m -N < patches/ws-tls13.diff)
|
||||
|
||||
end
|
||||
|
||||
1248
ios/Podfile.lock
1248
ios/Podfile.lock
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@
|
||||
0BEA5C3B1F7B8F73000D0AB4 /* ComplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BEA5C3A1F7B8F73000D0AB4 /* ComplicationController.swift */; };
|
||||
0BEA5C3D1F7B8F73000D0AB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0BEA5C3C1F7B8F73000D0AB4 /* Assets.xcassets */; };
|
||||
0BEA5C411F7B8F73000D0AB4 /* JitsiMeetCompanion.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 0BEA5C251F7B8F73000D0AB4 /* JitsiMeetCompanion.app */; };
|
||||
13B07FBD1A68108700A75B9A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.storyboard */; };
|
||||
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
2681BB562C7A0B42CFBA6719 /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D6152FF9E9F7B0E86F70A21D /* libPods-JitsiMeet.a */; };
|
||||
361974E2A13624D7735D619D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 5C1BE20ECD5DEEB48FED90B5 /* PrivacyInfo.xcprivacy */; };
|
||||
@@ -132,7 +132,7 @@
|
||||
0BEA5C3C1F7B8F73000D0AB4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
0BEA5C3E1F7B8F73000D0AB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* jitsi-meet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jitsi-meet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = src/Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
3E0F4ED943C0B12BE77F6B45 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -250,6 +250,7 @@
|
||||
DEA0B7132D7EF7590062A9F6 /* AppDelegate.swift */,
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
||||
13B07FB61A68108700A75B9A /* Info.plist */,
|
||||
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
|
||||
DEA0B7112D7EF16E0062A9F6 /* ViewController.swift */,
|
||||
);
|
||||
path = src;
|
||||
@@ -474,7 +475,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
13B07FBD1A68108700A75B9A /* LaunchScreen.storyboard in Resources */,
|
||||
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
|
||||
361974E2A13624D7735D619D /* PrivacyInfo.xcprivacy in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -686,12 +687,12 @@
|
||||
name = Interface.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13B07FB11A68108700A75B9A /* LaunchScreen.storyboard */ = {
|
||||
13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
13B07FB21A68108700A75B9A /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
name = LaunchScreen.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
@@ -37,7 +37,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
let vc = ViewController()
|
||||
self.window?.rootViewController = vc
|
||||
jitsiMeet.showSplashScreen()
|
||||
jitsiMeet.showSplashScreen(vc.view)
|
||||
|
||||
self.window?.makeKeyAndVisible()
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" userInteractionEnabled="NO" contentMode="center" image="LaunchScreen" translatesAutoresizingMaskIntoConstraints="NO" id="4B8-Xf-NDE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.090196078431372548" green="0.62745098039215685" blue="0.85882352941176465" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="4B8-Xf-NDE" secondAttribute="bottom" id="aFF-BR-glX"/>
|
||||
<constraint firstItem="4B8-Xf-NDE" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="glR-YN-1GF"/>
|
||||
<constraint firstAttribute="trailing" secondItem="4B8-Xf-NDE" secondAttribute="trailing" id="tva-gl-jRX"/>
|
||||
<constraint firstItem="4B8-Xf-NDE" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="yaV-1V-oEh"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="LaunchScreen" width="480" height="480"/>
|
||||
</resources>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
35
ios/app/src/Base.lproj/LaunchScreen.xib
Normal file
35
ios/app/src/Base.lproj/LaunchScreen.xib
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" userInteractionEnabled="NO" contentMode="center" image="LaunchScreen" translatesAutoresizingMaskIntoConstraints="NO" id="4B8-Xf-NDE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.090196078431372548" green="0.62745098039215685" blue="0.85882352941176465" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="4B8-Xf-NDE" secondAttribute="bottom" id="aFF-BR-glX"/>
|
||||
<constraint firstItem="4B8-Xf-NDE" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="glR-YN-1GF"/>
|
||||
<constraint firstAttribute="trailing" secondItem="4B8-Xf-NDE" secondAttribute="trailing" id="tva-gl-jRX"/>
|
||||
<constraint firstItem="4B8-Xf-NDE" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="yaV-1V-oEh"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="LaunchScreen" width="480" height="480"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -90,7 +90,7 @@
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleLightContent</string>
|
||||
|
||||
@@ -100,9 +100,6 @@ typedef NS_ENUM(NSInteger, WebRTCLoggingSeverity) {
|
||||
|
||||
- (BOOL)isCrashReportingDisabled;
|
||||
|
||||
/**
|
||||
* Shows the splash screen.
|
||||
*/
|
||||
- (void)showSplashScreen;
|
||||
- (void)showSplashScreen:(UIView * _Nonnull) rootView;
|
||||
|
||||
@end
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#import "JitsiMeetView+Private.h"
|
||||
#import "RCTBridgeWrapper.h"
|
||||
#import "ReactUtils.h"
|
||||
#import "RNSplashScreen.h"
|
||||
#import "ScheenshareEventEmiter.h"
|
||||
|
||||
#import <react-native-webrtc/WebRTCModuleOptions.h>
|
||||
@@ -220,17 +221,8 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)showSplashScreen {
|
||||
Class splashClass = NSClassFromString(@"SplashView");
|
||||
if (splashClass && [splashClass respondsToSelector:@selector(sharedInstance)]) {
|
||||
id splashInstance = [splashClass performSelector:@selector(sharedInstance)];
|
||||
if (splashInstance && [splashInstance respondsToSelector:@selector(showSplash)]) {
|
||||
[splashInstance performSelector:@selector(showSplash)];
|
||||
NSLog(@"✅ Splash Screen Shown Successfully");
|
||||
}
|
||||
} else {
|
||||
NSLog(@"⚠️ SplashView module not found");
|
||||
}
|
||||
- (void)showSplashScreen:(UIView*)rootView {
|
||||
[RNSplashScreen showSplash:@"LaunchScreen" inRootView:rootView];
|
||||
}
|
||||
|
||||
#pragma mark - Property getter / setters
|
||||
|
||||
@@ -300,12 +300,6 @@
|
||||
"alreadySharedVideoTitle": "Only one shared video is allowed at a time",
|
||||
"applicationWindow": "Application window",
|
||||
"authenticationRequired": "Authentication required",
|
||||
"cameraCaptureDialog": {
|
||||
"description": "Take and send a picture using your mobile camera",
|
||||
"ok": "Open camera",
|
||||
"reject": "Not now",
|
||||
"title": "Take a picture"
|
||||
},
|
||||
"cameraConstraintFailedError": "Your camera does not satisfy some of the required constraints.",
|
||||
"cameraNotFoundError": "Camera was not found.",
|
||||
"cameraNotSendingData": "We are unable to access your camera. Please check if another application is using this device, select another device from the settings menu or try to reload the application.",
|
||||
@@ -381,34 +375,22 @@
|
||||
"micTimeoutError": "Could not start audio source. Timeout occurred!",
|
||||
"micUnknownError": "Cannot use microphone for an unknown reason.",
|
||||
"moderationAudioLabel": "Allow attendees to unmute themselves",
|
||||
"moderationDesktopLabel": "Allow non-moderators to share their screen",
|
||||
"moderationVideoLabel": "Allow non-moderators to start their video",
|
||||
"muteEveryoneDialog": "The participants can unmute themselves at any time.",
|
||||
"muteEveryoneDialogModerationOn": "The participants can send a request to speak at any time.",
|
||||
"muteEveryoneElseDialog": "Once muted, you won't be able to unmute them, but they can unmute themselves at any time.",
|
||||
"muteEveryoneElseTitle": "Mute everyone except {{whom}}?",
|
||||
"muteEveryoneElsesDesktopDialog": "Once the share is stopped, you won't be able to restart it, but they can do so at any time.",
|
||||
"muteEveryoneElsesDesktopTitle": "Stop everyone's screen-share except {{whom}}?",
|
||||
"muteEveryoneElsesVideoDialog": "Once the camera is disabled, you won't be able to turn it back on, but they can turn it back on at any time.",
|
||||
"muteEveryoneElsesVideoTitle": "Stop everyone's video except {{whom}}?",
|
||||
"muteEveryoneSelf": "yourself",
|
||||
"muteEveryoneStartMuted": "Everyone starts muted from now on",
|
||||
"muteEveryoneTitle": "Mute everyone?",
|
||||
"muteEveryonesDesktopDialog": "The participants can share their screen at any time.",
|
||||
"muteEveryonesDesktopDialogModerationOn": "The participants can send a request to share their screen at any time.",
|
||||
"muteEveryonesDesktopTitle": "Stop everyone's screen share?",
|
||||
"muteEveryonesVideoDialog": "The participants can turn on their video at any time.",
|
||||
"muteEveryonesVideoDialogModerationOn": "The participants can send a request to turn on their video at any time.",
|
||||
"muteEveryonesVideoDialogOk": "Disable",
|
||||
"muteEveryonesVideoTitle": "Stop everyone's video?",
|
||||
"muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
|
||||
"muteParticipantButton": "Mute",
|
||||
"muteParticipantsDesktopBody": "You won't be able to start their screen-share, but they can do so at any time.",
|
||||
"muteParticipantsDesktopBodyModerationOn": "You won't be able to start their screen-share and neither will they.",
|
||||
"muteParticipantsDesktopButton": "Stop screen sharing",
|
||||
"muteParticipantsDesktopDialog": "Are you sure you want to turn off this participant's screen-share? You won't be able to restart it, but they can do so at any time.",
|
||||
"muteParticipantsDesktopDialogModerationOn": "Are you sure you want to turn off this participant's screen-share? You won't be able to turn the screen back on and neither will they.",
|
||||
"muteParticipantsDesktopTitle": "Disable screen-share of this participant?",
|
||||
"muteParticipantsVideoBody": "You won't be able to turn the camera back on, but they can turn it back on at any time.",
|
||||
"muteParticipantsVideoBodyModerationOn": "You won't be able to turn the camera back on and neither will they.",
|
||||
"muteParticipantsVideoButton": "Stop video",
|
||||
@@ -785,9 +767,8 @@
|
||||
"me": "me",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "Security vulnerability!",
|
||||
"allowAll": "Allow All",
|
||||
"allowAudio": "Allow Audio",
|
||||
"allowDesktop": "Allow screen sharing",
|
||||
"allowBoth": "Both",
|
||||
"allowVideo": "Allow Video",
|
||||
"allowedUnmute": "You can unmute your microphone, start your camera or share your screen.",
|
||||
"audioUnmuteBlockedDescription": "Mic unmute operation has been temporarily blocked because of system limits.",
|
||||
@@ -801,7 +782,6 @@
|
||||
"dataChannelClosedDescription": "The bridge channel is down and thus video quality may be limited to its lowest setting.",
|
||||
"dataChannelClosedDescriptionWithAudio": "The bridge channel is down and thus disruptions to audio and video may occur.",
|
||||
"dataChannelClosedWithAudio": "Audio and video quality may be impaired",
|
||||
"desktopMutedRemotelyTitle": "Your screen sharing has been stopped by {{participantDisplayName}}",
|
||||
"disabledIframe": "Embedding is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes.",
|
||||
"disabledIframeSecondaryNative": "Embedding {{domain}} is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes.",
|
||||
"disabledIframeSecondaryWeb": "Embedding {{domain}} is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes. Please use <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> for production embedding!",
|
||||
@@ -882,7 +862,6 @@
|
||||
"suggestRecordingDescription": "Would you like to start a recording?",
|
||||
"suggestRecordingTitle": "Record this meeting",
|
||||
"unmute": "Unmute Audio",
|
||||
"unmuteScreen": "Start screen sharing",
|
||||
"unmuteVideo": "Unmute Video",
|
||||
"videoMutedRemotelyDescription": "You can always turn it on again.",
|
||||
"videoMutedRemotelyTitle": "Your video has been turned off by {{participantDisplayName}}",
|
||||
@@ -902,14 +881,11 @@
|
||||
"admit": "Admit",
|
||||
"admitAll": "Admit all",
|
||||
"allow": "Allow non-moderators to:",
|
||||
"allowDesktop": "Allow screen sharing",
|
||||
"allowVideo": "Allow video",
|
||||
"askDesktop": "Ask to share screen",
|
||||
"askUnmute": "Ask to unmute",
|
||||
"audioModeration": "Unmute themselves",
|
||||
"blockEveryoneMicCamera": "Block everyone's mic and camera",
|
||||
"breakoutRooms": "Breakout rooms",
|
||||
"desktopModeration": "Start screen sharing",
|
||||
"goLive": "Go live",
|
||||
"invite": "Invite someone",
|
||||
"lowerAllHands": "Lower all hands",
|
||||
@@ -921,8 +897,6 @@
|
||||
"muteAll": "Mute all",
|
||||
"muteEveryoneElse": "Mute everyone else",
|
||||
"reject": "Reject",
|
||||
"stopDesktop": "Stop screen sharing",
|
||||
"stopEveryonesDesktop": "Stop everyone's screen-share",
|
||||
"stopEveryonesVideo": "Stop everyone's video",
|
||||
"stopVideo": "Stop video",
|
||||
"unblockEveryoneMicCamera": "Unblock everyone's mic and camera",
|
||||
@@ -1529,8 +1503,6 @@
|
||||
"connectionInfo": "Connection Info",
|
||||
"demote": "Move to viewer",
|
||||
"domute": "Mute",
|
||||
"domuteDesktop": "Stop screen-sharing",
|
||||
"domuteDesktopOfOthers": "Stop screen-sharing for everyone else",
|
||||
"domuteOthers": "Mute everyone else",
|
||||
"domuteVideo": "Disable camera",
|
||||
"domuteVideoOfOthers": "Disable camera of everyone else",
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
requestEnableAudioModeration,
|
||||
requestEnableVideoModeration
|
||||
} from '../../react/features/av-moderation/actions';
|
||||
import { isEnabledFromState, isForceMuted } from '../../react/features/av-moderation/functions';
|
||||
import { isEnabledFromState } from '../../react/features/av-moderation/functions';
|
||||
import { setAudioOnly } from '../../react/features/base/audio-only/actions';
|
||||
import {
|
||||
endConference,
|
||||
@@ -30,7 +30,6 @@ import { overwriteConfig } from '../../react/features/base/config/actions';
|
||||
import { getWhitelistedJSON } from '../../react/features/base/config/functions.any';
|
||||
import { toggleDialog } from '../../react/features/base/dialog/actions';
|
||||
import { isSupportedBrowser } from '../../react/features/base/environment/environment';
|
||||
import { isMobileBrowser } from '../../react/features/base/environment/utils';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt/functions';
|
||||
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../../react/features/base/media/constants';
|
||||
@@ -107,17 +106,14 @@ import {
|
||||
close as closeParticipantsPane,
|
||||
open as openParticipantsPane
|
||||
} from '../../react/features/participants-pane/actions';
|
||||
import { getParticipantsPaneOpen } from '../../react/features/participants-pane/functions';
|
||||
import { getParticipantsPaneOpen, isForceMuted } from '../../react/features/participants-pane/functions';
|
||||
import { startLocalVideoRecording, stopLocalVideoRecording } from '../../react/features/recording/actions.any';
|
||||
import { grantRecordingConsent, grantRecordingConsentAndUnmute } from '../../react/features/recording/actions.web';
|
||||
import { RECORDING_METADATA_ID, RECORDING_TYPES } from '../../react/features/recording/constants';
|
||||
import { getActiveSession, supportsLocalRecording } from '../../react/features/recording/functions';
|
||||
import { startAudioScreenShareFlow, startScreenShareFlow } from '../../react/features/screen-share/actions';
|
||||
import { isScreenAudioSupported } from '../../react/features/screen-share/functions';
|
||||
import {
|
||||
openCameraCaptureDialog,
|
||||
toggleScreenshotCaptureSummary
|
||||
} from '../../react/features/screenshot-capture/actions';
|
||||
import { toggleScreenshotCaptureSummary } from '../../react/features/screenshot-capture/actions';
|
||||
import { isScreenshotCaptureEnabled } from '../../react/features/screenshot-capture/functions';
|
||||
import SettingsDialog from '../../react/features/settings/components/web/SettingsDialog';
|
||||
import { SETTINGS_TABS } from '../../react/features/settings/constants';
|
||||
@@ -944,20 +940,6 @@ function initCommands() {
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'capture-camera-picture' : {
|
||||
const { cameraFacingMode, descriptionText, titleText } = request;
|
||||
|
||||
if (!isMobileBrowser()) {
|
||||
logger.error('This feature is only supported on mobile');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(openCameraCaptureDialog(callback, { cameraFacingMode,
|
||||
descriptionText,
|
||||
titleText }));
|
||||
break;
|
||||
}
|
||||
case 'deployment-info':
|
||||
callback(APP.store.getState()['features/base/config'].deploymentInfo);
|
||||
break;
|
||||
|
||||
21
modules/API/external/external_api.js
vendored
21
modules/API/external/external_api.js
vendored
@@ -820,27 +820,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures a picture through OS camera.
|
||||
*
|
||||
* @param {string} cameraFacingMode - The OS camera facing mode (environment/user).
|
||||
* @param {string} descriptionText - The OS camera facing mode (environment/user).
|
||||
* @param {string} titleText - The OS camera facing mode (environment/user).
|
||||
* @returns {Promise<string>} - Resolves with a base64 encoded image data of the screenshot.
|
||||
*/
|
||||
captureCameraPicture(
|
||||
cameraFacingMode,
|
||||
descriptionText,
|
||||
titleText
|
||||
) {
|
||||
return this._transport.sendRequest({
|
||||
name: 'capture-camera-picture',
|
||||
cameraFacingMode,
|
||||
descriptionText,
|
||||
titleText
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listeners and removes the Jitsi Meet frame.
|
||||
*
|
||||
|
||||
7671
package-lock.json
generated
7671
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@@ -23,7 +23,7 @@
|
||||
"@emotion/styled": "11.10.6",
|
||||
"@giphy/js-fetch-api": "4.9.3",
|
||||
"@giphy/react-components": "6.9.4",
|
||||
"@giphy/react-native-sdk": "3.3.1",
|
||||
"@giphy/react-native-sdk": "2.3.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
|
||||
"@jitsi/js-utils": "2.2.1",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
@@ -34,14 +34,13 @@
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
"@react-native-clipboard/clipboard": "1.14.3",
|
||||
"@react-native-community/netinfo": "11.1.0",
|
||||
"@react-native-community/slider": "4.5.6",
|
||||
"@react-native-community/slider": "4.4.3",
|
||||
"@react-native-google-signin/google-signin": "10.1.0",
|
||||
"@react-navigation/bottom-tabs": "6.6.0",
|
||||
"@react-navigation/elements": "1.3.30",
|
||||
"@react-navigation/material-top-tabs": "6.6.13",
|
||||
"@react-navigation/native": "6.1.17",
|
||||
"@react-navigation/stack": "6.4.0",
|
||||
"@sayem314/react-native-keep-awake": "1.3.1",
|
||||
"@stomp/stompjs": "7.0.0",
|
||||
"@svgr/webpack": "6.3.1",
|
||||
"@tensorflow/tfjs-backend-wasm": "3.13.0",
|
||||
@@ -70,7 +69,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/v2030.0.0+b225c920/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2025.0.0+49eb29a8/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"null-loader": "4.0.1",
|
||||
"optional-require": "1.0.3",
|
||||
@@ -83,31 +82,32 @@
|
||||
"react-focus-on": "3.8.1",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "0.77.2",
|
||||
"react-native-background-timer": "https://github.com/jitsi/react-native-background-timer.git#d180dfaa4486ae3ee17d01242db92cb3195f4718",
|
||||
"react-native-calendar-events": "https://github.com/jitsi/react-native-calendar-events.git#47f068dedfed7c0f72042e093f688eb11624eb7b",
|
||||
"react-native-default-preference": "https://github.com/jitsi/react-native-default-preference.git#c9bf63bdc058e3fa2aa0b87b1ee1af240f44ed02",
|
||||
"react-native": "0.75.5",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "10.9.0",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.24.0",
|
||||
"react-native-get-random-values": "1.11.0",
|
||||
"react-native-immersive-mode": "https://github.com/jitsi/react-native-immersive-mode.git#38cc9001db24618bc0c61800f81e889bcfb6ff2c",
|
||||
"react-native-gesture-handler": "2.18.1",
|
||||
"react-native-get-random-values": "1.9.0",
|
||||
"react-native-immersive-mode": "2.0.2",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-orientation-locker": "1.6.0",
|
||||
"react-native-pager-view": "6.4.1",
|
||||
"react-native-paper": "5.10.3",
|
||||
"react-native-performance": "5.1.2",
|
||||
"react-native-safe-area-context": "5.4.0",
|
||||
"react-native-screens": "4.11.1",
|
||||
"react-native-sound": "https://github.com/jitsi/react-native-sound.git#ea13c97b5c2a4ff5e0d9bacbd9ff5e4457fe2c3c",
|
||||
"react-native-splash-view": "0.0.18",
|
||||
"react-native-svg": "15.11.2",
|
||||
"react-native-performance": "5.0.0",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "3.35.0",
|
||||
"react-native-sound": "0.11.2",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
"react-native-svg": "13.13.0",
|
||||
"react-native-svg-transformer": "1.2.0",
|
||||
"react-native-tab-view": "3.5.2",
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-video": "6.13.0",
|
||||
"react-native-video": "6.0.0-alpha.11",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-webrtc": "124.0.4",
|
||||
"react-native-webview": "13.13.5",
|
||||
"react-native-webview": "13.8.7",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-textarea-autosize": "8.3.0",
|
||||
@@ -133,11 +133,7 @@
|
||||
"@babel/preset-env": "7.25.9",
|
||||
"@babel/preset-react": "7.25.9",
|
||||
"@jitsi/eslint-config": "6.0.4",
|
||||
"@react-native-community/cli": "15.0.1",
|
||||
"@react-native-community/cli-platform-android": "15.0.1",
|
||||
"@react-native-community/cli-platform-ios": "15.0.1",
|
||||
"@react-native/babel-preset": "0.77.2",
|
||||
"@react-native/metro-config": "0.77.2",
|
||||
"@react-native/metro-config": "0.75.5",
|
||||
"@types/amplitude-js": "8.16.5",
|
||||
"@types/audioworklet": "0.0.29",
|
||||
"@types/dom-screen-wake-lock": "1.0.1",
|
||||
@@ -181,6 +177,7 @@
|
||||
"eslint-plugin-typescript-sort-keys": "3.3.0",
|
||||
"jetifier": "1.6.4",
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"metro-react-native-babel-preset": "0.77.0",
|
||||
"patch-package": "6.4.7",
|
||||
"pretty": "2.0.0",
|
||||
"process": "0.11.10",
|
||||
|
||||
11
patches/@giphy+react-native-sdk+2.3.0.patch
Normal file
11
patches/@giphy+react-native-sdk+2.3.0.patch
Normal file
@@ -0,0 +1,11 @@
|
||||
diff --git a/node_modules/@giphy/react-native-sdk/giphy-react-native-sdk.podspec b/node_modules/@giphy/react-native-sdk/giphy-react-native-sdk.podspec
|
||||
index ddd41ac..a7b143e 100644
|
||||
--- a/node_modules/@giphy/react-native-sdk/giphy-react-native-sdk.podspec
|
||||
+++ b/node_modules/@giphy/react-native-sdk/giphy-react-native-sdk.podspec
|
||||
@@ -16,5 +16,5 @@ Pod::Spec.new do |s|
|
||||
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
||||
|
||||
s.dependency "React-Core"
|
||||
- s.dependency "Giphy", "2.2.4"
|
||||
+ s.dependency "Giphy", "2.2.12"
|
||||
end
|
||||
@@ -5,9 +5,8 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$rootProject.ext.gradlePluginVersion"
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
}
|
||||
namespace 'org.jitsi.meet.reactnativesdk'
|
||||
}
|
||||
|
||||
def isNewArchitectureEnabled() {
|
||||
@@ -47,8 +46,8 @@ android {
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
JitsiMeetReactNative_kotlinVersion=2.0.21
|
||||
JitsiMeetReactNative_minSdkVersion=26
|
||||
JitsiMeetReactNative_targetSdkVersion=34
|
||||
JitsiMeetReactNative_compileSdkVersion=34
|
||||
JitsiMeetReactNative_ndkversion=27.1.12297006
|
||||
JitsiMeetReactNative_kotlinVersion=1.7.0
|
||||
JitsiMeetReactNative_minSdkVersion=21
|
||||
JitsiMeetReactNative_targetSdkVersion=31
|
||||
JitsiMeetReactNative_compileSdkVersion=31
|
||||
JitsiMeetReactNative_ndkversion=21.4.7075529
|
||||
|
||||
@@ -64,8 +64,7 @@
|
||||
"@react-native-community/netinfo": "0.0.0",
|
||||
"@react-native-community/slider": "0.0.0",
|
||||
"@react-native-google-signin/google-signin": "0.0.0",
|
||||
"@sayem314/react-native-keep-awake": "0.0.0",
|
||||
"react-native": "0.0.0",
|
||||
"react-native": "*",
|
||||
"react": "*",
|
||||
"react-native-background-timer": "0.0.0",
|
||||
"react-native-calendar-events": "0.0.0",
|
||||
@@ -74,13 +73,14 @@
|
||||
"react-native-get-random-values": "0.0.0",
|
||||
"react-native-gesture-handler": "0.0.0",
|
||||
"react-native-immersive-mode": "0.0.0",
|
||||
"react-native-keep-awake": "0.0.0",
|
||||
"react-native-pager-view": "0.0.0",
|
||||
"react-native-performance": "0.0.0",
|
||||
"react-native-orientation-locker": "0.0.0",
|
||||
"react-native-safe-area-context": "0.0.0",
|
||||
"react-native-screens": "0.0.0",
|
||||
"react-native-sound": "0.0.0",
|
||||
"react-native-splash-view": "0.0.0",
|
||||
"react-native-splash-screen": "0.0.0",
|
||||
"react-native-svg": "0.0.0",
|
||||
"react-native-video": "0.0.0",
|
||||
"react-native-watch-connectivity": "0.0.0",
|
||||
|
||||
@@ -63,11 +63,6 @@ export const ACTION_SHORTCUT_TRIGGERED = 'triggered';
|
||||
*/
|
||||
export const AUDIO_MUTE = 'audio.mute';
|
||||
|
||||
/**
|
||||
* The name of the keyboard shortcut or toolbar button for muting desktop sharing.
|
||||
*/
|
||||
export const DESKTOP_MUTE = 'desktop.mute';
|
||||
|
||||
/**
|
||||
* The name of the keyboard shortcut or toolbar button for muting video.
|
||||
*/
|
||||
|
||||
@@ -2,8 +2,7 @@ import React, { ComponentType } from 'react';
|
||||
import { NativeModules, Platform, StyleSheet, View } from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
// @ts-ignore
|
||||
import { hideSplash } from 'react-native-splash-view';
|
||||
import SplashScreen from 'react-native-splash-screen';
|
||||
|
||||
import BottomSheetContainer from '../../base/dialog/components/native/BottomSheetContainer';
|
||||
import DialogContainer from '../../base/dialog/components/native/DialogContainer';
|
||||
@@ -85,7 +84,7 @@ export class App extends AbstractApp<IProps> {
|
||||
override async componentDidMount() {
|
||||
await super.componentDidMount();
|
||||
|
||||
hideSplash();
|
||||
SplashScreen.hide();
|
||||
|
||||
const liteTxt = AppInfo.isLiteSDK ? ' (lite)' : '';
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { getServerURL } from '../base/settings/functions.web';
|
||||
import { getJitsiMeetGlobalNS } from '../base/util/helpers';
|
||||
|
||||
export * from './functions.any';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Retrieves the default URL for the app. This can either come from a prop to
|
||||
@@ -33,27 +31,3 @@ export function getDefaultURL(stateful: IStateful) {
|
||||
export function getName() {
|
||||
return interfaceConfig.APP_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a handler function after the window load event has been received.
|
||||
* If the app has already loaded, the handler is executed immediately.
|
||||
* Otherwise, the handler is registered as a 'load' event listener.
|
||||
*
|
||||
* @param {Function} handler - The callback function to execute.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function executeAfterLoad(handler: () => void) {
|
||||
const safeHandler = () => {
|
||||
try {
|
||||
handler();
|
||||
} catch (error) {
|
||||
logger.error('Error executing handler after load:', error);
|
||||
}
|
||||
};
|
||||
|
||||
if (getJitsiMeetGlobalNS()?.hasLoaded) {
|
||||
safeHandler();
|
||||
} else {
|
||||
window.addEventListener('load', safeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import '../calendar-sync/middleware';
|
||||
import '../chat/middleware';
|
||||
import '../conference/middleware';
|
||||
import '../connection-indicator/middleware';
|
||||
import '../deep-linking/middleware';
|
||||
import '../device-selection/middleware';
|
||||
import '../display-name/middleware';
|
||||
import '../dynamic-branding/middleware';
|
||||
|
||||
@@ -2,7 +2,6 @@ import '../base/app/middleware';
|
||||
import '../base/connection/middleware';
|
||||
import '../base/devices/middleware';
|
||||
import '../base/media/middleware';
|
||||
import '../deep-linking/middleware.web';
|
||||
import '../dynamic-branding/middleware';
|
||||
import '../e2ee/middleware';
|
||||
import '../external-api/middleware';
|
||||
|
||||
@@ -37,15 +37,6 @@ export const ENABLE_MODERATION = 'ENABLE_MODERATION';
|
||||
*/
|
||||
export const REQUEST_DISABLE_AUDIO_MODERATION = 'REQUEST_DISABLE_AUDIO_MODERATION';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that Desktop Moderation disable has been requested.
|
||||
*
|
||||
* {
|
||||
* type: REQUEST_DISABLE_DESKTOP_MODERATION
|
||||
* }
|
||||
*/
|
||||
export const REQUEST_DISABLE_DESKTOP_MODERATION = 'REQUEST_DISABLE_DESKTOP_MODERATION';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that Video Moderation disable has been requested.
|
||||
*
|
||||
@@ -64,15 +55,6 @@ export const REQUEST_DISABLE_VIDEO_MODERATION = 'REQUEST_DISABLE_VIDEO_MODERATIO
|
||||
*/
|
||||
export const REQUEST_ENABLE_AUDIO_MODERATION = 'REQUEST_ENABLE_AUDIO_MODERATION';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that Desktop Moderation enable has been requested.
|
||||
*
|
||||
* {
|
||||
* type: REQUEST_ENABLE_DESKTOP_MODERATION
|
||||
* }
|
||||
*/
|
||||
export const REQUEST_ENABLE_DESKTOP_MODERATION = 'REQUEST_ENABLE_DESKTOP_MODERATION';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that Video Moderation enable has been requested.
|
||||
*
|
||||
@@ -135,7 +117,7 @@ export const PARTICIPANT_REJECTED = 'PARTICIPANT_REJECTED';
|
||||
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that a participant asked to have its audio unmuted.
|
||||
* The type of (redux) action which signals that a participant asked to have its audio umuted.
|
||||
*
|
||||
* {
|
||||
* type: PARTICIPANT_PENDING_AUDIO
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
import { getConferenceState } from '../base/conference/functions';
|
||||
import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
|
||||
import { getParticipantById, isParticipantModerator } from '../base/participants/functions';
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { isForceMuted } from '../participants-pane/functions';
|
||||
|
||||
import {
|
||||
DISABLE_MODERATION,
|
||||
@@ -16,14 +16,11 @@ import {
|
||||
PARTICIPANT_PENDING_AUDIO,
|
||||
PARTICIPANT_REJECTED,
|
||||
REQUEST_DISABLE_AUDIO_MODERATION,
|
||||
REQUEST_DISABLE_DESKTOP_MODERATION,
|
||||
REQUEST_DISABLE_VIDEO_MODERATION,
|
||||
REQUEST_ENABLE_AUDIO_MODERATION,
|
||||
REQUEST_ENABLE_DESKTOP_MODERATION,
|
||||
REQUEST_ENABLE_VIDEO_MODERATION
|
||||
} from './actionTypes';
|
||||
import { MEDIA_TYPE, type MediaType } from './constants';
|
||||
import { isEnabledFromState, isForceMuted } from './functions';
|
||||
import { isEnabledFromState } from './functions';
|
||||
|
||||
/**
|
||||
* Action used by moderator to approve audio for a participant.
|
||||
@@ -45,25 +42,6 @@ export const approveParticipantAudio = (id: string) => (dispatch: IStore['dispat
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Action used by moderator to approve desktop for a participant.
|
||||
*
|
||||
* @param {staring} id - The id of the participant to be approved.
|
||||
* @returns {void}
|
||||
*/
|
||||
export const approveParticipantDesktop = (id: string) => (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const { conference } = getConferenceState(state);
|
||||
const participant = getParticipantById(state, id);
|
||||
|
||||
const isDesktopForceMuted = isForceMuted(participant, MEDIA_TYPE.DESKTOP, state);
|
||||
const isDesktopModerationOn = isEnabledFromState(MEDIA_TYPE.DESKTOP, state);
|
||||
|
||||
if (isDesktopModerationOn && isDesktopForceMuted) {
|
||||
conference?.avModerationApprove(MEDIA_TYPE.DESKTOP, id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Action used by moderator to approve video for a participant.
|
||||
*
|
||||
@@ -90,11 +68,8 @@ export const approveParticipantVideo = (id: string) => (dispatch: IStore['dispat
|
||||
* @returns {void}
|
||||
*/
|
||||
export const approveParticipant = (id: string) => (dispatch: IStore['dispatch']) => {
|
||||
batch(() => {
|
||||
dispatch(approveParticipantAudio(id));
|
||||
dispatch(approveParticipantDesktop(id));
|
||||
dispatch(approveParticipantVideo(id));
|
||||
});
|
||||
dispatch(approveParticipantAudio(id));
|
||||
dispatch(approveParticipantVideo(id));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -117,26 +92,6 @@ export const rejectParticipantAudio = (id: string) => (dispatch: IStore['dispatc
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Action used by moderator to reject desktop for a participant.
|
||||
*
|
||||
* @param {staring} id - The id of the participant to be rejected.
|
||||
* @returns {void}
|
||||
*/
|
||||
export const rejectParticipantDesktop = (id: string) => (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const { conference } = getConferenceState(state);
|
||||
const desktopModeration = isEnabledFromState(MEDIA_TYPE.DESKTOP, state);
|
||||
|
||||
const participant = getParticipantById(state, id);
|
||||
const isDesktopForceMuted = isForceMuted(participant, MEDIA_TYPE.DESKTOP, state);
|
||||
const isModerator = isParticipantModerator(participant);
|
||||
|
||||
if (desktopModeration && !isDesktopForceMuted && !isModerator) {
|
||||
conference?.avModerationReject(MEDIA_TYPE.DESKTOP, id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Action used by moderator to reject video for a participant.
|
||||
*
|
||||
@@ -230,19 +185,6 @@ export const requestDisableAudioModeration = () => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests disable of video moderation.
|
||||
*
|
||||
* @returns {{
|
||||
* type: REQUEST_DISABLE_DESKTOP_MODERATION
|
||||
* }}
|
||||
*/
|
||||
export const requestDisableDesktopModeration = () => {
|
||||
return {
|
||||
type: REQUEST_DISABLE_DESKTOP_MODERATION
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests disable of video moderation.
|
||||
*
|
||||
@@ -269,19 +211,6 @@ export const requestEnableAudioModeration = () => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests enable of video moderation.
|
||||
*
|
||||
* @returns {{
|
||||
* type: REQUEST_ENABLE_DESKTOP_MODERATION
|
||||
* }}
|
||||
*/
|
||||
export const requestEnableDesktopModeration = () => {
|
||||
return {
|
||||
type: REQUEST_ENABLE_DESKTOP_MODERATION
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests enable of video moderation.
|
||||
*
|
||||
@@ -384,3 +313,4 @@ export function participantRejected(id: string, mediaType: MediaType) {
|
||||
mediaType
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,18 @@
|
||||
export type MediaType = 'audio' | 'video' | 'desktop';
|
||||
import { MEDIA_TYPE } from '../base/media/constants';
|
||||
|
||||
/**
|
||||
* The set of media types for AV moderation.
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
export const MEDIA_TYPE: {
|
||||
AUDIO: MediaType;
|
||||
DESKTOP: MediaType;
|
||||
VIDEO: MediaType;
|
||||
} = {
|
||||
AUDIO: 'audio',
|
||||
DESKTOP: 'desktop',
|
||||
VIDEO: 'video'
|
||||
};
|
||||
|
||||
/**
|
||||
* Mapping between a media type and the whitelist reducer key.
|
||||
* Mapping between a media type and the witelist reducer key.
|
||||
*/
|
||||
export const MEDIA_TYPE_TO_WHITELIST_STORE_KEY: { [key: string]: string; } = {
|
||||
[MEDIA_TYPE.AUDIO]: 'audioWhitelist',
|
||||
[MEDIA_TYPE.DESKTOP]: 'desktopWhitelist',
|
||||
[MEDIA_TYPE.VIDEO]: 'videoWhitelist'
|
||||
};
|
||||
|
||||
/**
|
||||
* Mapping between a media type and the pending reducer key.
|
||||
*/
|
||||
export const MEDIA_TYPE_TO_PENDING_STORE_KEY: { [key: string]: 'pendingAudio' | 'pendingDesktop' | 'pendingVideo'; } = {
|
||||
export const MEDIA_TYPE_TO_PENDING_STORE_KEY: { [key: string]: 'pendingAudio' | 'pendingVideo'; } = {
|
||||
[MEDIA_TYPE.AUDIO]: 'pendingAudio',
|
||||
[MEDIA_TYPE.DESKTOP]: 'pendingDesktop',
|
||||
[MEDIA_TYPE.VIDEO]: 'pendingVideo'
|
||||
};
|
||||
|
||||
@@ -37,15 +20,11 @@ export const ASKED_TO_UNMUTE_NOTIFICATION_ID = 'asked-to-unmute';
|
||||
export const ASKED_TO_UNMUTE_SOUND_ID = 'ASKED_TO_UNMUTE_SOUND';
|
||||
|
||||
export const AUDIO_MODERATION_NOTIFICATION_ID = 'audio-moderation';
|
||||
export const DESKTOP_MODERATION_NOTIFICATION_ID = 'desktop-moderation';
|
||||
export const VIDEO_MODERATION_NOTIFICATION_ID = 'video-moderation';
|
||||
|
||||
export const AUDIO_RAISED_HAND_NOTIFICATION_ID = 'raise-hand-audio';
|
||||
export const DESKTOP_RAISED_HAND_NOTIFICATION_ID = 'raise-hand-desktop';
|
||||
export const VIDEO_RAISED_HAND_NOTIFICATION_ID = 'raise-hand-video';
|
||||
export const CS_MODERATION_NOTIFICATION_ID = 'screensharing-moderation';
|
||||
|
||||
export const MODERATION_NOTIFICATIONS = {
|
||||
[MEDIA_TYPE.AUDIO]: AUDIO_MODERATION_NOTIFICATION_ID,
|
||||
[MEDIA_TYPE.DESKTOP]: DESKTOP_MODERATION_NOTIFICATION_ID,
|
||||
[MEDIA_TYPE.SCREENSHARE]: CS_MODERATION_NOTIFICATION_ID,
|
||||
[MEDIA_TYPE.VIDEO]: VIDEO_MODERATION_NOTIFICATION_ID
|
||||
};
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { isLocalParticipantModerator, isParticipantModerator } from '../base/participants/functions';
|
||||
import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
|
||||
import { isLocalParticipantModerator } from '../base/participants/functions';
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
MEDIA_TYPE_TO_PENDING_STORE_KEY,
|
||||
MEDIA_TYPE_TO_WHITELIST_STORE_KEY,
|
||||
MediaType
|
||||
} from './constants';
|
||||
import { MEDIA_TYPE_TO_PENDING_STORE_KEY, MEDIA_TYPE_TO_WHITELIST_STORE_KEY } from './constants';
|
||||
|
||||
/**
|
||||
* Returns this feature's root state.
|
||||
@@ -33,18 +29,10 @@ const EMPTY_ARRAY: any[] = [];
|
||||
* @param {IReduxState} state - Global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isEnabledFromState = (mediaType: MediaType, state: IReduxState) => {
|
||||
switch (mediaType) {
|
||||
case MEDIA_TYPE.AUDIO:
|
||||
return getState(state)?.audioModerationEnabled === true;
|
||||
case MEDIA_TYPE.DESKTOP:
|
||||
return getState(state)?.desktopModerationEnabled === true;
|
||||
case MEDIA_TYPE.VIDEO:
|
||||
return getState(state)?.videoModerationEnabled === true;
|
||||
default:
|
||||
throw new Error(`Unknown media type: ${mediaType}`);
|
||||
}
|
||||
};
|
||||
export const isEnabledFromState = (mediaType: MediaType, state: IReduxState) =>
|
||||
(mediaType === MEDIA_TYPE.AUDIO
|
||||
? getState(state)?.audioModerationEnabled
|
||||
: getState(state)?.videoModerationEnabled) === true;
|
||||
|
||||
/**
|
||||
* Returns whether moderation is enabled per media type.
|
||||
@@ -73,20 +61,11 @@ export const isSupported = () => (state: IReduxState) => {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isLocalParticipantApprovedFromState = (mediaType: MediaType, state: IReduxState) => {
|
||||
if (isLocalParticipantModerator(state)) {
|
||||
return true;
|
||||
}
|
||||
const approved = (mediaType === MEDIA_TYPE.AUDIO
|
||||
? getState(state).audioUnmuteApproved
|
||||
: getState(state).videoUnmuteApproved) === true;
|
||||
|
||||
switch (mediaType) {
|
||||
case MEDIA_TYPE.AUDIO:
|
||||
return getState(state).audioUnmuteApproved === true;
|
||||
case MEDIA_TYPE.DESKTOP:
|
||||
return getState(state).desktopUnmuteApproved === true;
|
||||
case MEDIA_TYPE.VIDEO:
|
||||
return getState(state).videoUnmuteApproved === true;
|
||||
default:
|
||||
throw new Error(`Unknown media type: ${mediaType}`);
|
||||
}
|
||||
return approved || isLocalParticipantModerator(state);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -155,28 +134,3 @@ export const getParticipantsAskingToAudioUnmute = (state: IReduxState) => {
|
||||
export const shouldShowModeratedNotification = (mediaType: MediaType, state: IReduxState) =>
|
||||
isEnabledFromState(mediaType, state)
|
||||
&& !isLocalParticipantApprovedFromState(mediaType, state);
|
||||
|
||||
/**
|
||||
* Checks if a participant is force muted.
|
||||
*
|
||||
* @param {IParticipant|undefined} participant - The participant.
|
||||
* @param {MediaType} mediaType - The media type.
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {MediaState}
|
||||
*/
|
||||
export function isForceMuted(participant: IParticipant | undefined, mediaType: MediaType, state: IReduxState) {
|
||||
if (isEnabledFromState(mediaType, state)) {
|
||||
if (participant?.local) {
|
||||
return !isLocalParticipantApprovedFromState(mediaType, state);
|
||||
}
|
||||
|
||||
// moderators cannot be force muted
|
||||
if (isParticipantModerator(participant)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isParticipantApproved(participant?.id ?? '', mediaType)(state);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,8 @@ import { batch } from 'react-redux';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
|
||||
import { getConferenceState } from '../base/conference/functions';
|
||||
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE as TRACK_MEDIA_TYPE } from '../base/media/constants';
|
||||
import {
|
||||
isAudioMuted,
|
||||
isScreenshareMuted,
|
||||
isVideoMuted
|
||||
} from '../base/media/functions';
|
||||
import { MEDIA_TYPE, MediaType } from '../base/media/constants';
|
||||
import { isAudioMuted, isVideoMuted } from '../base/media/functions';
|
||||
import { PARTICIPANT_UPDATED } from '../base/participants/actionTypes';
|
||||
import { raiseHand } from '../base/participants/actions';
|
||||
import {
|
||||
@@ -34,10 +30,8 @@ import {
|
||||
PARTICIPANT_APPROVED,
|
||||
PARTICIPANT_REJECTED,
|
||||
REQUEST_DISABLE_AUDIO_MODERATION,
|
||||
REQUEST_DISABLE_DESKTOP_MODERATION,
|
||||
REQUEST_DISABLE_VIDEO_MODERATION,
|
||||
REQUEST_ENABLE_AUDIO_MODERATION,
|
||||
REQUEST_ENABLE_DESKTOP_MODERATION,
|
||||
REQUEST_ENABLE_VIDEO_MODERATION
|
||||
} from './actionTypes';
|
||||
import {
|
||||
@@ -55,10 +49,8 @@ import {
|
||||
ASKED_TO_UNMUTE_NOTIFICATION_ID,
|
||||
ASKED_TO_UNMUTE_SOUND_ID,
|
||||
AUDIO_MODERATION_NOTIFICATION_ID,
|
||||
DESKTOP_MODERATION_NOTIFICATION_ID,
|
||||
MEDIA_TYPE,
|
||||
MediaType,
|
||||
VIDEO_MODERATION_NOTIFICATION_ID,
|
||||
CS_MODERATION_NOTIFICATION_ID,
|
||||
VIDEO_MODERATION_NOTIFICATION_ID
|
||||
} from './constants';
|
||||
import {
|
||||
isEnabledFromState,
|
||||
@@ -98,9 +90,9 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
uid = VIDEO_MODERATION_NOTIFICATION_ID;
|
||||
break;
|
||||
}
|
||||
case MEDIA_TYPE.DESKTOP: {
|
||||
case MEDIA_TYPE.SCREENSHARE: {
|
||||
titleKey = 'notify.moderationInEffectCSTitle';
|
||||
uid = DESKTOP_MODERATION_NOTIFICATION_ID;
|
||||
uid = CS_MODERATION_NOTIFICATION_ID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -123,10 +115,6 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
conference?.disableAVModeration(MEDIA_TYPE.AUDIO);
|
||||
break;
|
||||
}
|
||||
case REQUEST_DISABLE_DESKTOP_MODERATION: {
|
||||
conference?.disableAVModeration(MEDIA_TYPE.DESKTOP);
|
||||
break;
|
||||
}
|
||||
case REQUEST_DISABLE_VIDEO_MODERATION: {
|
||||
conference?.disableAVModeration(MEDIA_TYPE.VIDEO);
|
||||
break;
|
||||
@@ -135,10 +123,6 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
conference?.enableAVModeration(MEDIA_TYPE.AUDIO);
|
||||
break;
|
||||
}
|
||||
case REQUEST_ENABLE_DESKTOP_MODERATION: {
|
||||
conference?.enableAVModeration(MEDIA_TYPE.DESKTOP);
|
||||
break;
|
||||
}
|
||||
case REQUEST_ENABLE_VIDEO_MODERATION: {
|
||||
conference?.enableAVModeration(MEDIA_TYPE.VIDEO);
|
||||
break;
|
||||
@@ -235,37 +219,24 @@ StateListenerRegistry.register(
|
||||
const customActionHandler = [];
|
||||
|
||||
if ((mediaType === MEDIA_TYPE.AUDIO || getState()['features/av-moderation'].audioUnmuteApproved)
|
||||
&& isAudioMuted(getState())) {
|
||||
&& isAudioMuted(getState())) {
|
||||
customActionNameKey.push('notify.unmute');
|
||||
customActionHandler.push(() => {
|
||||
dispatch(muteLocal(false, TRACK_MEDIA_TYPE.AUDIO));
|
||||
dispatch(muteLocal(false, MEDIA_TYPE.AUDIO));
|
||||
dispatch(hideNotification(ASKED_TO_UNMUTE_NOTIFICATION_ID));
|
||||
});
|
||||
}
|
||||
|
||||
if ((mediaType === MEDIA_TYPE.DESKTOP || getState()['features/av-moderation'].desktopUnmuteApproved)
|
||||
&& isScreenshareMuted(getState())) {
|
||||
customActionNameKey.push('notify.unmuteScreen');
|
||||
customActionHandler.push(() => {
|
||||
dispatch(muteLocal(false, TRACK_MEDIA_TYPE.SCREENSHARE));
|
||||
dispatch(hideNotification(ASKED_TO_UNMUTE_NOTIFICATION_ID));
|
||||
|
||||
// Since permission is requested by raising the hand, lower it not to rely on dominant speaker detection
|
||||
// to clear the hand.
|
||||
dispatch(raiseHand(false));
|
||||
});
|
||||
}
|
||||
|
||||
if ((mediaType === MEDIA_TYPE.VIDEO || getState()['features/av-moderation'].videoUnmuteApproved)
|
||||
&& isVideoMuted(getState())) {
|
||||
&& isVideoMuted(getState())) {
|
||||
customActionNameKey.push('notify.unmuteVideo');
|
||||
customActionHandler.push(() => {
|
||||
dispatch(muteLocal(false, TRACK_MEDIA_TYPE.VIDEO));
|
||||
dispatch(muteLocal(false, MEDIA_TYPE.VIDEO));
|
||||
dispatch(hideNotification(ASKED_TO_UNMUTE_NOTIFICATION_ID));
|
||||
|
||||
// Since permission is requested by raising the hand, lower it not to rely on dominant speaker detection
|
||||
// to clear the hand.
|
||||
// lower hand as there will be no audio and change in dominant speaker to clear it
|
||||
dispatch(raiseHand(false));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
|
||||
import {
|
||||
PARTICIPANT_LEFT,
|
||||
PARTICIPANT_UPDATED
|
||||
@@ -15,21 +16,14 @@ import {
|
||||
PARTICIPANT_PENDING_AUDIO,
|
||||
PARTICIPANT_REJECTED
|
||||
} from './actionTypes';
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
MEDIA_TYPE_TO_PENDING_STORE_KEY,
|
||||
type MediaType
|
||||
} from './constants';
|
||||
import { MEDIA_TYPE_TO_PENDING_STORE_KEY } from './constants';
|
||||
|
||||
const initialState = {
|
||||
audioModerationEnabled: false,
|
||||
desktopModerationEnabled: false,
|
||||
videoModerationEnabled: false,
|
||||
audioWhitelist: {},
|
||||
desktopWhitelist: {},
|
||||
videoWhitelist: {},
|
||||
pendingAudio: [],
|
||||
pendingDesktop: [],
|
||||
pendingVideo: []
|
||||
};
|
||||
|
||||
@@ -37,11 +31,7 @@ export interface IAVModerationState {
|
||||
audioModerationEnabled: boolean;
|
||||
audioUnmuteApproved?: boolean | undefined;
|
||||
audioWhitelist: { [id: string]: boolean; };
|
||||
desktopModerationEnabled: boolean;
|
||||
desktopUnmuteApproved?: boolean | undefined;
|
||||
desktopWhitelist: { [id: string]: boolean; };
|
||||
pendingAudio: Array<{ id: string; }>;
|
||||
pendingDesktop: Array<{ id: string; }>;
|
||||
pendingVideo: Array<{ id: string; }>;
|
||||
videoModerationEnabled: boolean;
|
||||
videoUnmuteApproved?: boolean | undefined;
|
||||
@@ -87,61 +77,28 @@ ReducerRegistry.register<IAVModerationState>('features/av-moderation',
|
||||
(state = initialState, action): IAVModerationState => {
|
||||
switch (action.type) {
|
||||
case DISABLE_MODERATION: {
|
||||
let newState = {};
|
||||
|
||||
switch (action.mediaType) {
|
||||
case MEDIA_TYPE.AUDIO:
|
||||
newState = {
|
||||
const newState = action.mediaType === MEDIA_TYPE.AUDIO
|
||||
? {
|
||||
audioModerationEnabled: false,
|
||||
audioUnmuteApproved: undefined
|
||||
};
|
||||
break;
|
||||
case MEDIA_TYPE.DESKTOP:
|
||||
newState = {
|
||||
desktopModerationEnabled: false,
|
||||
desktopUnmuteApproved: undefined
|
||||
};
|
||||
break;
|
||||
case MEDIA_TYPE.VIDEO:
|
||||
newState = {
|
||||
} : {
|
||||
videoModerationEnabled: false,
|
||||
videoUnmuteApproved: undefined
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
...newState,
|
||||
audioWhitelist: {},
|
||||
desktopWhitelist: {},
|
||||
videoWhitelist: {},
|
||||
pendingAudio: [],
|
||||
pendingDesktop: [],
|
||||
pendingVideo: []
|
||||
};
|
||||
}
|
||||
|
||||
case ENABLE_MODERATION: {
|
||||
let newState = {};
|
||||
|
||||
switch (action.mediaType) {
|
||||
case MEDIA_TYPE.AUDIO:
|
||||
newState = {
|
||||
audioModerationEnabled: true,
|
||||
};
|
||||
break;
|
||||
case MEDIA_TYPE.DESKTOP:
|
||||
newState = {
|
||||
desktopModerationEnabled: true,
|
||||
};
|
||||
break;
|
||||
case MEDIA_TYPE.VIDEO:
|
||||
newState = {
|
||||
videoModerationEnabled: true,
|
||||
};
|
||||
break;
|
||||
}
|
||||
const newState = action.mediaType === MEDIA_TYPE.AUDIO
|
||||
? { audioModerationEnabled: true } : { videoModerationEnabled: true };
|
||||
|
||||
return {
|
||||
...state,
|
||||
@@ -150,25 +107,8 @@ ReducerRegistry.register<IAVModerationState>('features/av-moderation',
|
||||
}
|
||||
|
||||
case LOCAL_PARTICIPANT_APPROVED: {
|
||||
let newState = {};
|
||||
|
||||
switch (action.mediaType) {
|
||||
case MEDIA_TYPE.AUDIO:
|
||||
newState = {
|
||||
audioUnmuteApproved: true
|
||||
};
|
||||
break;
|
||||
case MEDIA_TYPE.DESKTOP:
|
||||
newState = {
|
||||
desktopUnmuteApproved: true
|
||||
};
|
||||
break;
|
||||
case MEDIA_TYPE.VIDEO:
|
||||
newState = {
|
||||
videoUnmuteApproved: true
|
||||
};
|
||||
break;
|
||||
}
|
||||
const newState = action.mediaType === MEDIA_TYPE.AUDIO
|
||||
? { audioUnmuteApproved: true } : { videoUnmuteApproved: true };
|
||||
|
||||
return {
|
||||
...state,
|
||||
@@ -177,25 +117,8 @@ ReducerRegistry.register<IAVModerationState>('features/av-moderation',
|
||||
}
|
||||
|
||||
case LOCAL_PARTICIPANT_REJECTED: {
|
||||
let newState = {};
|
||||
|
||||
switch (action.mediaType) {
|
||||
case MEDIA_TYPE.AUDIO:
|
||||
newState = {
|
||||
audioUnmuteApproved: false
|
||||
};
|
||||
break;
|
||||
case MEDIA_TYPE.DESKTOP:
|
||||
newState = {
|
||||
desktopUnmuteApproved: false
|
||||
};
|
||||
break;
|
||||
case MEDIA_TYPE.VIDEO:
|
||||
newState = {
|
||||
videoUnmuteApproved: false
|
||||
};
|
||||
break;
|
||||
}
|
||||
const newState = action.mediaType === MEDIA_TYPE.AUDIO
|
||||
? { audioUnmuteApproved: false } : { videoUnmuteApproved: false };
|
||||
|
||||
return {
|
||||
...state,
|
||||
@@ -223,7 +146,7 @@ ReducerRegistry.register<IAVModerationState>('features/av-moderation',
|
||||
|
||||
case PARTICIPANT_UPDATED: {
|
||||
const participant = action.participant;
|
||||
const { audioModerationEnabled, desktopModerationEnabled, videoModerationEnabled } = state;
|
||||
const { audioModerationEnabled, videoModerationEnabled } = state;
|
||||
let hasStateChanged = false;
|
||||
|
||||
// skips changing the reference of pendingAudio or pendingVideo,
|
||||
@@ -232,10 +155,6 @@ ReducerRegistry.register<IAVModerationState>('features/av-moderation',
|
||||
hasStateChanged = _updatePendingParticipant(MEDIA_TYPE.AUDIO, participant, state);
|
||||
}
|
||||
|
||||
if (desktopModerationEnabled) {
|
||||
hasStateChanged = hasStateChanged || _updatePendingParticipant(MEDIA_TYPE.DESKTOP, participant, state);
|
||||
}
|
||||
|
||||
if (videoModerationEnabled) {
|
||||
hasStateChanged = hasStateChanged || _updatePendingParticipant(MEDIA_TYPE.VIDEO, participant, state);
|
||||
}
|
||||
@@ -249,10 +168,9 @@ ReducerRegistry.register<IAVModerationState>('features/av-moderation',
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
case PARTICIPANT_LEFT: {
|
||||
const participant = action.participant;
|
||||
const { audioModerationEnabled, desktopModerationEnabled, videoModerationEnabled } = state;
|
||||
const { audioModerationEnabled, videoModerationEnabled } = state;
|
||||
let hasStateChanged = false;
|
||||
|
||||
// skips changing the reference of pendingAudio or pendingVideo,
|
||||
@@ -266,15 +184,6 @@ ReducerRegistry.register<IAVModerationState>('features/av-moderation',
|
||||
}
|
||||
}
|
||||
|
||||
if (desktopModerationEnabled) {
|
||||
const newPendingDesktop = state.pendingDesktop.filter(pending => pending.id !== participant.id);
|
||||
|
||||
if (state.pendingDesktop.length !== newPendingDesktop.length) {
|
||||
state.pendingDesktop = newPendingDesktop;
|
||||
hasStateChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (videoModerationEnabled) {
|
||||
const newPendingVideo = state.pendingVideo.filter(pending => pending.id !== participant.id);
|
||||
|
||||
@@ -304,13 +213,6 @@ ReducerRegistry.register<IAVModerationState>('features/av-moderation',
|
||||
};
|
||||
}
|
||||
|
||||
if (mediaType === MEDIA_TYPE.DESKTOP) {
|
||||
return {
|
||||
...state,
|
||||
pendingDesktop: state.pendingDesktop.filter(pending => pending.id !== id)
|
||||
};
|
||||
}
|
||||
|
||||
if (mediaType === MEDIA_TYPE.VIDEO) {
|
||||
return {
|
||||
...state,
|
||||
@@ -334,16 +236,6 @@ ReducerRegistry.register<IAVModerationState>('features/av-moderation',
|
||||
};
|
||||
}
|
||||
|
||||
if (mediaType === MEDIA_TYPE.DESKTOP) {
|
||||
return {
|
||||
...state,
|
||||
desktopWhitelist: {
|
||||
...state.desktopWhitelist,
|
||||
[id]: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (mediaType === MEDIA_TYPE.VIDEO) {
|
||||
return {
|
||||
...state,
|
||||
@@ -370,16 +262,6 @@ ReducerRegistry.register<IAVModerationState>('features/av-moderation',
|
||||
};
|
||||
}
|
||||
|
||||
if (mediaType === MEDIA_TYPE.DESKTOP) {
|
||||
return {
|
||||
...state,
|
||||
desktopWhitelist: {
|
||||
...state.desktopWhitelist,
|
||||
[id]: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (mediaType === MEDIA_TYPE.VIDEO) {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -83,8 +83,7 @@ import {
|
||||
getConferenceState,
|
||||
getCurrentConference,
|
||||
getVisitorOptions,
|
||||
sendLocalParticipant,
|
||||
updateTrackMuteState
|
||||
sendLocalParticipant
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { IConferenceMetadata, IJitsiConference } from './reducer';
|
||||
@@ -187,15 +186,6 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
|
||||
(disableVideoMuteChange: boolean) => {
|
||||
dispatch(setVideoUnmutePermissions(disableVideoMuteChange));
|
||||
});
|
||||
conference.on(
|
||||
JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
|
||||
({ audio, video }: { audio: boolean; video: boolean; }) => {
|
||||
dispatch(onStartMutedPolicyChanged(audio, video));
|
||||
|
||||
updateTrackMuteState(state, dispatch, true);
|
||||
updateTrackMuteState(state, dispatch, false);
|
||||
}
|
||||
);
|
||||
|
||||
// Dispatches into features/base/tracks follow:
|
||||
|
||||
@@ -1023,8 +1013,6 @@ export function setStartMutedPolicy(
|
||||
audio: startAudioMuted,
|
||||
video: startVideoMuted
|
||||
});
|
||||
|
||||
dispatch(onStartMutedPolicyChanged(startAudioMuted, startVideoMuted));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,3 @@ export const CONFERENCE_LEAVE_REASONS = {
|
||||
SWITCH_ROOM: 'switch_room',
|
||||
UNRECOVERABLE_ERROR: 'unrecoverable_error'
|
||||
};
|
||||
|
||||
/**
|
||||
* The ID of the notification that is shown when the user is muted by focus.
|
||||
*/
|
||||
export const START_MUTED_NOTIFICATION_ID = 'start-muted';
|
||||
|
||||
@@ -3,13 +3,9 @@ import { upperFirst, words } from 'lodash-es';
|
||||
|
||||
import { getName } from '../../app/functions';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { determineTranscriptionLanguage } from '../../transcribing/functions';
|
||||
import { IStateful } from '../app/types';
|
||||
import { JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
import { setAudioMuted, setVideoMuted } from '../media/actions';
|
||||
import { VIDEO_MUTISM_AUTHORITY } from '../media/constants';
|
||||
import {
|
||||
participantJoined,
|
||||
participantLeft
|
||||
@@ -26,8 +22,7 @@ import { setObfuscatedRoom } from './actions';
|
||||
import {
|
||||
AVATAR_URL_COMMAND,
|
||||
EMAIL_COMMAND,
|
||||
JITSI_CONFERENCE_URL_KEY,
|
||||
START_MUTED_NOTIFICATION_ID
|
||||
JITSI_CONFERENCE_URL_KEY
|
||||
} from './constants';
|
||||
import logger from './logger';
|
||||
import { IJitsiConference } from './reducer';
|
||||
@@ -579,42 +574,3 @@ function safeStartCase(s = '') {
|
||||
(result, word, index) => result + (index ? ' ' : '') + upperFirst(word)
|
||||
, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the mute state of the track based on the start muted policy.
|
||||
*
|
||||
* @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
|
||||
* @param {Function} dispatch - Redux dispatch function.
|
||||
* @param {boolean} isAudio - Whether the track is audio or video.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function updateTrackMuteState(stateful: IStateful, dispatch: IStore['dispatch'], isAudio: boolean) {
|
||||
const state = toState(stateful);
|
||||
const mutedPolicyKey = isAudio ? 'startAudioMutedPolicy' : 'startVideoMutedPolicy';
|
||||
const mutedPolicyValue = state['features/base/conference'][mutedPolicyKey];
|
||||
|
||||
// Currently, the policy only supports force muting others, not unmuting them.
|
||||
if (!mutedPolicyValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
let muteStateUpdated = false;
|
||||
const { muted } = isAudio ? state['features/base/media'].audio : state['features/base/media'].video;
|
||||
|
||||
if (isAudio && !Boolean(muted)) {
|
||||
dispatch(setAudioMuted(mutedPolicyValue, true));
|
||||
muteStateUpdated = true;
|
||||
} else if (!isAudio && !Boolean(muted)) {
|
||||
// TODO: Add a new authority for video mutism for the moderator case.
|
||||
dispatch(setVideoMuted(mutedPolicyValue, VIDEO_MUTISM_AUTHORITY.USER, true));
|
||||
muteStateUpdated = true;
|
||||
}
|
||||
|
||||
if (muteStateUpdated) {
|
||||
dispatch(showNotification({
|
||||
titleKey: 'notify.mutedTitle',
|
||||
descriptionKey: 'notify.muted',
|
||||
uid: START_MUTED_NOTIFICATION_ID // use the same id, to make sure we show one notification
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ import {
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { IConferenceMetadata } from './reducer';
|
||||
import './subscriber';
|
||||
|
||||
/**
|
||||
* Handler for before unload event.
|
||||
|
||||
61
react/features/base/conference/subscriber.ts
Normal file
61
react/features/base/conference/subscriber.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { setAudioMuted, setVideoMuted } from '../media/actions';
|
||||
import { VIDEO_MUTISM_AUTHORITY } from '../media/constants';
|
||||
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||
|
||||
let hasShownNotification = false;
|
||||
|
||||
/**
|
||||
* Handles changes in the start muted policy for audio and video tracks in the meta data set for the conference.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/conference'].startAudioMutedPolicy,
|
||||
/* listener */ (startAudioMutedPolicy, store) => {
|
||||
_updateTrackMuteState(store, true);
|
||||
});
|
||||
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/conference'].startVideoMutedPolicy,
|
||||
/* listener */(startVideoMutedPolicy, store) => {
|
||||
_updateTrackMuteState(store, false);
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates the mute state of the track based on the start muted policy.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @param {boolean} isAudio - Whether the track is audio or video.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _updateTrackMuteState(store: IStore, isAudio: boolean) {
|
||||
const { dispatch, getState } = store;
|
||||
const mutedPolicyKey = isAudio ? 'startAudioMutedPolicy' : 'startVideoMutedPolicy';
|
||||
const mutedPolicyValue = getState()['features/base/conference'][mutedPolicyKey];
|
||||
|
||||
// Currently, the policy only supports force muting others, not unmuting them.
|
||||
if (!mutedPolicyValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
let muteStateUpdated = false;
|
||||
const { muted } = isAudio ? getState()['features/base/media'].audio : getState()['features/base/media'].video;
|
||||
|
||||
if (isAudio && !Boolean(muted)) {
|
||||
dispatch(setAudioMuted(mutedPolicyValue, true));
|
||||
muteStateUpdated = true;
|
||||
} else if (!isAudio && !Boolean(muted)) {
|
||||
// TODO: Add a new authority for video mutism for the moderator case.
|
||||
dispatch(setVideoMuted(mutedPolicyValue, VIDEO_MUTISM_AUTHORITY.USER, true));
|
||||
muteStateUpdated = true;
|
||||
}
|
||||
|
||||
if (!hasShownNotification && muteStateUpdated) {
|
||||
hasShownNotification = true;
|
||||
dispatch(showNotification({
|
||||
titleKey: 'notify.mutedTitle',
|
||||
descriptionKey: 'notify.muted'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { showModeratedNotification } from '../../av-moderation/actions';
|
||||
import { MEDIA_TYPE as AVM_MEDIA_TYPE } from '../../av-moderation/constants';
|
||||
import { shouldShowModeratedNotification } from '../../av-moderation/functions';
|
||||
import { isModerationNotificationDisplayed } from '../../notifications/functions';
|
||||
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
TOGGLE_CAMERA_FACING_MODE
|
||||
} from './actionTypes';
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
MediaType,
|
||||
SCREENSHARE_MUTISM_AUTHORITY,
|
||||
VIDEO_MUTISM_AUTHORITY
|
||||
@@ -56,23 +56,10 @@ export function setAudioAvailable(available: boolean) {
|
||||
* }}
|
||||
*/
|
||||
export function setAudioMuted(muted: boolean, ensureTrack = false) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
|
||||
// check for A/V Moderation when trying to unmute
|
||||
if (!muted && shouldShowModeratedNotification(AVM_MEDIA_TYPE.AUDIO, state)) {
|
||||
if (!isModerationNotificationDisplayed(AVM_MEDIA_TYPE.AUDIO, state)) {
|
||||
ensureTrack && dispatch(showModeratedNotification(AVM_MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SET_AUDIO_MUTED,
|
||||
ensureTrack,
|
||||
muted
|
||||
});
|
||||
return {
|
||||
type: SET_AUDIO_MUTED,
|
||||
ensureTrack,
|
||||
muted
|
||||
};
|
||||
}
|
||||
|
||||
@@ -139,9 +126,9 @@ export function setScreenshareMuted(
|
||||
const state = getState();
|
||||
|
||||
// check for A/V Moderation when trying to unmute
|
||||
if (!muted && shouldShowModeratedNotification(AVM_MEDIA_TYPE.DESKTOP, state)) {
|
||||
if (!isModerationNotificationDisplayed(AVM_MEDIA_TYPE.DESKTOP, state)) {
|
||||
ensureTrack && dispatch(showModeratedNotification(AVM_MEDIA_TYPE.DESKTOP));
|
||||
if (!muted && shouldShowModeratedNotification(MEDIA_TYPE.SCREENSHARE, state)) {
|
||||
if (!isModerationNotificationDisplayed(MEDIA_TYPE.SCREENSHARE, state)) {
|
||||
ensureTrack && dispatch(showModeratedNotification(MEDIA_TYPE.SCREENSHARE));
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -197,9 +184,9 @@ export function setVideoMuted(
|
||||
const state = getState();
|
||||
|
||||
// check for A/V Moderation when trying to unmute
|
||||
if (!muted && shouldShowModeratedNotification(AVM_MEDIA_TYPE.VIDEO, state)) {
|
||||
if (!isModerationNotificationDisplayed(AVM_MEDIA_TYPE.VIDEO, state)) {
|
||||
ensureTrack && dispatch(showModeratedNotification(AVM_MEDIA_TYPE.VIDEO));
|
||||
if (!muted && shouldShowModeratedNotification(MEDIA_TYPE.VIDEO, state)) {
|
||||
if (!isModerationNotificationDisplayed(MEDIA_TYPE.VIDEO, state)) {
|
||||
ensureTrack && dispatch(showModeratedNotification(MEDIA_TYPE.VIDEO));
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
export const CAMERA_FACING_MODE: Record<string, string> = {
|
||||
export const CAMERA_FACING_MODE = {
|
||||
ENVIRONMENT: 'environment',
|
||||
USER: 'user'
|
||||
};
|
||||
|
||||
@@ -88,16 +88,6 @@ export function getStartWithVideoMuted(stateful: IStateful) {
|
||||
return Boolean(getPropertyValue(stateful, 'startWithVideoMuted', START_WITH_AUDIO_VIDEO_MUTED_SOURCES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether screen-share is currently muted.
|
||||
*
|
||||
* @param {Function|Object} stateful - The redux store, state, or {@code getState} function.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isScreenshareMuted(stateful: IStateful) {
|
||||
return Boolean(toState(stateful)['features/base/media'].screenshare.muted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether video is currently muted.
|
||||
*
|
||||
|
||||
@@ -8,17 +8,15 @@ import {
|
||||
} from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IStore } from '../../app/types';
|
||||
import { MEDIA_TYPE as AVM_MEDIA_TYPE } from '../../av-moderation/constants';
|
||||
import { isForceMuted } from '../../av-moderation/functions';
|
||||
import { APP_STATE_CHANGED } from '../../mobile/background/actionTypes';
|
||||
import { showWarningNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { isForceMuted } from '../../participants-pane/functions';
|
||||
import { isScreenMediaShared } from '../../screen-share/functions';
|
||||
import { SET_AUDIO_ONLY } from '../audio-only/actionTypes';
|
||||
import { setAudioOnly } from '../audio-only/actions';
|
||||
import { SET_ROOM } from '../conference/actionTypes';
|
||||
import { isRoomValid } from '../conference/functions';
|
||||
import { PARTICIPANT_MUTED_US } from '../participants/actionTypes';
|
||||
import { getLocalParticipant } from '../participants/functions';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { getPropertyValue } from '../settings/functions.any';
|
||||
@@ -48,8 +46,7 @@ import {
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
SCREENSHARE_MUTISM_AUTHORITY,
|
||||
VIDEO_MUTISM_AUTHORITY,
|
||||
VIDEO_TYPE
|
||||
VIDEO_MUTISM_AUTHORITY
|
||||
} from './constants';
|
||||
import { getStartWithAudioMuted, getStartWithVideoMuted } from './functions';
|
||||
import logger from './logger';
|
||||
@@ -69,24 +66,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case APP_STATE_CHANGED:
|
||||
return _appStateChanged(store, next, action);
|
||||
|
||||
case PARTICIPANT_MUTED_US: {
|
||||
const { dispatch } = store;
|
||||
const { track } = action;
|
||||
|
||||
// Sync the media muted state with the track muted state.
|
||||
if (track.isAudioTrack()) {
|
||||
dispatch(setAudioMuted(true, /* ensureTrack */ false));
|
||||
} else if (track.isVideoTrack()) {
|
||||
if (track.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
||||
dispatch(setScreenshareMuted(true, SCREENSHARE_MUTISM_AUTHORITY.USER, /* ensureTrack */ false));
|
||||
} else {
|
||||
dispatch(setVideoMuted(true, VIDEO_MUTISM_AUTHORITY.USER, /* ensureTrack */ false));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_AUDIO_ONLY:
|
||||
return _setAudioOnly(store, next, action);
|
||||
|
||||
@@ -109,7 +88,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const state = store.getState();
|
||||
const participant = getLocalParticipant(state);
|
||||
|
||||
if (!action.muted && isForceMuted(participant, AVM_MEDIA_TYPE.AUDIO, state)) {
|
||||
if (!action.muted && isForceMuted(participant, MEDIA_TYPE.AUDIO, state)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -134,7 +113,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const state = store.getState();
|
||||
const participant = getLocalParticipant(state);
|
||||
|
||||
if (!action.muted && isForceMuted(participant, AVM_MEDIA_TYPE.DESKTOP, state)) {
|
||||
if (!action.muted && isForceMuted(participant, MEDIA_TYPE.SCREENSHARE, state)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -143,7 +122,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const state = store.getState();
|
||||
const participant = getLocalParticipant(state);
|
||||
|
||||
if (!action.muted && isForceMuted(participant, AVM_MEDIA_TYPE.VIDEO, state)) {
|
||||
if (!action.muted && isForceMuted(participant, MEDIA_TYPE.VIDEO, state)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -112,17 +112,6 @@ export const PARTICIPANT_KICKED = 'PARTICIPANT_KICKED';
|
||||
*/
|
||||
export const PARTICIPANT_LEFT = 'PARTICIPANT_LEFT';
|
||||
|
||||
/**
|
||||
* Action to handle case when the remote participant mutes the local participant.
|
||||
*
|
||||
* {
|
||||
* type: PARTICIPANT_MUTED_US,
|
||||
* participant: Participant,
|
||||
* track: JitsiLocalTrack
|
||||
* }
|
||||
*/
|
||||
export const PARTICIPANT_MUTED_US = 'PARTICIPANT_MUTED_US';
|
||||
|
||||
/**
|
||||
* Action to handle case when the sources attached to a participant are updated.
|
||||
*
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
PARTICIPANT_JOINED,
|
||||
PARTICIPANT_KICKED,
|
||||
PARTICIPANT_LEFT,
|
||||
PARTICIPANT_MUTED_US,
|
||||
PARTICIPANT_ROLE_CHANGED,
|
||||
PARTICIPANT_SOURCES_UPDATED,
|
||||
PARTICIPANT_UPDATED,
|
||||
@@ -468,10 +467,19 @@ export function participantUpdated(participant: IParticipant = { id: '' }) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function participantMutedUs(participant: any, track: any) {
|
||||
return {
|
||||
type: PARTICIPANT_MUTED_US,
|
||||
participant,
|
||||
track
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
if (!participant) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isAudio = track.isAudioTrack();
|
||||
|
||||
dispatch(showNotification({
|
||||
titleKey: isAudio ? 'notify.mutedRemotelyTitle' : 'notify.videoMutedRemotelyTitle',
|
||||
titleArguments: {
|
||||
participantDisplayName: getParticipantDisplayName(getState, participant.getId())
|
||||
}
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -392,7 +392,7 @@ export function getMutedStateByParticipantAndMediaType(
|
||||
if (mediaType === MEDIA_TYPE.AUDIO) {
|
||||
return Array.from(sources.values())[0].muted;
|
||||
}
|
||||
const videoType = mediaType === MEDIA_TYPE.VIDEO ? VIDEO_TYPE.CAMERA : VIDEO_TYPE.DESKTOP;
|
||||
const videoType = mediaType === MEDIA_TYPE.VIDEO ? VIDEO_TYPE.CAMERA : VIDEO_TYPE.SCREENSHARE;
|
||||
const source = Array.from(sources.values()).find(src => src.videoType === videoType);
|
||||
|
||||
return source?.muted ?? true;
|
||||
|
||||
@@ -3,19 +3,7 @@ import { batch } from 'react-redux';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import {
|
||||
approveParticipant,
|
||||
approveParticipantAudio,
|
||||
approveParticipantDesktop,
|
||||
approveParticipantVideo
|
||||
} from '../../av-moderation/actions';
|
||||
import {
|
||||
AUDIO_RAISED_HAND_NOTIFICATION_ID,
|
||||
DESKTOP_RAISED_HAND_NOTIFICATION_ID,
|
||||
MEDIA_TYPE,
|
||||
VIDEO_RAISED_HAND_NOTIFICATION_ID
|
||||
} from '../../av-moderation/constants';
|
||||
import { isForceMuted } from '../../av-moderation/functions';
|
||||
import { approveParticipant, approveParticipantAudio, approveParticipantVideo } from '../../av-moderation/actions';
|
||||
import { UPDATE_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
|
||||
import { getBreakoutRooms } from '../../breakout-rooms/functions';
|
||||
import { toggleE2EE } from '../../e2ee/actions';
|
||||
@@ -27,6 +15,7 @@ import {
|
||||
RAISE_HAND_NOTIFICATION_ID
|
||||
} from '../../notifications/constants';
|
||||
import { open as openParticipantsPane } from '../../participants-pane/actions';
|
||||
import { isForceMuted } from '../../participants-pane/functions';
|
||||
import { CALLING, INVITED } from '../../presence-status/constants';
|
||||
import { RAISE_HAND_SOUND_ID } from '../../reactions/constants';
|
||||
import { RECORDING_OFF_SOUND_ID, RECORDING_ON_SOUND_ID } from '../../recording/constants';
|
||||
@@ -37,7 +26,7 @@ import { IJitsiConference } from '../conference/reducer';
|
||||
import { SET_CONFIG } from '../config/actionTypes';
|
||||
import { getDisableRemoveRaisedHandOnFocus } from '../config/functions.any';
|
||||
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
|
||||
import { VIDEO_TYPE } from '../media/constants';
|
||||
import { MEDIA_TYPE } from '../media/constants';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||
import { playSound, registerSound, unregisterSound } from '../sounds/actions';
|
||||
@@ -54,7 +43,6 @@ import {
|
||||
OVERWRITE_PARTICIPANT_NAME,
|
||||
PARTICIPANT_JOINED,
|
||||
PARTICIPANT_LEFT,
|
||||
PARTICIPANT_MUTED_US,
|
||||
PARTICIPANT_UPDATED,
|
||||
RAISE_HAND_UPDATED,
|
||||
SET_LOCAL_PARTICIPANT_RECORDING_STATUS
|
||||
@@ -304,31 +292,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case PARTICIPANT_MUTED_US: {
|
||||
const { dispatch, getState } = store;
|
||||
const { participant, track } = action;
|
||||
let titleKey;
|
||||
|
||||
if (track.isAudioTrack()) {
|
||||
titleKey = 'notify.mutedRemotelyTitle';
|
||||
} else if (track.isVideoTrack()) {
|
||||
if (track.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
||||
titleKey = 'notify.desktopMutedRemotelyTitle';
|
||||
} else {
|
||||
titleKey = 'notify.videoMutedRemotelyTitle';
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
titleKey,
|
||||
titleArguments: {
|
||||
participantDisplayName: getParticipantDisplayName(getState, participant.getId())
|
||||
}
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PARTICIPANT_UPDATED:
|
||||
return _participantJoinedOrUpdated(store, next, action);
|
||||
|
||||
@@ -823,124 +786,77 @@ function _raiseHandUpdated({ dispatch, getState }: IStore, conference: IJitsiCon
|
||||
APP.API.notifyRaiseHandUpdated(participantId, raisedHandTimestamp);
|
||||
}
|
||||
|
||||
if (!raisedHandTimestamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Display notifications about raised hands.
|
||||
|
||||
const isModerator = isLocalParticipantModerator(state);
|
||||
const participant = getParticipantById(state, participantId);
|
||||
const participantName = getParticipantDisplayName(state, participantId);
|
||||
|
||||
let shouldDisplayAllowAudio = false;
|
||||
let shouldDisplayAllowVideo = false;
|
||||
let shouldDisplayAllowDesktop = false;
|
||||
|
||||
if (isModerator) {
|
||||
shouldDisplayAllowAudio = isForceMuted(participant, MEDIA_TYPE.AUDIO, state);
|
||||
shouldDisplayAllowVideo = isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
|
||||
shouldDisplayAllowDesktop = isForceMuted(participant, MEDIA_TYPE.DESKTOP, state);
|
||||
}
|
||||
|
||||
if (shouldDisplayAllowAudio || shouldDisplayAllowVideo || shouldDisplayAllowDesktop) {
|
||||
const action: {
|
||||
customActionHandler: Array<() => void>;
|
||||
customActionNameKey: string[];
|
||||
} = {
|
||||
customActionHandler: [],
|
||||
customActionNameKey: [],
|
||||
let action;
|
||||
|
||||
if (shouldDisplayAllowAudio || shouldDisplayAllowVideo) {
|
||||
action = {
|
||||
customActionNameKey: [] as string[],
|
||||
customActionHandler: [] as Function[]
|
||||
};
|
||||
|
||||
// Always add a "allow all" at the end of the list.
|
||||
action.customActionNameKey.push('notify.allowAll');
|
||||
action.customActionHandler.push(() => {
|
||||
dispatch(approveParticipant(participantId));
|
||||
dispatch(hideNotification(AUDIO_RAISED_HAND_NOTIFICATION_ID));
|
||||
dispatch(hideNotification(DESKTOP_RAISED_HAND_NOTIFICATION_ID));
|
||||
dispatch(hideNotification(VIDEO_RAISED_HAND_NOTIFICATION_ID));
|
||||
});
|
||||
|
||||
if (shouldDisplayAllowAudio) {
|
||||
const customActionNameKey = action.customActionNameKey.slice();
|
||||
const customActionHandler = action.customActionHandler.slice();
|
||||
|
||||
customActionNameKey.unshift('notify.allowAudio');
|
||||
customActionHandler.unshift(() => {
|
||||
action.customActionNameKey.push('notify.allowAudio');
|
||||
action.customActionHandler.push(() => {
|
||||
dispatch(approveParticipantAudio(participantId));
|
||||
dispatch(hideNotification(AUDIO_RAISED_HAND_NOTIFICATION_ID));
|
||||
dispatch(hideNotification(RAISE_HAND_NOTIFICATION_ID));
|
||||
});
|
||||
|
||||
dispatch(showNotification({
|
||||
title: participantName,
|
||||
descriptionKey: 'notify.raisedHand',
|
||||
uid: AUDIO_RAISED_HAND_NOTIFICATION_ID,
|
||||
customActionNameKey,
|
||||
customActionHandler,
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.EXTRA_LONG));
|
||||
}
|
||||
if (shouldDisplayAllowVideo) {
|
||||
const customActionNameKey = action.customActionNameKey.slice();
|
||||
const customActionHandler = action.customActionHandler.slice();
|
||||
|
||||
customActionNameKey.unshift('notify.allowVideo');
|
||||
customActionHandler.unshift(() => {
|
||||
action.customActionNameKey.push('notify.allowVideo');
|
||||
action.customActionHandler.push(() => {
|
||||
dispatch(approveParticipantVideo(participantId));
|
||||
dispatch(hideNotification(VIDEO_RAISED_HAND_NOTIFICATION_ID));
|
||||
dispatch(hideNotification(RAISE_HAND_NOTIFICATION_ID));
|
||||
});
|
||||
|
||||
dispatch(showNotification({
|
||||
title: participantName,
|
||||
descriptionKey: 'notify.raisedHand',
|
||||
uid: VIDEO_RAISED_HAND_NOTIFICATION_ID,
|
||||
customActionNameKey,
|
||||
customActionHandler,
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.EXTRA_LONG));
|
||||
}
|
||||
if (shouldDisplayAllowDesktop) {
|
||||
const customActionNameKey = action.customActionNameKey.slice();
|
||||
const customActionHandler = action.customActionHandler.slice();
|
||||
|
||||
customActionNameKey.unshift('notify.allowDesktop');
|
||||
customActionHandler.unshift(() => {
|
||||
dispatch(approveParticipantDesktop(participantId));
|
||||
dispatch(hideNotification(DESKTOP_RAISED_HAND_NOTIFICATION_ID));
|
||||
if (shouldDisplayAllowAudio && shouldDisplayAllowVideo) {
|
||||
action.customActionNameKey.push('notify.allowBoth');
|
||||
action.customActionHandler.push(() => {
|
||||
dispatch(approveParticipant(participantId));
|
||||
dispatch(hideNotification(RAISE_HAND_NOTIFICATION_ID));
|
||||
});
|
||||
|
||||
dispatch(showNotification({
|
||||
title: participantName,
|
||||
descriptionKey: 'notify.raisedHand',
|
||||
uid: DESKTOP_RAISED_HAND_NOTIFICATION_ID,
|
||||
customActionNameKey,
|
||||
customActionHandler
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.EXTRA_LONG));
|
||||
}
|
||||
} else {
|
||||
action = {
|
||||
customActionNameKey: [ 'notify.viewParticipants' ],
|
||||
customActionHandler: [ () => dispatch(openParticipantsPane()) ]
|
||||
};
|
||||
}
|
||||
|
||||
if (raisedHandTimestamp) {
|
||||
let notificationTitle;
|
||||
const participantName = getParticipantDisplayName(state, participantId);
|
||||
const { raisedHandsQueue } = state['features/base/participants'];
|
||||
|
||||
if (raisedHandsQueue.length > 1) {
|
||||
const raisedHands = raisedHandsQueue.length - 1;
|
||||
|
||||
notificationTitle = i18n.t('notify.raisedHands', {
|
||||
participantName,
|
||||
raisedHands: raisedHandsQueue.length - 1
|
||||
raisedHands
|
||||
});
|
||||
} else {
|
||||
notificationTitle = participantName;
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
titleKey: 'notify.somebody',
|
||||
title: notificationTitle,
|
||||
descriptionKey: 'notify.raisedHand',
|
||||
concatText: true,
|
||||
uid: RAISE_HAND_NOTIFICATION_ID,
|
||||
customActionNameKey: [ 'notify.viewParticipants' ],
|
||||
customActionHandler: [ () => dispatch(openParticipantsPane()) ]
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
...action
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
dispatch(playSound(RAISE_HAND_SOUND_ID));
|
||||
}
|
||||
|
||||
dispatch(playSound(RAISE_HAND_SOUND_ID));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from '../../../../modules/UI/UIErrors';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { showModeratedNotification } from '../../av-moderation/actions';
|
||||
import { MEDIA_TYPE as AVM_MEDIA_TYPE } from '../../av-moderation/constants';
|
||||
import { shouldShowModeratedNotification } from '../../av-moderation/functions';
|
||||
import { setNoiseSuppressionEnabled } from '../../noise-suppression/actions';
|
||||
import { showErrorNotification, showNotification } from '../../notifications/actions';
|
||||
@@ -56,10 +55,10 @@ export function toggleScreensharing(
|
||||
shareOptions: IShareOptions = {}) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
// check for A/V Moderation when trying to start screen sharing
|
||||
if ((enabled || enabled === undefined) && shouldShowModeratedNotification(AVM_MEDIA_TYPE.DESKTOP, getState())) {
|
||||
dispatch(showModeratedNotification(AVM_MEDIA_TYPE.DESKTOP));
|
||||
if ((enabled || enabled === undefined) && shouldShowModeratedNotification(MEDIA_TYPE.VIDEO, getState())) {
|
||||
dispatch(showModeratedNotification(MEDIA_TYPE.SCREENSHARE));
|
||||
|
||||
return Promise.resolve();
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
return _toggleScreenSharing({
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { getSsrcRewritingFeatureFlag } from '../config/functions.any';
|
||||
import { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
||||
import { gumPending } from '../media/actions';
|
||||
import { CAMERA_FACING_MODE, MEDIA_TYPE, MediaType, VIDEO_TYPE } from '../media/constants';
|
||||
import { IMediaState } from '../media/reducer';
|
||||
import { IGUMPendingState } from '../media/types';
|
||||
import {
|
||||
getMutedStateByParticipantAndMediaType,
|
||||
getVirtualScreenshareParticipantOwnerId,
|
||||
isScreenShareParticipant
|
||||
} from '../participants/functions';
|
||||
@@ -37,10 +35,6 @@ export function isParticipantMediaMuted(participant: IParticipant | undefined,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getSsrcRewritingFeatureFlag(state)) {
|
||||
return getMutedStateByParticipantAndMediaType(state, participant, mediaType);
|
||||
}
|
||||
|
||||
const tracks = getTrackState(state);
|
||||
|
||||
if (participant?.local) {
|
||||
@@ -59,21 +53,10 @@ export function isParticipantMediaMuted(participant: IParticipant | undefined,
|
||||
* @param {IReduxState} state - Global state.
|
||||
* @returns {boolean} - Is audio muted for the participant.
|
||||
*/
|
||||
export function isParticipantAudioMuted(participant: IParticipant | undefined, state: IReduxState) {
|
||||
export function isParticipantAudioMuted(participant: IParticipant, state: IReduxState) {
|
||||
return isParticipantMediaMuted(participant, MEDIA_TYPE.AUDIO, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the participant is screen-share muted.
|
||||
*
|
||||
* @param {IParticipant} participant - Participant reference.
|
||||
* @param {IReduxState} state - Global state.
|
||||
* @returns {boolean} - Is screen-share muted for the participant.
|
||||
*/
|
||||
export function isParticipantScreenShareMuted(participant: IParticipant | undefined, state: IReduxState) {
|
||||
return isParticipantMediaMuted(participant, MEDIA_TYPE.SCREENSHARE, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the participant is video muted.
|
||||
*
|
||||
@@ -135,10 +118,6 @@ export function getLocalJitsiDesktopTrack(state: IReduxState) {
|
||||
* @returns {(Track|undefined)}
|
||||
*/
|
||||
export function getLocalTrack(tracks: ITrack[], mediaType: MediaType, includePending = false) {
|
||||
if (mediaType === MEDIA_TYPE.SCREENSHARE) {
|
||||
return getLocalDesktopTrack(tracks, includePending);
|
||||
}
|
||||
|
||||
return (
|
||||
getLocalTracks(tracks, includePending)
|
||||
.find(t => t.mediaType === mediaType));
|
||||
@@ -237,14 +216,6 @@ export function getTrackByMediaTypeAndParticipant(
|
||||
tracks: ITrack[],
|
||||
mediaType: MediaType,
|
||||
participantId?: string) {
|
||||
if (!participantId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mediaType === MEDIA_TYPE.SCREENSHARE) {
|
||||
return getScreenShareTrack(tracks, participantId);
|
||||
}
|
||||
|
||||
return tracks.find(
|
||||
t => Boolean(t.jitsiTrack) && t.participantId === participantId && t.mediaType === mediaType
|
||||
);
|
||||
|
||||
@@ -188,6 +188,10 @@ function _setMuted(store: IStore, { ensureTrack, muted }: {
|
||||
const localTrack = _getLocalTrack(store, mediaType, /* includePending */ true);
|
||||
const state = getState();
|
||||
|
||||
if (mediaType === MEDIA_TYPE.SCREENSHARE && !muted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (localTrack) {
|
||||
// The `jitsiTrack` property will have a value only for a localTrack for which `getUserMedia` has already
|
||||
// completed. If there's no `jitsiTrack`, then the `muted` state will be applied once the `jitsiTrack` is
|
||||
@@ -199,11 +203,8 @@ function _setMuted(store: IStore, { ensureTrack, muted }: {
|
||||
.catch(() => dispatch(trackMuteUnmuteFailed(localTrack, muted)));
|
||||
}
|
||||
} else if (!muted && ensureTrack) {
|
||||
// TODO(saghul): reconcile these 2 types.
|
||||
const createMediaType = mediaType === MEDIA_TYPE.SCREENSHARE ? 'desktop' : mediaType;
|
||||
|
||||
typeof APP !== 'undefined' && dispatch(gumPending([ mediaType ], IGUMPendingState.PENDING_UNMUTE));
|
||||
dispatch(createLocalTracksA({ devices: [ createMediaType ] })).then(() => {
|
||||
dispatch(createLocalTracksA({ devices: [ mediaType ] })).then(() => {
|
||||
typeof APP !== 'undefined' && dispatch(gumPending([ mediaType ], IGUMPendingState.NONE));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ const IconButton: React.FC<IIconButtonProps> = ({
|
||||
style = { [
|
||||
iconButtonContainerStyles,
|
||||
style
|
||||
] as ViewStyle[] }
|
||||
] as ViewStyle }
|
||||
underlayColor = { underlayColor }>
|
||||
<Icon
|
||||
color = { color }
|
||||
|
||||
@@ -8,7 +8,7 @@ import { isVpaasMeeting } from '../jaas/functions';
|
||||
import DeepLinkingDesktopPage from './components/DeepLinkingDesktopPage';
|
||||
import DeepLinkingMobilePage from './components/DeepLinkingMobilePage';
|
||||
import NoMobileApp from './components/NoMobileApp';
|
||||
import { _openDesktopApp } from './openDesktopApp.web';
|
||||
import { _openDesktopApp } from './openDesktopApp';
|
||||
|
||||
/**
|
||||
* Generates a deep linking URL based on the current window URL.
|
||||
@@ -1,7 +1,7 @@
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
|
||||
import { OPEN_DESKTOP_APP } from './actionTypes';
|
||||
import { openDesktopApp } from './functions.web';
|
||||
import { openDesktopApp } from './functions';
|
||||
|
||||
/**
|
||||
* Implements the middleware of the deep linking feature.
|
||||
@@ -1,4 +1,3 @@
|
||||
import { executeAfterLoad } from '../app/functions.web';
|
||||
import { IReduxState } from '../app/types';
|
||||
import { URI_PROTOCOL_PATTERN } from '../base/util/uri';
|
||||
|
||||
@@ -17,10 +16,7 @@ export function _openDesktopApp(_state: Object) {
|
||||
const { appScheme } = deeplinkingDesktop;
|
||||
const regex = new RegExp(URI_PROTOCOL_PATTERN, 'gi');
|
||||
|
||||
// This is needed to workaround https://issues.chromium.org/issues/41398687
|
||||
executeAfterLoad(() => {
|
||||
window.location.href = window.location.href.replace(regex, `${appScheme}:`);
|
||||
});
|
||||
window.location.href = window.location.href.replace(regex, `${appScheme}:`);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { activateKeepAwake, deactivateKeepAwake } from '@sayem314/react-native-keep-awake';
|
||||
import KeepAwake from 'react-native-keep-awake';
|
||||
|
||||
import { getCurrentConference } from '../../base/conference/functions';
|
||||
import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
|
||||
@@ -28,8 +28,8 @@ StateListenerRegistry.register(
|
||||
*/
|
||||
function _setWakeLock(wakeLock: boolean) {
|
||||
if (wakeLock) {
|
||||
activateKeepAwake();
|
||||
KeepAwake.activate();
|
||||
} else {
|
||||
deactivateKeepAwake();
|
||||
KeepAwake.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MODERATION_NOTIFICATIONS, MediaType } from '../av-moderation/constants';
|
||||
import { MODERATION_NOTIFICATIONS } from '../av-moderation/constants';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { MediaType } from '../base/media/constants';
|
||||
import { toState } from '../base/redux/functions';
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
requestEnableAudioModeration,
|
||||
requestEnableVideoModeration
|
||||
} from '../../../av-moderation/actions';
|
||||
import { MEDIA_TYPE } from '../../../av-moderation/constants';
|
||||
import {
|
||||
isEnabled as isAvModerationEnabled,
|
||||
isSupported as isAvModerationSupported
|
||||
@@ -21,6 +20,7 @@ import { hideSheet, openDialog } from '../../../base/dialog/actions';
|
||||
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconCheck, IconRaiseHand, IconVideoOff } from '../../../base/icons/svg';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import { raiseHand } from '../../../base/participants/actions';
|
||||
import { getRaiseHandsQueue, isLocalParticipantModerator } from '../../../base/participants/functions';
|
||||
import { LOWER_HAND_MESSAGE } from '../../../base/tracks/constants';
|
||||
|
||||
@@ -2,9 +2,12 @@ import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { getSsrcRewritingFeatureFlag } from '../../../base/config/functions.any';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getMutedStateByParticipantAndMediaType,
|
||||
getParticipantById,
|
||||
getParticipantDisplayName,
|
||||
hasRaisedHand,
|
||||
@@ -166,8 +169,12 @@ function mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { participant } = ownProps;
|
||||
const { ownerId } = state['features/shared-video'];
|
||||
const localParticipantId = getLocalParticipant(state)?.id;
|
||||
const _isAudioMuted = isParticipantAudioMuted(participant, state);
|
||||
const _isVideoMuted = isParticipantVideoMuted(participant, state);
|
||||
const _isAudioMuted = getSsrcRewritingFeatureFlag(state)
|
||||
? Boolean(participant && getMutedStateByParticipantAndMediaType(state, participant, MEDIA_TYPE.AUDIO))
|
||||
: Boolean(participant && isParticipantAudioMuted(participant, state));
|
||||
const _isVideoMuted = getSsrcRewritingFeatureFlag(state)
|
||||
? Boolean(participant && getMutedStateByParticipantAndMediaType(state, participant, MEDIA_TYPE.VIDEO))
|
||||
: isParticipantVideoMuted(participant, state);
|
||||
const audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state);
|
||||
const videoMediaState = getParticipantVideoMediaState(participant, _isVideoMuted, state);
|
||||
const { disableModeratorIndicator } = state['features/base/config'];
|
||||
|
||||
@@ -6,7 +6,6 @@ import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconArrowDown, IconArrowUp } from '../../../base/icons/svg';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
@@ -79,7 +78,6 @@ export default function CurrentVisitorsList({ searchString }: IProps) {
|
||||
const visitors = useSelector(getVisitorsList);
|
||||
const featureEnabled = useSelector(isVisitorsListEnabled);
|
||||
const shouldDisplayList = useSelector(shouldDisplayCurrentVisitorsList);
|
||||
const { defaultRemoteDisplayName } = useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const { t } = useTranslation();
|
||||
const { classes } = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
@@ -111,11 +109,9 @@ export default function CurrentVisitorsList({ searchString }: IProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filtered = visitors.filter(v => {
|
||||
const displayName = v.name || defaultRemoteDisplayName || 'Fellow Jitster';
|
||||
|
||||
return normalizeAccents(displayName).toLowerCase().includes(normalizeAccents(searchString).toLowerCase());
|
||||
});
|
||||
const filtered = visitors.filter(v =>
|
||||
normalizeAccents(v.name).toLowerCase().includes(normalizeAccents(searchString).toLowerCase())
|
||||
);
|
||||
|
||||
// ListItem height is 56px including padding so the item size
|
||||
// for virtualization needs to match it exactly to avoid clipping.
|
||||
@@ -129,7 +125,7 @@ export default function CurrentVisitorsList({ searchString }: IProps) {
|
||||
<ParticipantItem
|
||||
actionsTrigger = { ACTION_TRIGGER.HOVER }
|
||||
audioMediaState = { MEDIA_STATE.NONE }
|
||||
displayName = { v.name || defaultRemoteDisplayName || 'Fellow Jitster' }
|
||||
displayName = { v.name }
|
||||
participantID = { v.id }
|
||||
videoMediaState = { MEDIA_STATE.NONE } />
|
||||
</div>
|
||||
|
||||
@@ -6,13 +6,10 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import {
|
||||
requestDisableAudioModeration,
|
||||
requestDisableDesktopModeration,
|
||||
requestDisableVideoModeration,
|
||||
requestEnableAudioModeration,
|
||||
requestEnableDesktopModeration,
|
||||
requestEnableVideoModeration
|
||||
} from '../../../av-moderation/actions';
|
||||
import { MEDIA_TYPE } from '../../../av-moderation/constants';
|
||||
import {
|
||||
isEnabled as isAvModerationEnabled,
|
||||
isSupported as isAvModerationSupported
|
||||
@@ -20,11 +17,10 @@ import {
|
||||
import { openDialog } from '../../../base/dialog/actions';
|
||||
import {
|
||||
IconCheck,
|
||||
IconCloseLarge,
|
||||
IconDotsHorizontal,
|
||||
IconScreenshare,
|
||||
IconVideoOff
|
||||
} from '../../../base/icons/svg';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import { getRaiseHandsQueue } from '../../../base/participants/functions';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
|
||||
@@ -34,7 +30,6 @@ import { openSettingsDialog } from '../../../settings/actions.web';
|
||||
import { SETTINGS_TABS } from '../../../settings/constants';
|
||||
import { shouldShowModeratorSettings } from '../../../settings/functions.web';
|
||||
import LowerHandButton from '../../../video-menu/components/web/LowerHandButton';
|
||||
import MuteEveryonesDesktopDialog from '../../../video-menu/components/web/MuteEveryonesDesktopDialog';
|
||||
import MuteEveryonesVideoDialog from '../../../video-menu/components/web/MuteEveryonesVideoDialog';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
@@ -91,16 +86,17 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
|
||||
const raisedHandsQueue = useSelector(getRaiseHandsQueue);
|
||||
const isModeratorSettingsTabEnabled = useSelector(shouldShowModeratorSettings);
|
||||
const isAudioModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.AUDIO));
|
||||
const isDesktopModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.DESKTOP));
|
||||
const isVideoModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.VIDEO));
|
||||
const isBreakoutRoom = useSelector(isInBreakoutRoom);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const disableAudioModeration = useCallback(() => dispatch(requestDisableAudioModeration()), [ dispatch ]);
|
||||
const disableDesktopModeration = useCallback(() => dispatch(requestDisableDesktopModeration()), [ dispatch ]);
|
||||
|
||||
const disableVideoModeration = useCallback(() => dispatch(requestDisableVideoModeration()), [ dispatch ]);
|
||||
|
||||
const enableAudioModeration = useCallback(() => dispatch(requestEnableAudioModeration()), [ dispatch ]);
|
||||
const enableDesktopModeration = useCallback(() => dispatch(requestEnableDesktopModeration()), [ dispatch ]);
|
||||
|
||||
const enableVideoModeration = useCallback(() => dispatch(requestEnableVideoModeration()), [ dispatch ]);
|
||||
|
||||
const { classes } = useStyles();
|
||||
@@ -108,9 +104,6 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
|
||||
const muteAllVideo = useCallback(
|
||||
() => dispatch(openDialog(MuteEveryonesVideoDialog)), [ dispatch ]);
|
||||
|
||||
const muteAllDesktop = useCallback(
|
||||
() => dispatch(openDialog(MuteEveryonesDesktopDialog)), [ dispatch ]);
|
||||
|
||||
const openModeratorSettings = () => dispatch(openSettingsDialog(SETTINGS_TABS.MODERATOR));
|
||||
|
||||
const actions = [
|
||||
@@ -120,7 +113,7 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
|
||||
id: isAudioModerationEnabled
|
||||
? 'participants-pane-context-menu-stop-audio-moderation'
|
||||
: 'participants-pane-context-menu-start-audio-moderation',
|
||||
icon: isAudioModerationEnabled ? IconCloseLarge : IconCheck,
|
||||
icon: !isAudioModerationEnabled && IconCheck,
|
||||
onClick: isAudioModerationEnabled ? disableAudioModeration : enableAudioModeration,
|
||||
text: t('participantsPane.actions.audioModeration')
|
||||
}, {
|
||||
@@ -129,18 +122,9 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
|
||||
id: isVideoModerationEnabled
|
||||
? 'participants-pane-context-menu-stop-video-moderation'
|
||||
: 'participants-pane-context-menu-start-video-moderation',
|
||||
icon: isVideoModerationEnabled ? IconCloseLarge : IconCheck,
|
||||
icon: !isVideoModerationEnabled && IconCheck,
|
||||
onClick: isVideoModerationEnabled ? disableVideoModeration : enableVideoModeration,
|
||||
text: t('participantsPane.actions.videoModeration')
|
||||
}, {
|
||||
accessibilityLabel: t('participantsPane.actions.desktopModeration'),
|
||||
className: isDesktopModerationEnabled ? classes.indentedLabel : '',
|
||||
id: isDesktopModerationEnabled
|
||||
? 'participants-pane-context-menu-stop-desktop-moderation'
|
||||
: 'participants-pane-context-menu-start-desktop-moderation',
|
||||
icon: isDesktopModerationEnabled ? IconCloseLarge : IconCheck,
|
||||
onClick: isDesktopModerationEnabled ? disableDesktopModeration : enableDesktopModeration,
|
||||
text: t('participantsPane.actions.desktopModeration')
|
||||
}
|
||||
];
|
||||
|
||||
@@ -153,22 +137,13 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
|
||||
onDrawerClose = { onDrawerClose }
|
||||
onMouseLeave = { onMouseLeave }>
|
||||
<ContextMenuItemGroup
|
||||
actions = { [
|
||||
{
|
||||
accessibilityLabel: t('participantsPane.actions.stopEveryonesVideo'),
|
||||
id: 'participants-pane-context-menu-stop-video',
|
||||
icon: IconVideoOff,
|
||||
onClick: muteAllVideo,
|
||||
text: t('participantsPane.actions.stopEveryonesVideo')
|
||||
},
|
||||
{
|
||||
accessibilityLabel: t('participantsPane.actions.stopEveryonesDesktop'),
|
||||
id: 'participants-pane-context-menu-stop-desktop',
|
||||
icon: IconScreenshare,
|
||||
onClick: muteAllDesktop,
|
||||
text: t('participantsPane.actions.stopEveryonesDesktop')
|
||||
}
|
||||
] } />
|
||||
actions = { [ {
|
||||
accessibilityLabel: t('participantsPane.actions.stopEveryonesVideo'),
|
||||
id: 'participants-pane-context-menu-stop-video',
|
||||
icon: IconVideoOff,
|
||||
onClick: muteAllVideo,
|
||||
text: t('participantsPane.actions.stopEveryonesVideo')
|
||||
} ] } />
|
||||
{raisedHandsQueue.length !== 0 && <LowerHandButton />}
|
||||
{!isBreakoutRoom && isModerationSupported && (
|
||||
<ContextMenuItemGroup actions = { actions }>
|
||||
|
||||
@@ -2,10 +2,12 @@ import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { getSsrcRewritingFeatureFlag } from '../../../base/config/functions.any';
|
||||
import { JitsiTrackEvents } from '../../../base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getMutedStateByParticipantAndMediaType,
|
||||
getParticipantByIdOrUndefined,
|
||||
getParticipantDisplayName,
|
||||
hasRaisedHand,
|
||||
@@ -121,6 +123,11 @@ interface IProps {
|
||||
*/
|
||||
isInBreakoutRoom: boolean;
|
||||
|
||||
/**
|
||||
* Callback used to open a confirmation dialog for audio muting.
|
||||
*/
|
||||
muteAudio: Function;
|
||||
|
||||
/**
|
||||
* The translated text for the mute participant button.
|
||||
*/
|
||||
@@ -146,6 +153,7 @@ interface IProps {
|
||||
*/
|
||||
overflowDrawer: boolean;
|
||||
|
||||
|
||||
/**
|
||||
* The aria-label for the ellipsis action.
|
||||
*/
|
||||
@@ -156,6 +164,11 @@ interface IProps {
|
||||
*/
|
||||
participantID?: string;
|
||||
|
||||
/**
|
||||
* Callback used to stop a participant's video.
|
||||
*/
|
||||
stopVideo: Function;
|
||||
|
||||
/**
|
||||
* The translated "you" text.
|
||||
*/
|
||||
@@ -183,11 +196,13 @@ function MeetingParticipantItem({
|
||||
_videoMediaState,
|
||||
isHighlighted,
|
||||
isInBreakoutRoom,
|
||||
muteAudio,
|
||||
onContextMenu,
|
||||
onLeave,
|
||||
openDrawerForParticipant,
|
||||
overflowDrawer,
|
||||
participantActionEllipsisLabel,
|
||||
stopVideo,
|
||||
youText
|
||||
}: IProps) {
|
||||
|
||||
@@ -253,8 +268,10 @@ function MeetingParticipantItem({
|
||||
{!isInBreakoutRoom && (
|
||||
<ParticipantQuickAction
|
||||
buttonType = { _quickActionButtonType }
|
||||
muteAudio = { muteAudio }
|
||||
participantID = { _participantID }
|
||||
participantName = { _displayName } />
|
||||
participantName = { _displayName }
|
||||
stopVideo = { stopVideo } />
|
||||
)}
|
||||
<ParticipantActionEllipsis
|
||||
accessibilityLabel = { participantActionEllipsisLabel }
|
||||
@@ -287,11 +304,15 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const participant = getParticipantByIdOrUndefined(state, participantID);
|
||||
const _displayName = getParticipantDisplayName(state, participant?.id ?? '');
|
||||
const _matchesSearch = participantMatchesSearch(participant, searchString);
|
||||
const _isAudioMuted = isParticipantAudioMuted(participant, state);
|
||||
const _isVideoMuted = isParticipantVideoMuted(participant, state);
|
||||
const _isAudioMuted = getSsrcRewritingFeatureFlag(state)
|
||||
? Boolean(participant && getMutedStateByParticipantAndMediaType(state, participant, MEDIA_TYPE.AUDIO))
|
||||
: Boolean(participant && isParticipantAudioMuted(participant, state));
|
||||
const _isVideoMuted = getSsrcRewritingFeatureFlag(state)
|
||||
? Boolean(participant && getMutedStateByParticipantAndMediaType(state, participant, MEDIA_TYPE.VIDEO))
|
||||
: isParticipantVideoMuted(participant, state);
|
||||
const _audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state);
|
||||
const _videoMediaState = getParticipantVideoMediaState(participant, _isVideoMuted, state);
|
||||
const _quickActionButtonType = getQuickActionButtonType(participant, state);
|
||||
const _quickActionButtonType = getQuickActionButtonType(participant, _isAudioMuted, _isVideoMuted, state);
|
||||
|
||||
const tracks = state['features/base/tracks'];
|
||||
const _audioTrack = participantID === localParticipantId
|
||||
|
||||
@@ -19,6 +19,11 @@ interface IProps {
|
||||
*/
|
||||
lowerMenu: Function;
|
||||
|
||||
/**
|
||||
* Callback used to open a confirmation dialog for audio muting.
|
||||
*/
|
||||
muteAudio: Function;
|
||||
|
||||
/**
|
||||
* The translated text for the mute participant button.
|
||||
*/
|
||||
@@ -54,6 +59,11 @@ interface IProps {
|
||||
*/
|
||||
searchString?: string;
|
||||
|
||||
/**
|
||||
* Callback used to stop a participant's video.
|
||||
*/
|
||||
stopVideo: Function;
|
||||
|
||||
/**
|
||||
* Callback for the activation of this item's context menu.
|
||||
*/
|
||||
@@ -74,12 +84,14 @@ function MeetingParticipantItems({
|
||||
isInBreakoutRoom,
|
||||
lowerMenu,
|
||||
toggleMenu,
|
||||
muteAudio,
|
||||
participantIds,
|
||||
openDrawerForParticipant,
|
||||
overflowDrawer,
|
||||
raiseContextId,
|
||||
participantActionEllipsisLabel,
|
||||
searchString,
|
||||
stopVideo,
|
||||
youText
|
||||
}: IProps) {
|
||||
const renderParticipant = (id: string) => (
|
||||
@@ -87,6 +99,7 @@ function MeetingParticipantItems({
|
||||
isHighlighted = { raiseContextId === id }
|
||||
isInBreakoutRoom = { isInBreakoutRoom }
|
||||
key = { id }
|
||||
muteAudio = { muteAudio }
|
||||
onContextMenu = { toggleMenu(id) }
|
||||
onLeave = { lowerMenu }
|
||||
openDrawerForParticipant = { openDrawerForParticipant }
|
||||
@@ -94,6 +107,7 @@ function MeetingParticipantItems({
|
||||
participantActionEllipsisLabel = { participantActionEllipsisLabel }
|
||||
participantID = { id }
|
||||
searchString = { searchString }
|
||||
stopVideo = { stopVideo }
|
||||
youText = { youText } />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { rejectParticipantAudio, rejectParticipantVideo } from '../../../av-moderation/actions';
|
||||
import participantsPaneTheme from '../../../base/components/themes/participantsPaneTheme.json';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import { getParticipantById, isScreenShareParticipant } from '../../../base/participants/functions';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import Input from '../../../base/ui/components/web/Input';
|
||||
@@ -12,6 +14,7 @@ import useContextMenu from '../../../base/ui/hooks/useContextMenu.web';
|
||||
import { normalizeAccents } from '../../../base/util/strings.web';
|
||||
import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
import { isButtonEnabled, showOverflowDrawer } from '../../../toolbox/functions.web';
|
||||
import { muteRemote } from '../../../video-menu/actions.web';
|
||||
import { iAmVisitor } from '../../../visitors/functions';
|
||||
import { getSortedParticipantIds, isCurrentRoomRenamable, shouldRenderInviteButton } from '../../functions';
|
||||
import { useParticipantDrawer } from '../../hooks';
|
||||
@@ -79,9 +82,18 @@ function MeetingParticipants({
|
||||
showInviteButton,
|
||||
sortedParticipantIds = []
|
||||
}: IProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [ lowerMenu, , toggleMenu, menuEnter, menuLeave, raiseContext ] = useContextMenu<string>();
|
||||
const muteAudio = useCallback(id => () => {
|
||||
dispatch(muteRemote(id, MEDIA_TYPE.AUDIO));
|
||||
dispatch(rejectParticipantAudio(id));
|
||||
}, [ dispatch ]);
|
||||
const stopVideo = useCallback(id => () => {
|
||||
dispatch(muteRemote(id, MEDIA_TYPE.VIDEO));
|
||||
dispatch(rejectParticipantVideo(id));
|
||||
}, [ dispatch ]);
|
||||
const [ drawerParticipant, closeDrawer, openDrawerForParticipant ] = useParticipantDrawer();
|
||||
|
||||
// FIXME:
|
||||
@@ -128,18 +140,21 @@ function MeetingParticipants({
|
||||
<MeetingParticipantItems
|
||||
isInBreakoutRoom = { isBreakoutRoom }
|
||||
lowerMenu = { lowerMenu }
|
||||
muteAudio = { muteAudio }
|
||||
openDrawerForParticipant = { openDrawerForParticipant }
|
||||
overflowDrawer = { overflowDrawer }
|
||||
participantActionEllipsisLabel = { participantActionEllipsisLabel }
|
||||
participantIds = { sortedParticipantIds }
|
||||
raiseContextId = { raiseContext.entity }
|
||||
searchString = { normalizeAccents(searchString) }
|
||||
stopVideo = { stopVideo }
|
||||
toggleMenu = { toggleMenu }
|
||||
youText = { youText } />
|
||||
</div>
|
||||
<MeetingParticipantContextMenu
|
||||
closeDrawer = { closeDrawer }
|
||||
drawerParticipant = { drawerParticipant }
|
||||
muteAudio = { muteAudio }
|
||||
offsetTarget = { raiseContext?.offsetTarget }
|
||||
onEnter = { menuEnter }
|
||||
onLeave = { menuLeave }
|
||||
|
||||
@@ -3,17 +3,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import {
|
||||
approveParticipantAudio,
|
||||
approveParticipantDesktop,
|
||||
approveParticipantVideo,
|
||||
rejectParticipantAudio,
|
||||
rejectParticipantDesktop,
|
||||
rejectParticipantVideo
|
||||
} from '../../../av-moderation/actions';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import { approveParticipantAudio, approveParticipantVideo } from '../../../av-moderation/actions';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import { muteRemote } from '../../../video-menu/actions.web';
|
||||
import { QUICK_ACTION_BUTTON } from '../../constants';
|
||||
|
||||
interface IProps {
|
||||
@@ -33,6 +24,11 @@ interface IProps {
|
||||
*/
|
||||
buttonType: string;
|
||||
|
||||
/**
|
||||
* Callback used to open a confirmation dialog for audio muting.
|
||||
*/
|
||||
muteAudio: Function;
|
||||
|
||||
/**
|
||||
* Label for mute participant button.
|
||||
*/
|
||||
@@ -48,6 +44,11 @@ interface IProps {
|
||||
*/
|
||||
participantName: string;
|
||||
|
||||
/**
|
||||
* Callback used to stop a participant's video.
|
||||
*/
|
||||
stopVideo: Function;
|
||||
|
||||
}
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
@@ -60,8 +61,10 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
const ParticipantQuickAction = ({
|
||||
buttonType,
|
||||
muteAudio,
|
||||
participantID,
|
||||
participantName
|
||||
participantName,
|
||||
stopVideo
|
||||
}: IProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
@@ -71,29 +74,10 @@ const ParticipantQuickAction = ({
|
||||
dispatch(approveParticipantAudio(participantID));
|
||||
}, [ dispatch, participantID ]);
|
||||
|
||||
const allowDesktop = useCallback(() => {
|
||||
dispatch(approveParticipantDesktop(participantID));
|
||||
}, [ dispatch, participantID ]);
|
||||
|
||||
const allowVideo = useCallback(() => {
|
||||
dispatch(approveParticipantVideo(participantID));
|
||||
}, [ dispatch, participantID ]);
|
||||
|
||||
const muteAudio = useCallback(() => {
|
||||
dispatch(muteRemote(participantID, MEDIA_TYPE.AUDIO));
|
||||
dispatch(rejectParticipantAudio(participantID));
|
||||
}, [ dispatch, participantID ]);
|
||||
|
||||
const stopDesktop = useCallback(() => {
|
||||
dispatch(muteRemote(participantID, MEDIA_TYPE.SCREENSHARE));
|
||||
dispatch(rejectParticipantDesktop(participantID));
|
||||
}, [ dispatch, participantID ]);
|
||||
|
||||
const stopVideo = useCallback(() => {
|
||||
dispatch(muteRemote(participantID, MEDIA_TYPE.VIDEO));
|
||||
dispatch(rejectParticipantVideo(participantID));
|
||||
}, [ dispatch, participantID ]);
|
||||
|
||||
switch (buttonType) {
|
||||
case QUICK_ACTION_BUTTON.MUTE: {
|
||||
return (
|
||||
@@ -101,7 +85,7 @@ const ParticipantQuickAction = ({
|
||||
accessibilityLabel = { `${t('participantsPane.actions.mute')} ${participantName}` }
|
||||
className = { styles.button }
|
||||
label = { t('participantsPane.actions.mute') }
|
||||
onClick = { muteAudio }
|
||||
onClick = { muteAudio(participantID) }
|
||||
size = 'small'
|
||||
testId = { `mute-audio-${participantID}` } />
|
||||
);
|
||||
@@ -117,17 +101,6 @@ const ParticipantQuickAction = ({
|
||||
testId = { `unmute-audio-${participantID}` } />
|
||||
);
|
||||
}
|
||||
case QUICK_ACTION_BUTTON.ALLOW_DESKTOP: {
|
||||
return (
|
||||
<Button
|
||||
accessibilityLabel = { `${t('participantsPane.actions.askDesktop')} ${participantName}` }
|
||||
className = { styles.button }
|
||||
label = { t('participantsPane.actions.allowDesktop') }
|
||||
onClick = { allowDesktop }
|
||||
size = 'small'
|
||||
testId = { `unmute-desktop-${participantID}` } />
|
||||
);
|
||||
}
|
||||
case QUICK_ACTION_BUTTON.ALLOW_VIDEO: {
|
||||
return (
|
||||
<Button
|
||||
@@ -139,24 +112,13 @@ const ParticipantQuickAction = ({
|
||||
testId = { `unmute-video-${participantID}` } />
|
||||
);
|
||||
}
|
||||
case QUICK_ACTION_BUTTON.STOP_DESKTOP: {
|
||||
return (
|
||||
<Button
|
||||
accessibilityLabel = { `${t('participantsPane.actions.stopDesktop')} ${participantName}` }
|
||||
className = { styles.button }
|
||||
label = { t('participantsPane.actions.stopDesktop') }
|
||||
onClick = { stopDesktop }
|
||||
size = 'small'
|
||||
testId = { `mute-desktop-${participantID}` } />
|
||||
);
|
||||
}
|
||||
case QUICK_ACTION_BUTTON.STOP_VIDEO: {
|
||||
return (
|
||||
<Button
|
||||
accessibilityLabel = { `${t('participantsPane.actions.mute')} ${participantName}` }
|
||||
className = { styles.button }
|
||||
label = { t('participantsPane.actions.stopVideo') }
|
||||
onClick = { stopVideo }
|
||||
onClick = { stopVideo(participantID) }
|
||||
size = 'small'
|
||||
testId = { `mute-video-${participantID}` } />
|
||||
);
|
||||
|
||||
@@ -45,6 +45,7 @@ const useStyles = makeStyles<IStylesProps>()((theme, { isChatOpen }) => {
|
||||
participantsPane: {
|
||||
backgroundColor: theme.palette.ui01,
|
||||
flexShrink: 0,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
transition: 'width .16s ease-in-out',
|
||||
width: '315px',
|
||||
@@ -71,6 +72,7 @@ const useStyles = makeStyles<IStylesProps>()((theme, { isChatOpen }) => {
|
||||
container: {
|
||||
boxSizing: 'border-box',
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
position: 'relative',
|
||||
padding: `0 ${participantsPaneTheme.panePadding}px`,
|
||||
display: 'flex',
|
||||
|
||||
@@ -36,27 +36,22 @@ export const MEDIA_STATE: { [key: string]: MediaState; } = {
|
||||
NONE: 'None'
|
||||
};
|
||||
|
||||
export type QuickActionButtonType
|
||||
= 'Mute' | 'AskToUnmute' | 'AllowVideo' | 'StopVideo' | 'AllowDesktop' | 'StopDesktop' | 'None';
|
||||
export type QuickActionButtonType = 'Mute' | 'AskToUnmute' | 'AllowVideo' | 'StopVideo' | 'None';
|
||||
|
||||
/**
|
||||
* Enum of possible participant mute button states.
|
||||
*/
|
||||
export const QUICK_ACTION_BUTTON: {
|
||||
ALLOW_DESKTOP: QuickActionButtonType;
|
||||
ALLOW_VIDEO: QuickActionButtonType;
|
||||
ASK_TO_UNMUTE: QuickActionButtonType;
|
||||
MUTE: QuickActionButtonType;
|
||||
NONE: QuickActionButtonType;
|
||||
STOP_DESKTOP: QuickActionButtonType;
|
||||
STOP_VIDEO: QuickActionButtonType;
|
||||
} = {
|
||||
ALLOW_DESKTOP: 'AllowDesktop',
|
||||
ALLOW_VIDEO: 'AllowVideo',
|
||||
MUTE: 'Mute',
|
||||
ASK_TO_UNMUTE: 'AskToUnmute',
|
||||
NONE: 'None',
|
||||
STOP_DESKTOP: 'StopDesktop',
|
||||
STOP_VIDEO: 'StopVideo'
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { MEDIA_TYPE as AVM_MEDIA_TYPE } from '../av-moderation/constants';
|
||||
import {
|
||||
isForceMuted,
|
||||
isEnabledFromState,
|
||||
isLocalParticipantApprovedFromState,
|
||||
isParticipantApproved,
|
||||
isSupported
|
||||
} from '../av-moderation/functions';
|
||||
import { IStateful } from '../base/app/types';
|
||||
@@ -9,26 +10,48 @@ import theme from '../base/components/themes/participantsPaneTheme.json';
|
||||
import { getCurrentConference } from '../base/conference/functions';
|
||||
import { INVITE_ENABLED, PARTICIPANTS_ENABLED } from '../base/flags/constants';
|
||||
import { getFeatureFlag } from '../base/flags/functions';
|
||||
import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
|
||||
import {
|
||||
getDominantSpeakerParticipant,
|
||||
getLocalParticipant,
|
||||
getRaiseHandsQueue,
|
||||
getRemoteParticipantsSorted,
|
||||
isLocalParticipantModerator
|
||||
isLocalParticipantModerator,
|
||||
isParticipantModerator
|
||||
} from '../base/participants/functions';
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import {
|
||||
isParticipantAudioMuted,
|
||||
isParticipantScreenShareMuted,
|
||||
isParticipantVideoMuted
|
||||
} from '../base/tracks/functions.any';
|
||||
import { normalizeAccents } from '../base/util/strings';
|
||||
import { BREAKOUT_ROOMS_RENAME_FEATURE } from '../breakout-rooms/constants';
|
||||
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
|
||||
import { MEDIA_STATE, QUICK_ACTION_BUTTON, REDUCER_KEY } from './constants';
|
||||
|
||||
/**
|
||||
* Checks if a participant is force muted.
|
||||
*
|
||||
* @param {IParticipant|undefined} participant - The participant.
|
||||
* @param {MediaType} mediaType - The media type.
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {MediaState}
|
||||
*/
|
||||
export function isForceMuted(participant: IParticipant | undefined, mediaType: MediaType, state: IReduxState) {
|
||||
if (isEnabledFromState(mediaType, state)) {
|
||||
if (participant?.local) {
|
||||
return !isLocalParticipantApprovedFromState(mediaType, state);
|
||||
}
|
||||
|
||||
// moderators cannot be force muted
|
||||
if (isParticipantModerator(participant)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isParticipantApproved(participant?.id ?? '', mediaType)(state);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the audio media state (the mic icon) for a participant.
|
||||
*
|
||||
@@ -46,7 +69,7 @@ export function getParticipantAudioMediaState(participant: IParticipant | undefi
|
||||
}
|
||||
|
||||
if (muted) {
|
||||
if (isForceMuted(participant, AVM_MEDIA_TYPE.AUDIO, state)) {
|
||||
if (isForceMuted(participant, MEDIA_TYPE.AUDIO, state)) {
|
||||
return MEDIA_STATE.FORCE_MUTED;
|
||||
}
|
||||
|
||||
@@ -71,7 +94,7 @@ export function getParticipantAudioMediaState(participant: IParticipant | undefi
|
||||
export function getParticipantVideoMediaState(participant: IParticipant | undefined,
|
||||
muted: Boolean, state: IReduxState) {
|
||||
if (muted) {
|
||||
if (isForceMuted(participant, AVM_MEDIA_TYPE.VIDEO, state)) {
|
||||
if (isForceMuted(participant, MEDIA_TYPE.VIDEO, state)) {
|
||||
return MEDIA_STATE.FORCE_MUTED;
|
||||
}
|
||||
|
||||
@@ -116,44 +139,33 @@ export const getParticipantsPaneOpen = (state: IReduxState) => Boolean(getState(
|
||||
* The button is displayed when hovering a participant from the participant list.
|
||||
*
|
||||
* @param {IParticipant} participant - The participant.
|
||||
* @param {boolean} isAudioMuted - If audio is muted for the participant.
|
||||
* @param {boolean} isVideoMuted - If audio is muted for the participant.
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {string} - The type of the quick action button.
|
||||
*/
|
||||
export function getQuickActionButtonType(participant: IParticipant | undefined, state: IReduxState) {
|
||||
if (!isLocalParticipantModerator(state)) {
|
||||
return QUICK_ACTION_BUTTON.NONE;
|
||||
}
|
||||
export function getQuickActionButtonType(
|
||||
participant: IParticipant | undefined,
|
||||
isAudioMuted: Boolean,
|
||||
isVideoMuted: Boolean,
|
||||
state: IReduxState) {
|
||||
// handled only by moderators
|
||||
const isVideoForceMuted = isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
|
||||
const isParticipantSilent = participant?.isSilent || false;
|
||||
|
||||
// Handled only by moderators.
|
||||
const isAudioMuted = isParticipantAudioMuted(participant, state);
|
||||
const isScreenShareMuted = isParticipantScreenShareMuted(participant, state);
|
||||
const isVideoMuted = isParticipantVideoMuted(participant, state);
|
||||
const isDesktopForceMuted = isForceMuted(participant, AVM_MEDIA_TYPE.DESKTOP, state);
|
||||
const isVideoForceMuted = isForceMuted(participant, AVM_MEDIA_TYPE.VIDEO, state);
|
||||
const isParticipantSilent = participant?.isSilent ?? false;
|
||||
|
||||
if (!isAudioMuted && !isParticipantSilent) {
|
||||
return QUICK_ACTION_BUTTON.MUTE;
|
||||
}
|
||||
|
||||
if (!isVideoMuted) {
|
||||
return QUICK_ACTION_BUTTON.STOP_VIDEO;
|
||||
}
|
||||
|
||||
if (!isScreenShareMuted) {
|
||||
return QUICK_ACTION_BUTTON.STOP_DESKTOP;
|
||||
}
|
||||
|
||||
if (isSupported()(state) && !isParticipantSilent) {
|
||||
return QUICK_ACTION_BUTTON.ASK_TO_UNMUTE;
|
||||
}
|
||||
|
||||
if (isVideoForceMuted) {
|
||||
return QUICK_ACTION_BUTTON.ALLOW_VIDEO;
|
||||
}
|
||||
|
||||
if (isDesktopForceMuted) {
|
||||
return QUICK_ACTION_BUTTON.ALLOW_DESKTOP;
|
||||
if (isLocalParticipantModerator(state)) {
|
||||
if (!isAudioMuted && !isParticipantSilent) {
|
||||
return QUICK_ACTION_BUTTON.MUTE;
|
||||
}
|
||||
if (!isVideoMuted) {
|
||||
return QUICK_ACTION_BUTTON.STOP_VIDEO;
|
||||
}
|
||||
if (isSupported()(state) && !isParticipantSilent) {
|
||||
return QUICK_ACTION_BUTTON.ASK_TO_UNMUTE;
|
||||
}
|
||||
if (isVideoForceMuted) {
|
||||
return QUICK_ACTION_BUTTON.ALLOW_VIDEO;
|
||||
}
|
||||
}
|
||||
|
||||
return QUICK_ACTION_BUTTON.NONE;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/polls');
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
COMMAND_NEW_POLL,
|
||||
COMMAND_OLD_POLLS
|
||||
} from './constants';
|
||||
import logger from './logger';
|
||||
import { IAnswer, IPoll, IPollData } from './types';
|
||||
|
||||
/**
|
||||
@@ -44,16 +43,7 @@ const parsePollData = (pollData: Partial<IPollData>): IPoll | null => {
|
||||
const { id, senderId, question, answers } = pollData;
|
||||
|
||||
if (typeof id !== 'string' || typeof senderId !== 'string'
|
||||
|| typeof question !== 'string' || !(answers instanceof Array)) {
|
||||
logger.error('Malformed poll data received:', pollData);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate answers.
|
||||
if (answers.some(answer => typeof answer !== 'string')) {
|
||||
logger.error('Malformed answers data received:', answers);
|
||||
|
||||
|| typeof question !== 'string' || !(answers instanceof Array)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -183,7 +173,7 @@ function _handleReceivePollsMessage(data: any, dispatch: IStore['dispatch'], get
|
||||
const receivedAnswer: IAnswer = {
|
||||
voterId,
|
||||
pollId,
|
||||
answers: answers.slice(0, MAX_ANSWERS).map(Boolean)
|
||||
answers: answers.slice(0, MAX_ANSWERS)
|
||||
};
|
||||
|
||||
dispatch(receiveAnswer(pollId, receivedAnswer));
|
||||
@@ -198,7 +188,7 @@ function _handleReceivePollsMessage(data: any, dispatch: IStore['dispatch'], get
|
||||
const poll = parsePollData(pollData);
|
||||
|
||||
if (poll === null) {
|
||||
logger.warn('Malformed old poll data', pollData);
|
||||
console.warn('[features/polls] Invalid old poll data');
|
||||
} else {
|
||||
dispatch(receivePoll(pollData.id, poll, false));
|
||||
}
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
import { Theme } from '@mui/material';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { hideDialog } from '../base/dialog/actions';
|
||||
import { translate } from '../base/i18n/functions';
|
||||
import Label from '../base/label/components/web/Label';
|
||||
import { CAMERA_FACING_MODE } from '../base/media/constants';
|
||||
import Button from '../base/ui/components/web/Button';
|
||||
import Dialog from '../base/ui/components/web/Dialog';
|
||||
import { BUTTON_TYPES } from '../base/ui/constants.any';
|
||||
|
||||
import { ICameraCapturePayload } from './actionTypes';
|
||||
|
||||
const useStyles = makeStyles()((theme: Theme) => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(3),
|
||||
textAlign: 'center'
|
||||
},
|
||||
buttonsContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(3),
|
||||
width: '100%',
|
||||
maxWidth: '100%'
|
||||
},
|
||||
|
||||
hidden: {
|
||||
display: 'none'
|
||||
},
|
||||
label: {
|
||||
background: 'transparent',
|
||||
margin: `${theme.spacing(3)} 0 ${theme.spacing(6)}`,
|
||||
},
|
||||
button: {
|
||||
width: '100%',
|
||||
height: '48px',
|
||||
maxWidth: '400px'
|
||||
}
|
||||
}));
|
||||
|
||||
/**
|
||||
* The type of {@link CameraCaptureDialog}'s React {@code Component} props.
|
||||
*/
|
||||
interface IProps extends WithTranslation {
|
||||
/**
|
||||
* Callback function on file input changed.
|
||||
*/
|
||||
callback: ({ error, dataURL }: { dataURL?: string; error?: string; }) => void;
|
||||
|
||||
/**
|
||||
* The camera capture payload.
|
||||
*/
|
||||
componentProps: ICameraCapturePayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the Camera capture dialog.
|
||||
*
|
||||
* @param {Object} props - The props of the component.
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
const CameraCaptureDialog = ({
|
||||
callback,
|
||||
componentProps,
|
||||
t,
|
||||
}: IProps) => {
|
||||
const { cameraFacingMode = CAMERA_FACING_MODE.ENVIRONMENT,
|
||||
descriptionText,
|
||||
titleText } = componentProps;
|
||||
const dispatch = useDispatch();
|
||||
const { classes } = useStyles();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const onCancel = useCallback(() => {
|
||||
callback({
|
||||
error: 'User canceled!'
|
||||
});
|
||||
dispatch(hideDialog());
|
||||
}, []);
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
inputRef.current?.click();
|
||||
}, []);
|
||||
|
||||
const onInputChange = useCallback(event => {
|
||||
const reader = new FileReader();
|
||||
const files = event.target.files;
|
||||
|
||||
if (!files?.[0]) {
|
||||
callback({
|
||||
error: 'No picture selected!'
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
reader.onload = () => {
|
||||
callback({ dataURL: reader.result as string });
|
||||
dispatch(hideDialog());
|
||||
};
|
||||
reader.onerror = () => {
|
||||
callback({ error: 'Failed generating base64 URL!' });
|
||||
dispatch(hideDialog());
|
||||
};
|
||||
|
||||
reader.readAsDataURL(files[0]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
cancel = {{ hidden: true }}
|
||||
disableAutoHideOnSubmit = { true }
|
||||
ok = {{ hidden: true }}
|
||||
onCancel = { onCancel }
|
||||
titleKey = { titleText || t('dialog.cameraCaptureDialog.title') }>
|
||||
<div className = { classes.container }>
|
||||
<Label
|
||||
aria-label = { descriptionText || t('dialog.cameraCaptureDialog.description') }
|
||||
className = { classes.label }
|
||||
text = { descriptionText || t('dialog.cameraCaptureDialog.description') } />
|
||||
<div className = { classes.buttonsContainer } >
|
||||
<Button
|
||||
accessibilityLabel = { t('dialog.cameraCaptureDialog.ok') }
|
||||
className = { classes.button }
|
||||
labelKey = { 'dialog.cameraCaptureDialog.ok' }
|
||||
onClick = { onSubmit } />
|
||||
<Button
|
||||
accessibilityLabel = { t('dialog.cameraCaptureDialog.reject') }
|
||||
className = { classes.button }
|
||||
labelKey = { 'dialog.cameraCaptureDialog.reject' }
|
||||
onClick = { onCancel }
|
||||
type = { BUTTON_TYPES.TERTIARY } />
|
||||
<input
|
||||
accept = 'image/*'
|
||||
capture = { cameraFacingMode }
|
||||
className = { classes.hidden }
|
||||
onChange = { onInputChange }
|
||||
ref = { inputRef }
|
||||
type = 'file' />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default translate(connect()(CameraCaptureDialog));
|
||||
@@ -7,23 +7,3 @@
|
||||
*/
|
||||
|
||||
export const SET_SCREENSHOT_CAPTURE = 'SET_SCREENSHOT_CAPTURE';
|
||||
|
||||
/**
|
||||
* The camera capture payload.
|
||||
*/
|
||||
export interface ICameraCapturePayload {
|
||||
/**
|
||||
* Selected camera on open.
|
||||
*/
|
||||
cameraFacingMode?: string;
|
||||
|
||||
/**
|
||||
* Custom explanatory text to show.
|
||||
*/
|
||||
descriptionText?: string,
|
||||
|
||||
/**
|
||||
* Custom dialog title text.
|
||||
*/
|
||||
titleText?: string
|
||||
};
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import { isMobileBrowser } from '../base/environment/utils';
|
||||
import { getLocalJitsiDesktopTrack } from '../base/tracks/functions';
|
||||
|
||||
import CameraCaptureDialog from './CameraCaptureDialog';
|
||||
import { ICameraCapturePayload, SET_SCREENSHOT_CAPTURE } from './actionTypes';
|
||||
import { SET_SCREENSHOT_CAPTURE } from './actionTypes';
|
||||
import { createScreenshotCaptureSummary } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
@@ -64,23 +61,3 @@ export function toggleScreenshotCaptureSummary(enabled: boolean) {
|
||||
return Promise.resolve();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@code CameraCaptureDialog}.
|
||||
*
|
||||
* @param {Function} callback - The callback to execute on picture taken.
|
||||
* @param {ICameraCapturePayload} componentProps - The camera capture payload.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function openCameraCaptureDialog(callback: Function, componentProps: ICameraCapturePayload) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
if (!isMobileBrowser()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(openDialog(CameraCaptureDialog, {
|
||||
callback,
|
||||
componentProps
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { MEDIA_TYPE } from '../av-moderation/constants';
|
||||
import { isEnabledFromState } from '../av-moderation/functions';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { isNameReadOnly } from '../base/config/functions.any';
|
||||
import { SERVER_URL_CHANGE_ENABLED } from '../base/flags/constants';
|
||||
import { getFeatureFlag } from '../base/flags/functions';
|
||||
import i18next, { DEFAULT_LANGUAGE, LANGUAGES } from '../base/i18n/i18next';
|
||||
import { MEDIA_TYPE } from '../base/media/constants';
|
||||
import { getLocalParticipant } from '../base/participants/functions';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { getHideSelfView } from '../base/settings/functions.any';
|
||||
@@ -91,23 +91,6 @@ export function getNotificationsMap(stateful: IStateful): { [key: string]: boole
|
||||
}, {});
|
||||
}
|
||||
|
||||
function normalizeCurrentLanguage(language: string) {
|
||||
if (!language) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [ country, lang ] = language.split('-');
|
||||
const jitsiNormalized = `${country}${lang ?? ''}`;
|
||||
|
||||
if (LANGUAGES.includes(jitsiNormalized)) {
|
||||
return jitsiNormalized;
|
||||
}
|
||||
|
||||
if (LANGUAGES.includes(country)) {
|
||||
return country;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the properties for the "More" tab from settings dialog from Redux
|
||||
* state.
|
||||
@@ -119,7 +102,7 @@ function normalizeCurrentLanguage(language: string) {
|
||||
export function getMoreTabProps(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const stageFilmstripEnabled = isStageFilmstripEnabled(state);
|
||||
const language = normalizeCurrentLanguage(i18next.language) || DEFAULT_LANGUAGE;
|
||||
const language = i18next.language || DEFAULT_LANGUAGE;
|
||||
const configuredTabs: string[] = interfaceConfig.SETTINGS_SECTIONS || [];
|
||||
|
||||
// when self view is controlled by the config we hide the settings
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { MEDIA_TYPE } from '../av-moderation/constants';
|
||||
import { isForceMuted } from '../av-moderation/functions';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
|
||||
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { setAudioMuted } from '../base/media/actions';
|
||||
import { MEDIA_TYPE } from '../base/media/constants';
|
||||
import { raiseHand } from '../base/participants/actions';
|
||||
import { getLocalParticipant } from '../base/participants/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
|
||||
import { hideNotification, showNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
import { isForceMuted } from '../participants-pane/functions';
|
||||
import { isAudioMuteButtonDisabled } from '../toolbox/functions.any';
|
||||
|
||||
import { setCurrentNotificationUid } from './actions';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Image, View, ViewStyle } from 'react-native';
|
||||
import { SvgCssUri } from 'react-native-svg/css';
|
||||
import { SvgCssUri } from 'react-native-svg';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
@@ -44,7 +44,6 @@ class CustomOptionButton extends AbstractButton<ICustomOptionButton> {
|
||||
if (this.iconSrc?.includes('svg')) {
|
||||
iconComponent = (
|
||||
<SvgCssUri
|
||||
// @ts-ignore
|
||||
height = { BaseTheme.spacing[4] }
|
||||
uri = { this.iconSrc }
|
||||
width = { BaseTheme.spacing[4] } />
|
||||
@@ -63,7 +62,7 @@ class CustomOptionButton extends AbstractButton<ICustomOptionButton> {
|
||||
<View
|
||||
style = { this.props.isToolboxButton && [
|
||||
styles.toolboxButtonIconContainer,
|
||||
{ backgroundColor: this.backgroundColor } ] as ViewStyle[] }>
|
||||
{ backgroundColor: this.backgroundColor } ] as ViewStyle }>
|
||||
{ iconComponent }
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
import {
|
||||
AUDIO_MUTE,
|
||||
DESKTOP_MUTE,
|
||||
VIDEO_MUTE,
|
||||
createRemoteMuteConfirmedEvent,
|
||||
createToolbarEvent
|
||||
} from '../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../analytics/functions';
|
||||
import { IStore } from '../app/types';
|
||||
import {
|
||||
rejectParticipantAudio,
|
||||
rejectParticipantDesktop,
|
||||
rejectParticipantVideo
|
||||
} from '../av-moderation/actions';
|
||||
import { setAudioMuted, setScreenshareMuted, setVideoMuted } from '../base/media/actions';
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
MediaType,
|
||||
SCREENSHARE_MUTISM_AUTHORITY,
|
||||
VIDEO_MUTISM_AUTHORITY
|
||||
} from '../base/media/constants';
|
||||
import { rejectParticipantAudio, rejectParticipantVideo, showModeratedNotification } from '../av-moderation/actions';
|
||||
import { shouldShowModeratedNotification } from '../av-moderation/functions';
|
||||
import { setAudioMuted, setVideoMuted } from '../base/media/actions';
|
||||
import { MEDIA_TYPE, MediaType, VIDEO_MUTISM_AUTHORITY } from '../base/media/constants';
|
||||
import { muteRemoteParticipant } from '../base/participants/actions';
|
||||
import { getRemoteParticipants } from '../base/participants/functions';
|
||||
import { toggleScreensharing } from '../base/tracks/actions';
|
||||
import { isModerationNotificationDisplayed } from '../notifications/functions';
|
||||
|
||||
import logger from './logger';
|
||||
|
||||
@@ -29,31 +22,39 @@ import logger from './logger';
|
||||
*
|
||||
* @param {boolean} enable - Whether to mute or unmute.
|
||||
* @param {MEDIA_TYPE} mediaType - The type of the media channel to mute.
|
||||
* @param {boolean} stopScreenSharing - Whether or not to stop the screensharing.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function muteLocal(enable: boolean, mediaType: MediaType) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
switch (mediaType) {
|
||||
case MEDIA_TYPE.AUDIO: {
|
||||
sendAnalytics(createToolbarEvent(AUDIO_MUTE, { enable }));
|
||||
dispatch(setAudioMuted(enable, /* ensureTrack */ true));
|
||||
break;
|
||||
}
|
||||
case MEDIA_TYPE.SCREENSHARE: {
|
||||
sendAnalytics(createToolbarEvent(DESKTOP_MUTE, { enable }));
|
||||
dispatch(setScreenshareMuted(enable, SCREENSHARE_MUTISM_AUTHORITY.USER, /* ensureTrack */ true));
|
||||
break;
|
||||
}
|
||||
case MEDIA_TYPE.VIDEO: {
|
||||
sendAnalytics(createToolbarEvent(VIDEO_MUTE, { enable }));
|
||||
dispatch(setVideoMuted(enable, VIDEO_MUTISM_AUTHORITY.USER, /* ensureTrack */ true));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
export function muteLocal(enable: boolean, mediaType: MediaType, stopScreenSharing = false) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const isAudio = mediaType === MEDIA_TYPE.AUDIO;
|
||||
|
||||
if (!isAudio && mediaType !== MEDIA_TYPE.VIDEO) {
|
||||
logger.error(`Unsupported media type: ${mediaType}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// check for A/V Moderation when trying to unmute
|
||||
if (isAudio && !enable && shouldShowModeratedNotification(MEDIA_TYPE.AUDIO, getState())) {
|
||||
if (!isModerationNotificationDisplayed(MEDIA_TYPE.AUDIO, getState())) {
|
||||
dispatch(showModeratedNotification(MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (enable && stopScreenSharing) {
|
||||
dispatch(toggleScreensharing(false, false));
|
||||
}
|
||||
|
||||
sendAnalytics(createToolbarEvent(isAudio ? AUDIO_MUTE : VIDEO_MUTE, { enable }));
|
||||
dispatch(isAudio ? setAudioMuted(enable, /* ensureTrack */ true)
|
||||
: setVideoMuted(enable, VIDEO_MUTISM_AUTHORITY.USER, /* ensureTrack */ true));
|
||||
|
||||
// FIXME: The old conference logic still relies on this event being emitted.
|
||||
if (typeof APP !== 'undefined') {
|
||||
isAudio ? APP.conference.muteAudio(enable) : APP.conference.muteVideo(enable, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -67,12 +68,13 @@ export function muteLocal(enable: boolean, mediaType: MediaType) {
|
||||
*/
|
||||
export function muteRemote(participantId: string, mediaType: MediaType) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
if (mediaType !== MEDIA_TYPE.AUDIO && mediaType !== MEDIA_TYPE.VIDEO) {
|
||||
logger.error(`Unsupported media type: ${mediaType}`);
|
||||
|
||||
return;
|
||||
}
|
||||
sendAnalytics(createRemoteMuteConfirmedEvent(participantId, mediaType));
|
||||
|
||||
// TODO(saghul): reconcile these 2 types.
|
||||
const muteMediaType = mediaType === MEDIA_TYPE.SCREENSHARE ? 'desktop' : mediaType;
|
||||
|
||||
dispatch(muteRemoteParticipant(participantId, muteMediaType));
|
||||
dispatch(muteRemoteParticipant(participantId, mediaType));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -95,10 +97,8 @@ export function muteAllParticipants(exclude: Array<string>, mediaType: MediaType
|
||||
dispatch(muteRemote(id, mediaType));
|
||||
if (mediaType === MEDIA_TYPE.AUDIO) {
|
||||
dispatch(rejectParticipantAudio(id));
|
||||
} else if (mediaType === MEDIA_TYPE.VIDEO) {
|
||||
} else {
|
||||
dispatch(rejectParticipantVideo(id));
|
||||
} else if (mediaType === MEDIA_TYPE.SCREENSHARE) {
|
||||
dispatch(rejectParticipantDesktop(id));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { requestDisableAudioModeration, requestEnableAudioModeration } from '../../av-moderation/actions';
|
||||
import { MEDIA_TYPE as AVM_MEDIA_TYPE } from '../../av-moderation/constants';
|
||||
import { isEnabledFromState, isSupported } from '../../av-moderation/functions';
|
||||
import { MEDIA_TYPE } from '../../base/media/constants';
|
||||
import { getLocalParticipant, getParticipantDisplayName, isEveryoneModerator } from '../../base/participants/functions';
|
||||
import { muteAllParticipants } from '../actions';
|
||||
|
||||
import AbstractMuteRemoteParticipantDialog, {
|
||||
type IProps as AbstractProps
|
||||
} from './AbstractMuteRemoteParticipantDialog';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractMuteEveryoneDialog}.
|
||||
*/
|
||||
export interface IProps extends WithTranslation {
|
||||
export interface IProps extends AbstractProps {
|
||||
content?: string;
|
||||
dispatch: IStore['dispatch'];
|
||||
exclude: Array<string>;
|
||||
isAudioModerationEnabled?: boolean;
|
||||
isEveryoneModerator: boolean;
|
||||
isModerationSupported?: boolean;
|
||||
participantID: string;
|
||||
showAdvancedModerationToggle: boolean;
|
||||
title: string;
|
||||
}
|
||||
@@ -35,15 +33,17 @@ interface IState {
|
||||
* An abstract Component with the contents for a dialog that asks for confirmation
|
||||
* from the user before muting all remote participants.
|
||||
*
|
||||
* @augments AbstractMuteRemoteParticipantDialog
|
||||
*/
|
||||
export default class AbstractMuteEveryoneDialog<P extends IProps> extends Component<P, IState> {
|
||||
export default class AbstractMuteEveryoneDialog<P extends IProps> extends
|
||||
AbstractMuteRemoteParticipantDialog<P, IState> {
|
||||
static defaultProps = {
|
||||
exclude: [],
|
||||
muteLocal: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractMuteEveryoneDialog} instance.
|
||||
* Initializes a new {@code AbstractMuteRemoteParticipantDialog} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
@@ -84,7 +84,7 @@ export default class AbstractMuteEveryoneDialog<P extends IProps> extends Compon
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onSubmit() {
|
||||
override _onSubmit() {
|
||||
const {
|
||||
dispatch,
|
||||
exclude
|
||||
@@ -124,7 +124,7 @@ export function abstractMapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
isEveryoneModerator: isEveryoneModerator(state)
|
||||
} : {
|
||||
title: t('dialog.muteEveryoneTitle'),
|
||||
isAudioModerationEnabled: isEnabledFromState(AVM_MEDIA_TYPE.AUDIO, state),
|
||||
isAudioModerationEnabled: isEnabledFromState(MEDIA_TYPE.AUDIO, state),
|
||||
isModerationSupported: isSupported()(state),
|
||||
isEveryoneModerator: isEveryoneModerator(state)
|
||||
};
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { requestDisableDesktopModeration, requestEnableDesktopModeration } from '../../av-moderation/actions';
|
||||
import { MEDIA_TYPE as AVM_MEDIA_TYPE } from '../../av-moderation/constants';
|
||||
import { isEnabledFromState, isSupported } from '../../av-moderation/functions';
|
||||
import { MEDIA_TYPE } from '../../base/media/constants';
|
||||
import { getLocalParticipant, getParticipantDisplayName } from '../../base/participants/functions';
|
||||
import { muteAllParticipants } from '../actions';
|
||||
|
||||
import AbstractMuteRemoteParticipantsDesktopDialog, {
|
||||
type IProps as AbstractProps
|
||||
} from './AbstractMuteRemoteParticipantsDesktopDialog';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractMuteEveryonesDesktopDialog}.
|
||||
*/
|
||||
export interface IProps extends AbstractProps {
|
||||
content?: string;
|
||||
exclude: Array<string>;
|
||||
isModerationEnabled?: boolean;
|
||||
isModerationSupported?: boolean;
|
||||
showAdvancedModerationToggle: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
content: string;
|
||||
moderationEnabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* An abstract Component with the contents for a dialog that asks for confirmation
|
||||
* from the user before disabling all remote participants cameras.
|
||||
*
|
||||
* @augments AbstractMuteRemoteParticipantsDesktopDialog
|
||||
*/
|
||||
export default class AbstractMuteEveryonesDesktopDialog<P extends IProps>
|
||||
extends AbstractMuteRemoteParticipantsDesktopDialog<P, IState> {
|
||||
static defaultProps = {
|
||||
exclude: [],
|
||||
muteLocal: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractMuteRemoteParticipantsDesktopDialog} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
moderationEnabled: props.isModerationEnabled,
|
||||
content: props.content || props.t(props.isModerationEnabled
|
||||
? 'dialog.muteEveryonesDesktopDialogModerationOn' : 'dialog.muteEveryonesDesktopDialog'
|
||||
)
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
this._onToggleModeration = this._onToggleModeration.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles advanced moderation switch.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToggleModeration() {
|
||||
this.setState(state => {
|
||||
return {
|
||||
moderationEnabled: !state.moderationEnabled,
|
||||
content: this.props.t(state.moderationEnabled
|
||||
? 'dialog.muteEveryonesDesktopDialog' : 'dialog.muteEveryonesDesktopDialogModerationOn'
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the value of this dialog is submitted.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
override _onSubmit() {
|
||||
const {
|
||||
dispatch,
|
||||
exclude
|
||||
} = this.props;
|
||||
|
||||
dispatch(muteAllParticipants(exclude, MEDIA_TYPE.SCREENSHARE));
|
||||
if (this.state.moderationEnabled) {
|
||||
dispatch(requestEnableDesktopModeration());
|
||||
} else if (this.state.moderationEnabled !== undefined) {
|
||||
dispatch(requestDisableDesktopModeration());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated {@code AbstractMuteEveryonesDesktopDialog}'s props.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @param {Object} ownProps - The properties explicitly passed to the component.
|
||||
* @returns {IProps}
|
||||
*/
|
||||
export function abstractMapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
const { exclude = [], t } = ownProps;
|
||||
const isModerationEnabled = isEnabledFromState(AVM_MEDIA_TYPE.DESKTOP, state);
|
||||
|
||||
const whom = exclude
|
||||
// eslint-disable-next-line no-confusing-arrow
|
||||
.map(id => id === getLocalParticipant(state)?.id
|
||||
? t('dialog.muteEveryoneSelf')
|
||||
: getParticipantDisplayName(state, id))
|
||||
.join(', ');
|
||||
|
||||
return whom.length ? {
|
||||
content: t('dialog.muteEveryoneElsesDesktopDialog'),
|
||||
title: t('dialog.muteEveryoneElsesDesktopTitle', { whom })
|
||||
} : {
|
||||
title: t('dialog.muteEveryonesDesktopTitle'),
|
||||
isModerationEnabled,
|
||||
isModerationSupported: isSupported()(state)
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { requestDisableVideoModeration, requestEnableVideoModeration } from '../../av-moderation/actions';
|
||||
import { MEDIA_TYPE as AVM_MEDIA_TYPE } from '../../av-moderation/constants';
|
||||
import { isEnabledFromState, isSupported } from '../../av-moderation/functions';
|
||||
import { MEDIA_TYPE } from '../../base/media/constants';
|
||||
import { getLocalParticipant, getParticipantDisplayName } from '../../base/participants/functions';
|
||||
@@ -110,7 +109,7 @@ export default class AbstractMuteEveryonesVideoDialog<P extends IProps>
|
||||
*/
|
||||
export function abstractMapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
const { exclude = [], t } = ownProps;
|
||||
const isVideoModerationEnabled = isEnabledFromState(AVM_MEDIA_TYPE.VIDEO, state);
|
||||
const isVideoModerationEnabled = isEnabledFromState(MEDIA_TYPE.VIDEO, state);
|
||||
|
||||
const whom = exclude
|
||||
// eslint-disable-next-line no-confusing-arrow
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { MEDIA_TYPE } from '../../base/media/constants';
|
||||
import { muteRemote } from '../actions';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractMuteRemoteParticipantDialog}.
|
||||
*/
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* The ID of the remote participant to be muted.
|
||||
*/
|
||||
participantID: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract dialog to confirm a remote participant mute action.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
export default class AbstractMuteRemoteParticipantDialog<P extends IProps = IProps, State=void>
|
||||
extends Component<P, State> {
|
||||
/**
|
||||
* Initializes a new {@code AbstractMuteRemoteParticipantDialog} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the submit button action.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean} - True (to note that the modal should be closed).
|
||||
*/
|
||||
_onSubmit() {
|
||||
const { dispatch, participantID } = this.props;
|
||||
|
||||
dispatch(muteRemote(participantID, MEDIA_TYPE.AUDIO));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { rejectParticipantDesktop } from '../../av-moderation/actions';
|
||||
import { MEDIA_TYPE as AVM_MEDIA_TYPE } from '../../av-moderation/constants';
|
||||
import { isEnabledFromState } from '../../av-moderation/functions';
|
||||
import { MEDIA_TYPE } from '../../base/media/constants';
|
||||
import { muteRemote } from '../actions';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractMuteRemoteParticipantsDesktopDialog}.
|
||||
*/
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Whether or not desktop moderation is on.
|
||||
*/
|
||||
isModerationOn: boolean;
|
||||
|
||||
/**
|
||||
* The ID of the remote participant to be muted.
|
||||
*/
|
||||
participantID: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract dialog to confirm a remote participant desktop mute action.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
export default class AbstractMuteRemoteParticipantsDesktopDialog<P extends IProps = IProps, State=any>
|
||||
extends Component<P, State> {
|
||||
/**
|
||||
* Initializes a new {@code AbstractMuteRemoteParticipantsDesktopDialog} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the submit button action.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean} - True (to note that the modal should be closed).
|
||||
*/
|
||||
_onSubmit() {
|
||||
const { dispatch, participantID } = this.props;
|
||||
|
||||
dispatch(muteRemote(participantID, MEDIA_TYPE.SCREENSHARE));
|
||||
dispatch(rejectParticipantDesktop(participantID));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated
|
||||
* {@code AbstractDialogContainer}'s props.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function abstractMapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
isModerationOn: isEnabledFromState(AVM_MEDIA_TYPE.DESKTOP, state)
|
||||
};
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { rejectParticipantVideo } from '../../av-moderation/actions';
|
||||
import { MEDIA_TYPE as AVM_MEDIA_TYPE } from '../../av-moderation/constants';
|
||||
import { isEnabledFromState } from '../../av-moderation/functions';
|
||||
import { MEDIA_TYPE } from '../../base/media/constants';
|
||||
import { muteRemote } from '../actions';
|
||||
@@ -76,6 +75,6 @@ export default class AbstractMuteRemoteParticipantsVideoDialog<P extends IProps
|
||||
*/
|
||||
export function abstractMapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
isVideoModerationOn: isEnabledFromState(AVM_MEDIA_TYPE.VIDEO, state)
|
||||
isVideoModerationOn: isEnabledFromState(MEDIA_TYPE.VIDEO, state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { approveParticipant } from '../../../av-moderation/actions';
|
||||
import { MEDIA_TYPE } from '../../../av-moderation/constants';
|
||||
import { isForceMuted, isSupported } from '../../../av-moderation/functions';
|
||||
import { isSupported } from '../../../av-moderation/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconMic, IconVideo } from '../../../base/icons/svg';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import { getParticipantById, isLocalParticipantModerator } from '../../../base/participants/functions';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { isForceMuted } from '../../../participants-pane/functions';
|
||||
|
||||
export interface IProps extends AbstractButtonProps {
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { approveParticipantAudio, approveParticipantDesktop, approveParticipantVideo } from '../../../av-moderation/actions';
|
||||
import { IconMic, IconScreenshare, IconVideo } from '../../../base/icons/svg';
|
||||
import { approveParticipantAudio, approveParticipantVideo } from '../../../av-moderation/actions';
|
||||
import { IconMic, IconVideo } from '../../../base/icons/svg';
|
||||
import { MEDIA_TYPE, MediaType } from '../../../base/media/constants';
|
||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
|
||||
import { NOTIFY_CLICK_MODE } from '../../../toolbox/types';
|
||||
@@ -36,8 +36,6 @@ const AskToUnmuteButton = ({
|
||||
dispatch(approveParticipantAudio(participantID));
|
||||
} else if (buttonType === MEDIA_TYPE.VIDEO) {
|
||||
dispatch(approveParticipantVideo(participantID));
|
||||
} else if (buttonType === MEDIA_TYPE.SCREENSHARE) {
|
||||
dispatch(approveParticipantDesktop(participantID));
|
||||
}
|
||||
}, [ buttonType, dispatch, notifyClick, notifyMode, participantID ]);
|
||||
|
||||
@@ -46,8 +44,6 @@ const AskToUnmuteButton = ({
|
||||
return t('participantsPane.actions.askUnmute');
|
||||
} else if (buttonType === MEDIA_TYPE.VIDEO) {
|
||||
return t('participantsPane.actions.allowVideo');
|
||||
} else if (buttonType === MEDIA_TYPE.SCREENSHARE) {
|
||||
return t('participantsPane.actions.allowDesktop');
|
||||
}
|
||||
|
||||
return '';
|
||||
@@ -58,8 +54,6 @@ const AskToUnmuteButton = ({
|
||||
return IconMic;
|
||||
} else if (buttonType === MEDIA_TYPE.VIDEO) {
|
||||
return IconVideo;
|
||||
} else if (buttonType === MEDIA_TYPE.SCREENSHARE) {
|
||||
return IconScreenshare;
|
||||
}
|
||||
}, [ buttonType ]);
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { createRemoteVideoMenuButtonEvent } from '../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../analytics/functions';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { openDialog } from '../../../base/dialog/actions';
|
||||
import { IconScreenshare } from '../../../base/icons/svg';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import { isRemoteTrackMuted } from '../../../base/tracks/functions.any';
|
||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
|
||||
import { NOTIFY_CLICK_MODE } from '../../../toolbox/types';
|
||||
import { IButtonProps } from '../../types';
|
||||
|
||||
import MuteRemoteParticipantsDesktopDialog from './MuteRemoteParticipantsDesktopDialog';
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays a button for disabling
|
||||
* the desktop share of a participant in the conference.
|
||||
*
|
||||
* @returns {JSX.Element|null}
|
||||
*/
|
||||
const MuteDesktopButton = ({
|
||||
notifyClick,
|
||||
notifyMode,
|
||||
participantID
|
||||
}: IButtonProps): JSX.Element | null => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const tracks = useSelector((state: IReduxState) => state['features/base/tracks']);
|
||||
|
||||
// TODO: review if we shouldn't be using isParticipantMediaMuted.
|
||||
const trackMuted = useMemo(
|
||||
() => isRemoteTrackMuted(tracks, MEDIA_TYPE.SCREENSHARE, participantID),
|
||||
[ isRemoteTrackMuted, participantID, tracks ]
|
||||
);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
notifyClick?.();
|
||||
if (notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) {
|
||||
return;
|
||||
}
|
||||
sendAnalytics(createRemoteVideoMenuButtonEvent(
|
||||
'desktop.mute.button',
|
||||
{
|
||||
'participant_id': participantID
|
||||
}));
|
||||
|
||||
dispatch(openDialog(MuteRemoteParticipantsDesktopDialog, { participantID }));
|
||||
}, [ dispatch, notifyClick, notifyClick, participantID, sendAnalytics ]);
|
||||
|
||||
if (trackMuted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = { t('participantsPane.actions.stopDesktop') }
|
||||
className = 'mutedesktoplink'
|
||||
icon = { IconScreenshare }
|
||||
onClick = { handleClick }
|
||||
text = { t('participantsPane.actions.stopDesktop') } />
|
||||
);
|
||||
};
|
||||
|
||||
export default MuteDesktopButton;
|
||||
@@ -1,48 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../analytics/functions';
|
||||
import { openDialog } from '../../../base/dialog/actions';
|
||||
import { IconScreenshare } from '../../../base/icons/svg';
|
||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
|
||||
import { NOTIFY_CLICK_MODE } from '../../../toolbox/types';
|
||||
import { IButtonProps } from '../../types';
|
||||
|
||||
import MuteEveryonesDesktopDialog from './MuteEveryonesDesktopDialog';
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays a button for audio muting
|
||||
* every participant in the conference except the one with the given
|
||||
* participantID.
|
||||
*
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const MuteEveryoneElsesDesktopButton = ({
|
||||
notifyClick,
|
||||
notifyMode,
|
||||
participantID
|
||||
}: IButtonProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
notifyClick?.();
|
||||
if (notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) {
|
||||
return;
|
||||
}
|
||||
sendAnalytics(createToolbarEvent('mute.everyoneelsesdesktop.pressed'));
|
||||
dispatch(openDialog(MuteEveryonesDesktopDialog, { exclude: [ participantID ] }));
|
||||
}, [ notifyClick, notifyMode, participantID ]);
|
||||
|
||||
return (
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.muteEveryoneElsesDesktopStream') }
|
||||
icon = { IconScreenshare }
|
||||
onClick = { handleClick }
|
||||
text = { t('videothumbnail.domuteDesktopOfOthers') } />
|
||||
);
|
||||
};
|
||||
|
||||
export default MuteEveryoneElsesDesktopButton;
|
||||
@@ -1,52 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
import Switch from '../../../base/ui/components/web/Switch';
|
||||
import AbstractMuteEveryonesDesktopDialog, { type IProps, abstractMapStateToProps }
|
||||
from '../AbstractMuteEveryonesDesktopDialog';
|
||||
|
||||
/**
|
||||
* A React Component with the contents for a dialog that asks for confirmation
|
||||
* from the user before disabling all remote participants cameras.
|
||||
*
|
||||
* @augments AbstractMuteEveryonesDesktopDialog
|
||||
*/
|
||||
class MuteEveryonesDesktopDialog extends AbstractMuteEveryonesDesktopDialog<IProps> {
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
override render() {
|
||||
return (
|
||||
<Dialog
|
||||
ok = {{ translationKey: 'dialog.muteParticipantsDesktopButton' }}
|
||||
onSubmit = { this._onSubmit }
|
||||
title = { this.props.title }>
|
||||
<div className = 'mute-dialog'>
|
||||
{this.state.content}
|
||||
{ this.props.isModerationSupported && this.props.exclude.length === 0 && (
|
||||
<>
|
||||
<div className = 'separator-line' />
|
||||
<div className = 'control-row'>
|
||||
<label htmlFor = 'moderation-switch'>
|
||||
{this.props.t('dialog.moderationDesktopLabel')}
|
||||
</label>
|
||||
<Switch
|
||||
checked = { !this.state.moderationEnabled }
|
||||
id = 'moderation-switch'
|
||||
onChange = { this._onToggleModeration } />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect(abstractMapStateToProps)(MuteEveryonesDesktopDialog));
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
import AbstractMuteRemoteParticipantsDesktopDialog, {
|
||||
abstractMapStateToProps
|
||||
} from '../AbstractMuteRemoteParticipantsDesktopDialog';
|
||||
|
||||
/**
|
||||
* A React Component with the contents for a dialog that asks for confirmation
|
||||
* from the user before disabling a remote participants camera.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class MuteRemoteParticipantsDesktopDialog extends AbstractMuteRemoteParticipantsDesktopDialog {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
override render() {
|
||||
return (
|
||||
<Dialog
|
||||
ok = {{ translationKey: 'dialog.muteParticipantsDesktopButton' }}
|
||||
onSubmit = { this._onSubmit }
|
||||
titleKey = 'dialog.muteParticipantsDesktopTitle'>
|
||||
<div>
|
||||
{this.props.t(this.props.isModerationOn
|
||||
? 'dialog.muteParticipantsDesktopBodyModerationOn'
|
||||
: 'dialog.muteParticipantsDesktopBody'
|
||||
) }
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect(abstractMapStateToProps)(MuteRemoteParticipantsDesktopDialog));
|
||||
@@ -4,15 +4,14 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { MEDIA_TYPE as AVM_MEDIA_TYPE } from '../../../av-moderation/constants';
|
||||
import { isSupported as isAvModerationSupported, isForceMuted } from '../../../av-moderation/functions';
|
||||
import { isSupported as isAvModerationSupported } from '../../../av-moderation/functions';
|
||||
import Avatar from '../../../base/avatar/components/Avatar';
|
||||
import { isIosMobileBrowser, isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import { PARTICIPANT_ROLE } from '../../../base/participants/constants';
|
||||
import { getLocalParticipant, hasRaisedHand, isPrivateChatEnabled } from '../../../base/participants/functions';
|
||||
import { IParticipant } from '../../../base/participants/types';
|
||||
import { isParticipantAudioMuted } from '../../../base/tracks/functions.any';
|
||||
import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks/functions.any';
|
||||
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
|
||||
import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup';
|
||||
import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
@@ -21,7 +20,7 @@ import { displayVerification } from '../../../e2ee/functions';
|
||||
import { setVolume } from '../../../filmstrip/actions.web';
|
||||
import { isStageFilmstripAvailable } from '../../../filmstrip/functions.web';
|
||||
import { QUICK_ACTION_BUTTON } from '../../../participants-pane/constants';
|
||||
import { getQuickActionButtonType } from '../../../participants-pane/functions';
|
||||
import { getQuickActionButtonType, isForceMuted } from '../../../participants-pane/functions';
|
||||
import { requestRemoteControl, stopController } from '../../../remote-control/actions';
|
||||
import { getParticipantMenuButtonsWithNotifyClick, showOverflowDrawer } from '../../../toolbox/functions.web';
|
||||
import { NOTIFY_CLICK_MODE } from '../../../toolbox/types';
|
||||
@@ -35,9 +34,7 @@ import GrantModeratorButton from './GrantModeratorButton';
|
||||
import KickButton from './KickButton';
|
||||
import LowerHandButton from './LowerHandButton';
|
||||
import MuteButton from './MuteButton';
|
||||
import MuteDesktopButton from './MuteDesktopButton';
|
||||
import MuteEveryoneElseButton from './MuteEveryoneElseButton';
|
||||
import MuteEveryoneElsesDesktopButton from './MuteEveryoneElsesDesktopButton';
|
||||
import MuteEveryoneElsesVideoButton from './MuteEveryoneElsesVideoButton';
|
||||
import MuteVideoButton from './MuteVideoButton';
|
||||
import PrivateMessageMenuButton from './PrivateMessageMenuButton';
|
||||
@@ -137,10 +134,9 @@ const ParticipantContextMenu = ({
|
||||
const localParticipant = useSelector(getLocalParticipant);
|
||||
const _isModerator = Boolean(localParticipant?.role === PARTICIPANT_ROLE.MODERATOR);
|
||||
const _isVideoForceMuted = useSelector<IReduxState>(state =>
|
||||
isForceMuted(participant, AVM_MEDIA_TYPE.VIDEO, state));
|
||||
const _isDesktopForceMuted = useSelector<IReduxState>(state =>
|
||||
isForceMuted(participant, AVM_MEDIA_TYPE.DESKTOP, state));
|
||||
isForceMuted(participant, MEDIA_TYPE.VIDEO, state));
|
||||
const _isAudioMuted = useSelector((state: IReduxState) => isParticipantAudioMuted(participant, state));
|
||||
const _isVideoMuted = useSelector((state: IReduxState) => isParticipantVideoMuted(participant, state));
|
||||
const _overflowDrawer: boolean = useSelector(showOverflowDrawer);
|
||||
const { remoteVideoMenu = {}, disableRemoteMute, startSilent, customParticipantMenuButtons }
|
||||
= useSelector((state: IReduxState) => state['features/base/config']);
|
||||
@@ -194,7 +190,7 @@ const ParticipantContextMenu = ({
|
||||
() => !_overflowDrawer && !thumbnailMenu,
|
||||
[ _overflowDrawer, thumbnailMenu ]);
|
||||
const quickActionButtonType = useSelector((state: IReduxState) =>
|
||||
getQuickActionButtonType(participant, state));
|
||||
getQuickActionButtonType(participant, _isAudioMuted, _isVideoMuted, state));
|
||||
|
||||
const buttons: JSX.Element[] = [];
|
||||
const buttons2: JSX.Element[] = [];
|
||||
@@ -233,13 +229,6 @@ const ParticipantContextMenu = ({
|
||||
buttonType = { MEDIA_TYPE.VIDEO } />
|
||||
);
|
||||
}
|
||||
if (_isDesktopForceMuted
|
||||
&& !(isClickedFromParticipantPane && quickActionButtonType === QUICK_ACTION_BUTTON.ALLOW_DESKTOP)) {
|
||||
buttons.push(<AskToUnmuteButton
|
||||
{ ...getButtonProps(BUTTONS.ALLOW_DESKTOP) }
|
||||
buttonType = { MEDIA_TYPE.SCREENSHARE } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!disableRemoteMute && !participant.isSilent) {
|
||||
@@ -251,10 +240,6 @@ const ParticipantContextMenu = ({
|
||||
buttons.push(<MuteVideoButton { ...getButtonProps(BUTTONS.MUTE_VIDEO) } />);
|
||||
}
|
||||
buttons.push(<MuteEveryoneElsesVideoButton { ...getButtonProps(BUTTONS.MUTE_OTHERS_VIDEO) } />);
|
||||
if (!(isClickedFromParticipantPane && quickActionButtonType === QUICK_ACTION_BUTTON.STOP_DESKTOP)) {
|
||||
buttons.push(<MuteDesktopButton { ...getButtonProps(BUTTONS.MUTE_DESKTOP) } />);
|
||||
}
|
||||
buttons.push(<MuteEveryoneElsesDesktopButton { ...getButtonProps(BUTTONS.MUTE_OTHERS_DESKTOP) } />);
|
||||
}
|
||||
|
||||
if (raisedHands) {
|
||||
|
||||
@@ -17,7 +17,6 @@ export const VOLUME_SLIDER_SCALE = 100;
|
||||
* Participant context menu button keys.
|
||||
*/
|
||||
export const PARTICIPANT_MENU_BUTTONS = {
|
||||
ALLOW_DESKTOP: 'allow-desktop',
|
||||
ALLOW_VIDEO: 'allow-video',
|
||||
ASK_UNMUTE: 'ask-unmute',
|
||||
CONN_STATUS: 'conn-status',
|
||||
@@ -28,9 +27,7 @@ export const PARTICIPANT_MENU_BUTTONS = {
|
||||
KICK: 'kick',
|
||||
LOWER_PARTICIPANT_HAND: 'lower-participant-hand',
|
||||
MUTE: 'mute',
|
||||
MUTE_DESKTOP: 'mute-desktop',
|
||||
MUTE_OTHERS: 'mute-others',
|
||||
MUTE_OTHERS_DESKTOP: 'mute-others-desktop',
|
||||
MUTE_OTHERS_VIDEO: 'mute-others-video',
|
||||
MUTE_VIDEO: 'mute-video',
|
||||
PIN_TO_STAGE: 'pinToStage',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user