Compare commits

..

2 Commits

Author SHA1 Message Date
tmoldovan8x8
8d928ad315 fix(rn, recording) adds _toggleScreenshotCapture function to AbstractStartRecordingDialog 2021-12-20 08:44:45 +02:00
Saúl Ibarra Corretgé
ef9913c1c7 fix(rn) fix broken mobile build
Importing the entire feature triggers web UI imports.
2021-12-11 15:22:03 +01:00
1868 changed files with 88131 additions and 96065 deletions

View File

@@ -3,10 +3,12 @@ build/*
# Third-party source code which we (1) do not want to modify or (2) try to
# modify as little as possible.
flow-typed/*
libs/*
resources/*
react/features/stream-effects/virtual-background/vendor/*
react/features/face-landmarks/resources/*
load-test/*
react/features/facial-recognition/resources/*
# ESLint will by default ignore its own configuration file. However, there does
# not seem to be a reason why we will want to risk being inconsistent with our
@@ -14,4 +16,4 @@ react/features/face-landmarks/resources/*
!.eslintrc.js
# Not worth it.
actionTypes.ts
actionTypes.js

View File

@@ -1,6 +1,5 @@
module.exports = {
'extends': [
'@jitsi/eslint-config'
],
'ignorePatterns': [ '*.d.ts' ]
]
};

118
.flowconfig Normal file
View File

@@ -0,0 +1,118 @@
[ignore]
; We fork some components by platform
.*/*[.]android.js
; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
node_modules/react-native/Libraries/react-native/React.js
; Ignore duplicate module providers
; For RN Apps installed via npm, "Libraries" folder is inside
; "node_modules/react-native" but in the source repo it is in the root
node_modules/react-native/Libraries/react-native/React.js
; Flow doesn't support platforms
.*/Libraries/Utilities/LoadingView.js
; Ignore polyfills
node_modules/react-native/Libraries/polyfills/.*
; These should not be required directly
; require from fbjs/lib instead: require('fbjs/lib/warning')
node_modules/warning/.*
; Flow doesn't support platforms
.*/Libraries/Utilities/HMRLoadingView.js
[untyped]
.*/node_modules/@react-native-community/cli/.*/.*
; Ignore packages in node_modules which we (i.e. the jitsi-meet project) have
; seen to cause errors and we have chosen not to fix.
.*/node_modules/@atlaskit/.*/*.js.flow
.*/node_modules/react-native-keep-awake/.*
.*/node_modules/react-native-permissions/.*
.*/node_modules/styled-components/.*
.*/\.git/.*
[include]
[libs]
node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow/
[options]
emoji=true
esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable
; We (i.e. the jitsi-meet project) are using the haste module system on Web as
; well, not only on React Native. Unfortunately, Flow does not support .web.js
; by default. Override Flow's defaults to include .web.js as well. Technically,
; we have .native.js as well so the choice of .web.js may lead to errors.
; Practically though, it is a potential future problem that we do not have at
; the time of this writing.
module.file_ext=.web.js
; Flow's defaults:
module.file_ext=.js
module.file_ext=.json
module.file_ext=.ios.js
module.system=haste
module.system.haste.use_name_reducers=true
# get basename
module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
# strip .js or .js.flow suffix
module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
# strip .ios suffix
module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
module.system.haste.paths.blacklist=.*/__tests__/.*
module.system.haste.paths.blacklist=.*/__mocks__/.*
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/RNTester/.*
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/IntegrationTests/.*
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/react-native/react-native-implementation.js
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
munge_underscores=true
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
[lints]
sketchy-null-number=warn
sketchy-null-mixed=warn
sketchy-number=warn
untyped-type-import=warn
nonstrict-import=warn
deprecated-type=warn
unsafe-getters-setters=warn
inexact-spread=warn
unnecessary-invariant=warn
signature-verification-failure=warn
deprecated-utility=error
[strict]
deprecated-type
nonstrict-import
sketchy-null
unclear-type
unsafe-getters-setters
untyped-import
untyped-type-import
[version]
^0.104.0

View File

@@ -3,43 +3,20 @@ name: Simple CI
on: [pull_request]
jobs:
lint:
name: Lint
run-ci:
name: Build Frontend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
- uses: actions/setup-node@v1
with:
node-version: 16
cache: 'npm'
node-version: '16.x'
- run: npm install
- name: Check git status
run: git status
- name: Normalize lang files to ensure sorted
run: npm run lang-sort
- name: Check git diff
run: git diff
- name: Check if the git repository is clean
run: $(exit $(git status --porcelain --untracked-files=no | head -255 | wc -l)) || (echo "Dirty git tree"; git diff; exit 1)
run: exit $( git status --porcelain --untracked-files=no | head -255 | wc -l )
- run: npm run lint
- run: for file in lang/*.json; do npx --yes jsonlint -q $file || exit 1; done
linux-build:
name: Build Frontend (Linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
- run: npm install
- run: make
macos-ci:
name: Build Frontend (macOS)
runs-on: macOS-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
- run: npm install
- run: make

View File

@@ -3,8 +3,8 @@ We would love to have your help. Before you start working however, please read
and follow this short guide.
# Reporting Issues
Provide as much information as possible. Mention the version of Jitsi Meet,
Jicofo and JVB you are using, and explain (as detailed as you can) how the
Provide as much information as possible. Mention the version of Jitsi Meet,
Jicofo and JVB you are using, and explain (as detailed as you can) how the
problem can be reproduced.
# Code contributions
@@ -130,7 +130,7 @@ When adding a new feature, this would be the usual layout.
```
react/features/sample/
├── actionTypes.ts
├── actionTypes.js
├── actions.js
├── components
│   ├── AnotherComponent.js

View File

@@ -1,40 +1,35 @@
BUILD_DIR = build
CLEANCSS = ./node_modules/.bin/cleancss
DEPLOY_DIR = libs
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
OLM_DIR = node_modules/@matrix-org/olm
TF_WASM_DIR = node_modules/@tensorflow/tfjs-backend-wasm/dist/
RNNOISE_WASM_DIR = node_modules/@jitsi/rnnoise-wasm/dist
EXCALIDRAW_DIR = node_modules/@jitsi/excalidraw/dist/excalidraw-assets
EXCALIDRAW_DIR_DEV = node_modules/@jitsi/excalidraw/dist/excalidraw-assets-dev
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
TFLITE_WASM = react/features/stream-effects/virtual-background/vendor/tflite
MEET_MODELS_DIR = react/features/stream-effects/virtual-background/vendor/models
FACE_MODELS_DIR = node_modules/@vladmandic/human-models/models
MEET_MODELS_DIR = react/features/stream-effects/virtual-background/vendor/models/
FACIAL_MODELS_DIR = react/features/facial-recognition/resources
NODE_SASS = ./node_modules/.bin/sass
NPM = npm
OUTPUT_DIR = .
STYLES_BUNDLE = css/all.bundle.css
STYLES_DESTINATION = css/all.css
STYLES_MAIN = css/main.scss
ifeq ($(OS),Windows_NT)
WEBPACK = .\node_modules\.bin\webpack
WEBPACK_DEV_SERVER = .\node_modules\.bin\webpack serve --mode development
else
WEBPACK = ./node_modules/.bin/webpack
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack serve --mode development
endif
WEBPACK = ./node_modules/.bin/webpack
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack serve --mode development
all: compile deploy clean
compile:
NODE_OPTIONS=--max-old-space-size=8192 \
compile: compile-load-test
$(WEBPACK)
compile-load-test:
${NPM} install --prefix resources/load-test && ${NPM} run build --prefix resources/load-test
clean:
rm -fr $(BUILD_DIR)
.NOTPARALLEL:
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-excalidraw deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-olm deploy-tf-wasm deploy-css deploy-local deploy-face-landmarks
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-css deploy-local deploy-facial-expressions
deploy-init:
rm -fr $(DEPLOY_DIR)
@@ -48,6 +43,8 @@ deploy-appbundle:
$(BUILD_DIR)/do_external_connect.min.js.map \
$(BUILD_DIR)/external_api.min.js \
$(BUILD_DIR)/external_api.min.js.map \
$(BUILD_DIR)/flacEncodeWorker.min.js \
$(BUILD_DIR)/flacEncodeWorker.min.js.map \
$(BUILD_DIR)/dial_in_info_bundle.min.js \
$(BUILD_DIR)/dial_in_info_bundle.min.js.map \
$(BUILD_DIR)/alwaysontop.min.js \
@@ -55,10 +52,8 @@ deploy-appbundle:
$(OUTPUT_DIR)/analytics-ga.js \
$(BUILD_DIR)/analytics-ga.min.js \
$(BUILD_DIR)/analytics-ga.min.js.map \
$(BUILD_DIR)/face-landmarks-worker.min.js \
$(BUILD_DIR)/face-landmarks-worker.min.js.map \
$(BUILD_DIR)/noise-suppressor-worklet.min.js \
$(BUILD_DIR)/noise-suppressor-worklet.min.js.map \
$(BUILD_DIR)/facial-expressions-worker.min.js \
$(BUILD_DIR)/facial-expressions-worker.min.js.map \
$(DEPLOY_DIR)
cp \
$(BUILD_DIR)/close3.min.js \
@@ -67,23 +62,24 @@ deploy-appbundle:
deploy-lib-jitsi-meet:
cp \
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.min.js \
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.min.map \
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.e2ee-worker.js \
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.js \
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.map \
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.e2ee-worker.js \
$(LIBJITSIMEET_DIR)/connection_optimization/external_connect.js \
$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
$(DEPLOY_DIR)
deploy-libflac:
cp \
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js \
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js.mem \
$(DEPLOY_DIR)
deploy-olm:
cp \
$(OLM_DIR)/olm.wasm \
$(DEPLOY_DIR)
deploy-tf-wasm:
cp \
$(TF_WASM_DIR)/*.wasm \
$(DEPLOY_DIR)
deploy-rnnoise-binary:
cp \
$(RNNOISE_WASM_DIR)/rnnoise.wasm \
@@ -94,39 +90,26 @@ deploy-tflite:
$(TFLITE_WASM)/*.wasm \
$(DEPLOY_DIR)
deploy-excalidraw:
cp -R \
$(EXCALIDRAW_DIR) \
$(DEPLOY_DIR)/
deploy-excalidraw-dev:
cp -R \
$(EXCALIDRAW_DIR_DEV) \
$(DEPLOY_DIR)/
deploy-meet-models:
cp \
$(MEET_MODELS_DIR)/*.tflite \
$(DEPLOY_DIR)
deploy-face-landmarks:
deploy-facial-expressions:
cp \
$(FACE_MODELS_DIR)/blazeface-front.bin \
$(FACE_MODELS_DIR)/blazeface-front.json \
$(FACE_MODELS_DIR)/emotion.bin \
$(FACE_MODELS_DIR)/emotion.json \
$(FACIAL_MODELS_DIR)/* \
$(DEPLOY_DIR)
deploy-css:
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
$(CLEANCSS) --skip-rebase $(STYLES_BUNDLE) > $(STYLES_DESTINATION) && \
$(CLEANCSS) --skip-rebase $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
rm $(STYLES_BUNDLE)
deploy-local:
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
.NOTPARALLEL:
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-olm deploy-tf-wasm deploy-excalidraw-dev deploy-face-landmarks
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-facial-expressions
$(WEBPACK_DEV_SERVER)
source-package:

View File

@@ -18,6 +18,7 @@ Amongst others here are the main features Jitsi Meet offers:
* Web and native SDKs for integration
* HD audio and video
* Content sharing
* End-to-End Encryption
* Raise hand and reactions
* Chat with private conversations
* Polls
@@ -44,7 +45,7 @@ developed you can also sign up for our open beta testing here:
## Running your own instance
If you'd like to run your own Jitsi Meet installation head over to the [handbook](https://jitsi.github.io/handbook/docs/devops-guide/) to get started.
If you'd like to run your own Jitsi Meet installation head over to the [handbook](https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-start) to get started.
We provide Debian packages and a comprehensive Docker setup to make deployments as simple as possible.
Advanced users also have the possibility of building all the components from source.

View File

@@ -76,7 +76,7 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'

View File

@@ -7,14 +7,13 @@
android:extractNativeLibs="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".MainApplication"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme">
<meta-data
android:name="android.content.APP_RESTRICTIONS"
android:resource="@xml/app_restrictions" />
<activity
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"

View File

@@ -1,47 +0,0 @@
/*
* Copyright @ 2022-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet;
import android.app.Application;
import android.util.Log;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import org.jitsi.meet.sdk.JitsiReactNativeHost;
/**
* Application class for Jitsi Meet. The only reason why this exists is for Detox
* to believe our app is a "greenfield" app. SDK users need not use this.
*/
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new JitsiReactNativeHost(this);
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
// Initialize RN
Log.d(this.getClass().getCanonicalName(), "app onCreate");
getReactNativeHost().getReactInstanceManager();
}
}

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
android:insetTop="@dimen/abc_edit_text_inset_top_material"
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
<selector>
<!--
This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
<item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.
-->
<item android:state_enabled="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
<item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
</selector>
</inset>

View File

@@ -1,8 +1,7 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
<item name="android:forceDarkAllowed">false</item>
<!-- Customize your theme here. -->
<item name="android:navigationBarColor">@color/colorPrimaryDark</item>
<item name="android:windowDisablePreview">true</item>
</style>
</style>
</resources>

View File

@@ -1,5 +1,4 @@
import groovy.json.JsonSlurper
import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.util.VersionNumber
// Top-level build file where you can add configuration options common to all
@@ -8,30 +7,22 @@ import org.gradle.util.VersionNumber
buildscript {
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.4'
classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
classpath 'com.android.tools.build:gradle:4.1.2'
classpath 'com.google.gms:google-services:4.3.4'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
}
}
ext {
buildToolsVersion = "31.0.0"
buildToolsVersion = "30.0.3"
compileSdkVersion = 31
minSdkVersion = 23
targetSdkVersion = 31
supportLibVersion = "28.0.0"
if (System.properties['os.arch'] == "aarch64") {
// For M1 Users we need to use the NDK 24 which added support for aarch64
ndkVersion = "24.0.8215888"
} else {
// Otherwise we default to the side-by-side NDK version from AGP.
ndkVersion = "21.4.7075529"
}
// The Maven artifact groupdId 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
@@ -51,19 +42,12 @@ ext {
allprojects {
repositories {
google()
jcenter()
// React Native (JS, Obj-C sources, Android binaries) is installed from npm.
maven { url "$rootDir/../node_modules/react-native/android" }
// Android JSC is installed from npm.
maven { url("$rootDir/../node_modules/jsc-android/dist") }
mavenCentral {
// We don't want to fetch react-native from Maven Central as there are
// older versions over there.
content {
excludeGroup "com.facebook.react"
}
}
google()
maven { url 'https://www.jitpack.io' }
}
// Make sure we use the react-native version in node_modules and not the one

View File

@@ -9,7 +9,7 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
# Default value: -Xmx10248m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
@@ -26,5 +26,5 @@ android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
appVersion=22.6.0
sdkVersion=6.2.0
appVersion=21.6.0
sdkVersion=4.1.0

View File

@@ -1,5 +1,6 @@
#Wed Sep 23 11:48:00 EEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip

286
android/gradlew vendored
View File

@@ -1,129 +1,78 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#!/usr/bin/env sh
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
MAX_FD="maximum"
warn () {
echo "$*"
} >&2
}
die () {
echo
echo "$*"
echo
exit 1
} >&2
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD=$JAVA_HOME/bin/java
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -132,7 +81,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -140,95 +89,84 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

173
android/gradlew.bat vendored
View File

@@ -1,89 +1,84 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1,11 +0,0 @@
#!/bin/bash
PKG_NAME=${1:-org.jitsi.meet}
APP_PID=$(adb shell ps | grep $PKG_NAME | awk '{print $2}')
if [[ -z "$APP_PID" ]]; then
echo "App is not running"
exit 1
fi
exec adb logcat --pid=$APP_PID

View File

@@ -3,7 +3,6 @@ apply plugin: 'maven-publish'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
ndkVersion rootProject.ext.ndkVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
@@ -34,8 +33,9 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.fragment:fragment:1.4.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.fragment:fragment:1.2.5'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
@@ -44,17 +44,21 @@ dependencies {
//noinspection GradleDynamicVersion
implementation 'org.webkit:android-jsc:+'
implementation 'com.facebook.fresco:animated-gif:2.5.0'
implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
implementation 'com.jakewharton.timber:timber:4.7.1'
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"
// Only add these packages if we are NOT doing a LIBRE_BUILD
if (!rootProject.ext.libreBuild) {
if (rootProject.ext.libreBuild) {
implementation(project(':react-native-device-info')) {
exclude group: 'com.google.firebase'
exclude group: 'com.google.android.gms'
exclude group: 'com.android.installreferrer'
}
} else {
implementation project(':react-native-amplitude')
implementation project(':react-native-giphy')
implementation project(':react-native-device-info')
implementation(project(":react-native-google-signin")) {
exclude group: 'com.google.android.gms'
exclude group: 'androidx'
@@ -67,19 +71,13 @@ dependencies {
implementation project(':react-native-community_clipboard')
implementation project(':react-native-community_netinfo')
implementation project(':react-native-default-preference')
implementation(project(':react-native-device-info')) {
exclude group: 'com.google.firebase'
exclude group: 'com.google.android.gms'
exclude group: 'com.android.installreferrer'
}
implementation project(':react-native-gesture-handler')
implementation project(':react-native-get-random-values')
implementation project(':react-native-immersive')
implementation project(':react-native-keep-awake')
implementation project(':react-native-masked-view_masked-view')
implementation project(':react-native-orientation-locker')
implementation project(':react-native-pager-view')
implementation project(':react-native-performance')
implementation project(':react-native-reanimated')
implementation project(':react-native-safe-area-context')
implementation project(':react-native-screens')
implementation project(':react-native-slider')

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application android:usesCleartextTraffic="true">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>
</application>
</manifest>

View File

@@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -30,12 +31,12 @@
android:supportsRtl="true">
<activity
android:name=".JitsiMeetActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
android:launchMode="singleTask"
android:theme="@style/JitsiMeetActivityStyle"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="adjustResize"/>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service
android:name=".ConnectionService"

View File

@@ -16,7 +16,6 @@
package org.jitsi.meet.sdk;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
@@ -55,7 +54,6 @@ import java.util.concurrent.Executors;
* Before a call has started and after it has ended the
* {@code AudioModeModule.DEFAULT} mode should be used.
*/
@SuppressLint("AnnotateVersionCheck")
@ReactModule(name = AudioModeModule.NAME)
class AudioModeModule extends ReactContextBaseJavaModule {
public static final String NAME = "AudioMode";
@@ -81,6 +79,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
/**
* Whether or not the ConnectionService is used for selecting audio devices.
*/
private static final boolean supportsConnectionService = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
private static boolean useConnectionService_ = supportsConnectionService;
@@ -149,16 +148,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
audioManager = (AudioManager)reactContext.getSystemService(Context.AUDIO_SERVICE);
}
@ReactMethod
public void addListener(String eventName) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
public void removeListeners(Integer count) {
// Keep: Required for RN built in Event Emitter Calls.
}
/**
* Gets a mapping with the constants this module is exporting.
*

View File

@@ -0,0 +1,226 @@
/*
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.ReadableMap;
import com.rnimmersive.RNImmersiveModule;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
/**
* Base class for all views which are backed by a React Native view.
*/
public abstract class BaseReactView<ListenerT>
extends FrameLayout {
/**
* Background color used by {@code BaseReactView} and the React Native root
* view.
*/
protected static int BACKGROUND_COLOR = 0xFF111111;
/**
* The collection of all existing {@code BaseReactView}s. Used to find the
* {@code BaseReactView} when delivering events coming from
* {@link ExternalAPIModule}.
*/
static final Set<BaseReactView> views
= Collections.newSetFromMap(new WeakHashMap<BaseReactView, Boolean>());
/**
* Finds a {@code BaseReactView} which matches a specific external API
* scope.
*
* @param externalAPIScope - The external API scope associated with the
* {@code BaseReactView} to find.
* @return The {@code BaseReactView}, if any, associated with the specified
* {@code externalAPIScope}; otherwise, {@code null}.
*/
public static BaseReactView findViewByExternalAPIScope(
String externalAPIScope) {
synchronized (views) {
for (BaseReactView view : views) {
if (view.externalAPIScope.equals(externalAPIScope)) {
return view;
}
}
}
return null;
}
/**
* Gets all registered React views.
*
* @return An {@link ArrayList} containing all views currently held by React.
*/
static ArrayList<BaseReactView> getViews() {
return new ArrayList<>(views);
}
/**
* The unique identifier of this {@code BaseReactView} within the process
* for the purposes of {@link ExternalAPIModule}. The name scope was
* inspired by postis which we use on Web for the similar purposes of the
* iframe-based external API.
*/
protected final String externalAPIScope;
/**
* The listener (e.g. {@link JitsiMeetViewListener}) instance for reporting
* events occurring in Jitsi Meet.
*/
@Deprecated
private ListenerT listener;
/**
* React Native root view.
*/
private ReactRootView reactRootView;
public BaseReactView(@NonNull Context context) {
super(context);
setBackgroundColor(BACKGROUND_COLOR);
ReactInstanceManagerHolder.initReactInstanceManager((Activity)context);
// Hook this BaseReactView into ExternalAPI.
externalAPIScope = UUID.randomUUID().toString();
synchronized (views) {
views.add(this);
}
}
/**
* Creates the {@code ReactRootView} for the given app name with the given
* props. Once created it's set as the view of this {@code FrameLayout}.
*
* @param appName - The name of the "app" (in React Native terms) to load.
* @param props - The React Component props to pass to the app.
*/
public void createReactRootView(String appName, @Nullable Bundle props) {
if (props == null) {
props = new Bundle();
}
props.putString("externalAPIScope", externalAPIScope);
if (reactRootView == null) {
reactRootView = new ReactRootView(getContext());
reactRootView.startReactApplication(
ReactInstanceManagerHolder.getReactInstanceManager(),
appName,
props);
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
addView(reactRootView);
} else {
reactRootView.setAppProperties(props);
}
}
/**
* Releases the React resources (specifically the {@link ReactRootView})
* associated with this view.
*
* MUST be called when the {@link Activity} holding this view is destroyed,
* typically in the {@code onDestroy} method.
*/
public void dispose() {
if (reactRootView != null) {
removeView(reactRootView);
reactRootView.unmountReactApplication();
reactRootView = null;
}
}
/**
* Gets the listener set on this {@code BaseReactView}.
*
* @return The listener set on this {@code BaseReactView}.
*/
@Deprecated
public ListenerT getListener() {
return listener;
}
/**
* Abstract method called by {@link ExternalAPIModule} when an event is
* received for this view.
*
* @param name - The name of the event.
* @param data - The details of the event associated with/specific to the
* specified {@code name}.
*/
@Deprecated
protected abstract void onExternalAPIEvent(String name, ReadableMap data);
@Deprecated
protected void onExternalAPIEvent(
Map<String, Method> listenerMethods,
String name, ReadableMap data) {
ListenerT listener = getListener();
if (listener != null) {
ListenerUtils.runListenerMethod(
listener, listenerMethods, name, data);
}
}
/**
* Called when the window containing this view gains or loses focus.
*
* @param hasFocus If the window of this view now has focus, {@code true};
* otherwise, {@code false}.
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
if (hasFocus && immersive != null) {
immersive.emitImmersiveStateChangeEvent();
}
}
/**
* Sets a specific listener on this {@code BaseReactView}.
*
* @param listener The listener to set on this {@code BaseReactView}.
*/
@Deprecated
public void setListener(ListenerT listener) {
this.listener = listener;
}
}

View File

@@ -76,8 +76,7 @@ public class BroadcastAction {
OPEN_CHAT("org.jitsi.meet.OPEN_CHAT"),
CLOSE_CHAT("org.jitsi.meet.CLOSE_CHAT"),
SEND_CHAT_MESSAGE("org.jitsi.meet.SEND_CHAT_MESSAGE"),
SET_VIDEO_MUTED("org.jitsi.meet.SET_VIDEO_MUTED"),
SET_CLOSED_CAPTIONS_ENABLED("org.jitsi.meet.SET_CLOSED_CAPTIONS_ENABLED");
SET_VIDEO_MUTED("org.jitsi.meet.SET_VIDEO_MUTED");
private final String action;

View File

@@ -48,10 +48,4 @@ public class BroadcastIntentHelper {
intent.putExtra("muted", muted);
return intent;
}
public static Intent buildSetClosedCaptionsEnabledIntent(boolean enabled) {
Intent intent = new Intent(BroadcastAction.Type.SET_CLOSED_CAPTIONS_ENABLED.getAction());
intent.putExtra("enabled", enabled);
return intent;
}
}

View File

@@ -31,7 +31,8 @@ import java.util.Map;
* Module implementing an API for sending events from JavaScript to native code.
*/
@ReactModule(name = ExternalAPIModule.NAME)
class ExternalAPIModule extends ReactContextBaseJavaModule {
class ExternalAPIModule
extends ReactContextBaseJavaModule {
public static final String NAME = "ExternalAPI";
@@ -56,16 +57,6 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
ParticipantsService.init(reactContext);
}
@ReactMethod
public void addListener(String eventName) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
public void removeListeners(Integer count) {
// Keep: Required for RN built in Event Emitter Calls.
}
/**
* Gets the name of this module to be used in the React Native bridge.
*
@@ -95,25 +86,37 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
constants.put("CLOSE_CHAT", BroadcastAction.Type.CLOSE_CHAT.getAction());
constants.put("SEND_CHAT_MESSAGE", BroadcastAction.Type.SEND_CHAT_MESSAGE.getAction());
constants.put("SET_VIDEO_MUTED", BroadcastAction.Type.SET_VIDEO_MUTED.getAction());
constants.put("SET_CLOSED_CAPTIONS_ENABLED", BroadcastAction.Type.SET_CLOSED_CAPTIONS_ENABLED.getAction());
return constants;
}
/**
* Dispatches an event that occurred on the JavaScript side of the SDK to
* the native side.
* the specified {@link BaseReactView}'s listener.
*
* @param name The name of the event.
* @param data The details/specifics of the event to send determined
* by/associated with the specified {@code name}.
* @param scope
*/
@ReactMethod
public void sendEvent(String name, ReadableMap data) {
public void sendEvent(String name, ReadableMap data, String scope) {
// Keep track of the current ongoing conference.
OngoingConferenceTracker.getInstance().onExternalAPIEvent(name, data);
JitsiMeetLogger.d(TAG + " Sending event: " + name + " with data: " + data);
broadcastEmitter.sendBroadcast(name, data);
// The JavaScript App needs to provide uniquely identifying information
// to the native ExternalAPI module so that the latter may match the
// former to the native BaseReactView which hosts it.
BaseReactView view = BaseReactView.findViewByExternalAPIScope(scope);
if (view != null) {
JitsiMeetLogger.d(TAG + " Sending event: " + name + " with data: " + data);
try {
view.onExternalAPIEvent(name, data);
broadcastEmitter.sendBroadcast(name, data);
} catch (Exception e) {
JitsiMeetLogger.e(e, TAG + " onExternalAPIEvent: error sending event");
}
}
}
}

View File

@@ -15,17 +15,14 @@
*/
package org.jitsi.meet.sdk;
import android.app.Application;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.startup.Initializer;
import com.facebook.soloader.SoLoader;
import org.wonday.orientation.OrientationActivityLifecycle;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
public class JitsiInitializer implements Initializer<Boolean> {
@@ -33,22 +30,13 @@ public class JitsiInitializer implements Initializer<Boolean> {
@NonNull
@Override
public Boolean create(@NonNull Context context) {
Log.d(this.getClass().getCanonicalName(), "create");
SoLoader.init(context, /* native exopackage */ false);
// Register our uncaught exception handler.
JitsiMeetUncaughtExceptionHandler.register();
// Register activity lifecycle handler for the orientation locker module.
((Application) context).registerActivityLifecycleCallbacks(OrientationActivityLifecycle.getInstance());
return true;
}
@NonNull
@Override
public List<Class<? extends Initializer<?>>> dependencies() {
return Collections.emptyList();
return new ArrayList<>();
}
}

View File

@@ -16,12 +16,10 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
@@ -34,16 +32,11 @@ import com.facebook.react.modules.core.PermissionListener;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.HashMap;
import android.app.Activity;
/**
* A base activity for SDK users to embed. It contains all the required wiring
* between the {@code JitsiMeetView} and the Activity lifecycle methods.
*
* In this activity we use a single {@code JitsiMeetView} instance. This
* instance gives us access to a view which displays the welcome page and the
* conference itself. All lifecycle methods associated with this Activity are
* hooked to the React Native subsystem via proxy calls through the
* {@code JitsiMeetActivityDelegate} static methods.
* A base activity for SDK users to embed. It uses {@link JitsiMeetFragment} to do the heavy
* lifting and wires the remaining Activity lifecycle methods so it works out of the box.
*/
public class JitsiMeetActivity extends AppCompatActivity
implements JitsiMeetActivityInterface {
@@ -59,12 +52,6 @@ public class JitsiMeetActivity extends AppCompatActivity
onBroadcastReceived(intent);
}
};
/**
* Instance of the {@link JitsiMeetView} which this activity will display.
*/
private JitsiMeetView jitsiView;
// Helpers for starting the activity
//
@@ -87,20 +74,11 @@ public class JitsiMeetActivity extends AppCompatActivity
// Overrides
//
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
this.sendBroadcast(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jitsi_meet);
this.jitsiView = findViewById(R.id.jitsiView);
registerForBroadcastMessages();
@@ -109,18 +87,6 @@ public class JitsiMeetActivity extends AppCompatActivity
}
}
@Override
public void onResume() {
super.onResume();
JitsiMeetActivityDelegate.onHostResume(this);
}
@Override
public void onStop() {
JitsiMeetActivityDelegate.onHostPause(this);
super.onStop();
}
@Override
public void onDestroy() {
// Here we are trying to handle the following corner case: an application using the SDK
@@ -131,9 +97,6 @@ public class JitsiMeetActivity extends AppCompatActivity
// be operational so the external API won't be able to notify the native side that the
// conference terminated. Thus, try our best to clean up.
leave();
this.jitsiView = null;
if (AudioModeModule.useConnectionService()) {
ConnectionService.abortConnections();
}
@@ -141,8 +104,6 @@ public class JitsiMeetActivity extends AppCompatActivity
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
JitsiMeetActivityDelegate.onHostDestroy(this);
super.onDestroy();
}
@@ -157,7 +118,9 @@ public class JitsiMeetActivity extends AppCompatActivity
//
protected JitsiMeetView getJitsiView() {
return jitsiView;
JitsiMeetFragment fragment
= (JitsiMeetFragment) getSupportFragmentManager().findFragmentById(R.id.jitsiFragment);
return fragment != null ? fragment.getJitsiView() : null;
}
public void join(@Nullable String url) {
@@ -169,16 +132,23 @@ public class JitsiMeetActivity extends AppCompatActivity
}
public void join(JitsiMeetConferenceOptions options) {
if (this.jitsiView != null) {
this.jitsiView .join(options);
JitsiMeetView view = getJitsiView();
if (view != null) {
view.join(options);
} else {
JitsiMeetLogger.w("Cannot join, view is null");
}
}
protected void leave() {
Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
public void leave() {
JitsiMeetView view = getJitsiView();
if (view != null) {
view.leave();
} else {
JitsiMeetLogger.w("Cannot leave, view is null");
}
}
private @Nullable
@@ -219,7 +189,7 @@ public class JitsiMeetActivity extends AppCompatActivity
protected void onConferenceJoined(HashMap<String, Object> extraData) {
JitsiMeetLogger.i("Conference joined: " + extraData);
// Launch the service for the ongoing notification.
JitsiMeetOngoingConferenceService.launch(this, extraData);
JitsiMeetOngoingConferenceService.launch(this);
}
protected void onConferenceTerminated(HashMap<String, Object> extraData) {
@@ -282,8 +252,10 @@ public class JitsiMeetActivity extends AppCompatActivity
@Override
protected void onUserLeaveHint() {
if (this.jitsiView != null) {
this.jitsiView .enterPictureInPicture();
JitsiMeetView view = getJitsiView();
if (view != null) {
view.enterPictureInPicture();
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright @ 2019-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* Base {@link Fragment} for applications integrating Jitsi Meet at a higher level. It
* contains all the required wiring between the {@code JitsiMeetView} and
* the Fragment lifecycle methods already implemented.
*
* In this fragment we use a single {@code JitsiMeetView} instance. This
* instance gives us access to a view which displays the welcome page and the
* conference itself. All lifecycle methods associated with this Fragment are
* hooked to the React Native subsystem via proxy calls through the
* {@code JitsiMeetActivityDelegate} static methods.
*/
public class JitsiMeetFragment extends Fragment {
/**
* Instance of the {@link JitsiMeetView} which this activity will display.
*/
private JitsiMeetView view;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return this.view = new JitsiMeetView(getActivity());
}
public JitsiMeetView getJitsiView() {
return view;
}
@Override
public void onDestroy() {
super.onDestroy();
JitsiMeetActivityDelegate.onHostDestroy(getActivity());
}
@Override
public void onResume() {
super.onResume();
JitsiMeetActivityDelegate.onHostResume(getActivity());
}
@Override
public void onStop() {
super.onStop();
JitsiMeetActivityDelegate.onHostPause(getActivity());
}
}

View File

@@ -17,22 +17,18 @@
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.HashMap;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
* responsible for presenting an ongoing notification when a conference is in progress.
@@ -43,44 +39,29 @@ import java.util.HashMap;
public class JitsiMeetOngoingConferenceService extends Service
implements OngoingConferenceTracker.OngoingConferenceListener {
private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
private static final String EXTRA_DATA_KEY = "extraDataKey";
private static final String EXTRA_DATA_BUNDLE_KEY = "extraDataBundleKey";
private static final String IS_AUDIO_MUTED_KEY = "isAudioMuted";
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver();
private boolean isAudioMuted;
public static void launch(Context context, HashMap<String, Object> extraData) {
static void launch(Context context) {
OngoingNotification.createOngoingConferenceNotificationChannel();
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
Bundle extraDataBundle = new Bundle();
extraDataBundle.putSerializable(EXTRA_DATA_KEY, extraData);
intent.putExtra(EXTRA_DATA_BUNDLE_KEY, extraDataBundle);
intent.setAction(Action.START.getName());
ComponentName componentName;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
componentName = context.startForegroundService(intent);
} else {
componentName = context.startService(intent);
}
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
componentName = context.startForegroundService(intent);
} else {
componentName = context.startService(intent);
}
if (componentName == null) {
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
}
}
public static void abort(Context context) {
static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
context.stopService(intent);
}
@@ -89,15 +70,6 @@ public class JitsiMeetOngoingConferenceService extends Service
public void onCreate() {
super.onCreate();
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
}
OngoingConferenceTracker.getInstance().addListener(this);
IntentFilter intentFilter = new IntentFilter();
@@ -120,45 +92,37 @@ public class JitsiMeetOngoingConferenceService extends Service
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Boolean isAudioMuted = tryParseIsAudioMuted(intent);
if (isAudioMuted != null) {
this.isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
}
}
final String actionName = intent.getAction();
final Action action = Action.fromName(actionName);
// When starting the service, there is no action passed in the intent
if (action != null) {
switch (action) {
case UNMUTE:
case MUTE:
Intent muteBroadcastIntent = BroadcastIntentHelper.buildSetAudioMutedIntent(action == Action.MUTE);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(muteBroadcastIntent);
break;
case HANGUP:
JitsiMeetLogger.i(TAG + " Hangup requested");
Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
switch (action) {
case UNMUTE:
case MUTE:
Intent muteBroadcastIntent = BroadcastIntentHelper.buildSetAudioMutedIntent(action == Action.MUTE);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(muteBroadcastIntent);
break;
case START:
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
if (notification == null) {
stopSelf();
break;
default:
JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
break;
}
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
}
break;
case HANGUP:
JitsiMeetLogger.i(TAG + " Hangup requested");
Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
stopSelf();
break;
default:
JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
stopSelf();
break;
}
return START_NOT_STICKY;
@@ -174,6 +138,7 @@ public class JitsiMeetOngoingConferenceService extends Service
}
public enum Action {
START(TAG + ":START"),
HANGUP(TAG + ":HANGUP"),
MUTE(TAG + ":MUTE"),
UNMUTE(TAG + ":UNMUTE");
@@ -198,15 +163,6 @@ public class JitsiMeetOngoingConferenceService extends Service
}
}
private Boolean tryParseIsAudioMuted(Intent intent) {
try {
HashMap<String, Object> extraData = (HashMap<String, Object>) intent.getBundleExtra(EXTRA_DATA_BUNDLE_KEY).getSerializable(EXTRA_DATA_KEY);
return Boolean.parseBoolean((String) extraData.get(IS_AUDIO_MUTED_KEY));
} catch (Exception ignored) {
}
return null;
}
private class BroadcastReceiver extends android.content.BroadcastReceiver {
@Override
@@ -215,12 +171,10 @@ public class JitsiMeetOngoingConferenceService extends Service
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't update service, notification is null");
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " audio muted changed");
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
}
}
}

View File

@@ -16,33 +16,35 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.ReactRootView;
import com.rnimmersive.RNImmersiveModule;
import com.facebook.react.bridge.ReadableMap;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.lang.reflect.Method;
import java.util.Map;
public class JitsiMeetView extends FrameLayout {
public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
implements OngoingConferenceTracker.OngoingConferenceListener {
/**
* Background color used by {@code BaseReactView} and the React Native root
* view.
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
* redux action types.
*/
private static final int BACKGROUND_COLOR = 0xFF111111;
private static final Map<String, Method> LISTENER_METHODS
= ListenerUtils.mapListenerMethods(JitsiMeetViewListener.class);
/**
* React Native root view.
* The URL of the current conference.
*/
private ReactRootView reactRootView;
// XXX Currently, one thread writes and one thread reads, so it should be
// fine to have this field volatile without additional synchronization.
private volatile String url;
/**
* Helper method to recursively merge 2 {@link Bundle} objects representing React Native props.
@@ -93,32 +95,20 @@ public class JitsiMeetView extends FrameLayout {
public JitsiMeetView(@NonNull Context context) {
super(context);
initialize(context);
}
public JitsiMeetView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context);
}
public JitsiMeetView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(context);
}
/**
* Releases the React resources (specifically the {@link ReactRootView})
* associated with this view.
*
* MUST be called when the {@link Activity} holding this view is destroyed,
* typically in the {@code onDestroy} method.
*/
public void dispose() {
if (reactRootView != null) {
removeView(reactRootView);
reactRootView.unmountReactApplication();
reactRootView = null;
// Check if the parent Activity implements JitsiMeetActivityInterface,
// otherwise things may go wrong.
if (!(context instanceof JitsiMeetActivityInterface)) {
throw new RuntimeException("Enclosing Activity must implement JitsiMeetActivityInterface");
}
OngoingConferenceTracker.getInstance().addListener(this);
}
@Override
public void dispose() {
OngoingConferenceTracker.getInstance().removeListener(this);
super.dispose();
}
/**
@@ -136,7 +126,8 @@ public class JitsiMeetView extends FrameLayout {
PictureInPictureModule.class);
if (pipModule != null
&& pipModule.isPictureInPictureSupported()
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()) {
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()
&& this.url != null) {
try {
pipModule.enterPictureInPicture();
} catch (RuntimeException re) {
@@ -156,40 +147,10 @@ public class JitsiMeetView extends FrameLayout {
}
/**
* Creates the {@code ReactRootView} for the given app name with the given
* props. Once created it's set as the view of this {@code FrameLayout}.
*
* @param appName - The name of the "app" (in React Native terms) to load.
* @param props - The React Component props to pass to the app.
* Leaves the currently active conference.
*/
private void createReactRootView(String appName, @Nullable Bundle props) {
if (props == null) {
props = new Bundle();
}
if (reactRootView == null) {
reactRootView = new ReactRootView(getContext());
reactRootView.startReactApplication(
ReactInstanceManagerHolder.getReactInstanceManager(),
appName,
props);
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
addView(reactRootView);
} else {
reactRootView.setAppProperties(props);
}
}
private void initialize(@NonNull Context context) {
// Check if the parent Activity implements JitsiMeetActivityInterface,
// otherwise things may go wrong.
if (!(context instanceof JitsiMeetActivityInterface)) {
throw new RuntimeException("Enclosing Activity must implement JitsiMeetActivityInterface");
}
setBackgroundColor(BACKGROUND_COLOR);
ReactInstanceManagerHolder.initReactInstanceManager((Activity) context);
public void leave() {
setProps(new Bundle());
}
/**
@@ -206,7 +167,7 @@ public class JitsiMeetView extends FrameLayout {
// by leaving the conference. However, React and, respectively,
// appProperties/initialProperties are declarative expressions i.e. one
// and the same URL will not trigger an automatic re-render in the
// JavaScript source code. The workaround implemented below introduces
// JavaScript source code. The workaround implemented bellow introduces
// "imperativeness" in React Component props by defining a unique value
// per setProps() invocation.
props.putLong("timestamp", System.currentTimeMillis());
@@ -214,27 +175,36 @@ public class JitsiMeetView extends FrameLayout {
createReactRootView("App", props);
}
/**
* Handler for {@link OngoingConferenceTracker} events.
* @param conferenceUrl
*/
@Override
public void onCurrentConferenceChanged(String conferenceUrl) {
// This property was introduced in order to address
// an exception in the Picture-in-Picture functionality which arose
// because of delays related to bridging between JavaScript and Java. To
// reduce these delays do not wait for the call to be transferred to the
// UI thread.
this.url = conferenceUrl;
}
/**
* Handler for {@link ExternalAPIModule} events.
*
* @param name The name of the event.
* @param data The details/specifics of the event to send determined
* by/associated with the specified {@code name}.
*/
@Override
@Deprecated
protected void onExternalAPIEvent(String name, ReadableMap data) {
onExternalAPIEvent(LISTENER_METHODS, name, data);
}
@Override
protected void onDetachedFromWindow() {
dispose();
super.onDetachedFromWindow();
}
/**
* Called when the window containing this view gains or loses focus.
*
* @param hasFocus If the window of this view now has focus, {@code true};
* otherwise, {@code false}.
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
if (hasFocus && immersive != null) {
immersive.emitImmersiveStateChangeEvent();
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import java.util.Map;
/**
* Interface for listening to events coming from Jitsi Meet.
*/
@Deprecated
public interface JitsiMeetViewListener {
/**
* Called when a conference was joined.
*
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceJoined(Map<String, Object> data);
/**
* Called when the active conference ends, be it because of user choice or
* because of a failure.
*
* @param data Map with an "error" key with the error and a "url" key with
* the conference URL. If the conference finished gracefully no `error`
* key will be present. The possible values for "error" are described here:
* https://github.com/jitsi/lib-jitsi-meet/blob/master/JitsiConnectionErrors.js
* https://github.com/jitsi/lib-jitsi-meet/blob/master/JitsiConferenceErrors.js
*/
void onConferenceTerminated(Map<String, Object> data);
/**
* Called before the conference is joined.
*
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceWillJoin(Map<String, Object> data);
}

View File

@@ -1,41 +0,0 @@
package org.jitsi.meet.sdk;
import android.app.Application;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import java.util.List;
/**
* This is the minimal implementation of ReactNativeHost that will make things like the
* Detox testing framework believe we are a "greenfield" app.
*
* Generally speaking, apps using the SDK (other than the Jitsi Meet app itself) should not
* need to use this because the
*/
public class JitsiReactNativeHost extends ReactNativeHost {
public JitsiReactNativeHost(Application application) {
super(application);
}
@Override
public boolean getUseDeveloperSupport() {
// Unused since we override `createReactInstanceManager`.
return false;
}
@Override
protected List<ReactPackage> getPackages() {
// Unused since we override `createReactInstanceManager`.
return null;
}
@Override
protected ReactInstanceManager createReactInstanceManager() {
ReactInstanceManagerHolder.initReactInstanceManager(this.getApplication());
return ReactInstanceManagerHolder.getReactInstanceManager();
}
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright @ 2018-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.UiThreadUtil;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Utility methods for helping with transforming {@link ExternalAPIModule}
* events into listener methods. Used with descendants of {@link BaseReactView}.
*/
@Deprecated
public final class ListenerUtils {
/**
* Extracts the methods defined in a listener and creates a mapping of this
* form: event name -> method.
*
* @param listener - The listener whose methods we want to slurp.
* @return A mapping with event names - methods.
*/
public static Map<String, Method> mapListenerMethods(Class listener) {
Map<String, Method> methods = new HashMap<>();
// Figure out the mapping between the listener methods
// and the events i.e. redux action types.
Pattern onPattern = Pattern.compile("^on[A-Z]+");
Pattern camelcasePattern = Pattern.compile("([a-z0-9]+)([A-Z0-9]+)");
for (Method method : listener.getDeclaredMethods()) {
// * The method must be public (because it is declared by an
// interface).
// * The method must be/return void.
if (!Modifier.isPublic(method.getModifiers())
|| !Void.TYPE.equals(method.getReturnType())) {
continue;
}
// * The method name must start with "on" followed by a
// capital/uppercase letter (in agreement with the camelcase
// coding style customary to Java in general and the projects of
// the Jitsi community in particular).
String name = method.getName();
if (!onPattern.matcher(name).find()) {
continue;
}
// * The method must accept/have exactly 1 parameter of a type
// assignable from HashMap.
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1
|| !parameterTypes[0].isAssignableFrom(HashMap.class)) {
continue;
}
// Convert the method name to an event name.
name
= camelcasePattern.matcher(name.substring(2))
.replaceAll("$1_$2")
.toUpperCase(Locale.ROOT);
methods.put(name, method);
}
return methods;
}
/**
* Executes the right listener method for the given event.
* NOTE: This function will run asynchronously on the UI thread.
*
* @param listener - The listener on which the method will be called.
* @param listenerMethods - Mapping with event names and the matching
* methods.
* @param eventName - Name of the event.
* @param eventData - Data associated with the event.
*/
public static void runListenerMethod(
final Object listener,
final Map<String, Method> listenerMethods,
final String eventName,
final ReadableMap eventData) {
// Make sure listener methods are invoked on the UI thread. It
// was requested by SDK consumers.
if (UiThreadUtil.isOnUiThread()) {
runListenerMethodOnUiThread(
listener, listenerMethods, eventName, eventData);
} else {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
runListenerMethodOnUiThread(
listener, listenerMethods, eventName, eventData);
}
});
}
}
/**
* Helper companion for {@link ListenerUtils#runListenerMethod} which runs
* in the UI thread.
*/
private static void runListenerMethodOnUiThread(
Object listener,
Map<String, Method> listenerMethods,
String eventName,
ReadableMap eventData) {
UiThreadUtil.assertOnUiThread();
Method method = listenerMethods.get(eventName);
if (method != null) {
try {
method.invoke(listener, toHashMap(eventData));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
/**
* Initializes a new {@code HashMap} instance with the key-value
* associations of a specific {@code ReadableMap}.
*
* @param readableMap the {@code ReadableMap} specifying the key-value
* associations with which the new {@code HashMap} instance is to be
* initialized.
* @return a new {@code HashMap} instance initialized with the key-value
* associations of the specified {@code readableMap}.
*/
private static HashMap<String, Object> toHashMap(ReadableMap readableMap) {
HashMap<String, Object> hashMap = new HashMap<>();
for (ReadableMapKeySetIterator i = readableMap.keySetIterator();
i.hasNextKey();) {
String key = i.nextKey();
hashMap.put(key, readableMap.getString(key));
}
return hashMap;
}
}

View File

@@ -43,7 +43,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
private static final String TAG = NAME;
private static boolean isSupported;
private boolean isEnabled;
private boolean isDisabled;
public PictureInPictureModule(ReactApplicationContext reactContext) {
super(reactContext);
@@ -84,7 +84,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
*/
@TargetApi(Build.VERSION_CODES.O)
public void enterPictureInPicture() {
if (!isEnabled) {
if (isDisabled) {
return;
}
@@ -132,8 +132,8 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
}
@ReactMethod
public void setPictureInPictureEnabled(Boolean enabled) {
this.isEnabled = enabled;
public void setPictureInPictureDisabled(Boolean disabled) {
this.isDisabled = disabled;
}
public boolean isPictureInPictureSupported() {

View File

@@ -59,16 +59,6 @@ class RNConnectionService extends ReactContextBaseJavaModule {
super(reactContext);
}
@ReactMethod
public void addListener(String eventName) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
public void removeListeners(Integer count) {
// Keep: Required for RN built in Event Emitter Calls.
}
/**
* Starts a new outgoing call.
*
@@ -193,7 +183,7 @@ class RNConnectionService extends ReactContextBaseJavaModule {
* Called by the JS side to update the call's state.
*
* @param callUUID - the call's UUID.
* @param callState - the map which carries info about the current call's
* @param callState - the map which carries infor about the current call's
* state. See static fields in {@link ConnectionService.ConnectionImpl}
* prefixed with "KEY_" for the values supported by the Android
* implementation.

View File

@@ -17,8 +17,6 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.Application;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -28,6 +26,7 @@ import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.devsupport.DevInternalSettings;
import com.facebook.react.jscexecutor.JSCExecutorFactory;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.ViewManager;
@@ -46,8 +45,6 @@ import java.util.Arrays;
import java.util.List;
class ReactInstanceManagerHolder {
private static final String TAG = ReactInstanceManagerHolder.class.getSimpleName();
/**
* FIXME (from linter): Do not place Android context classes in static
* fields (static reference to ReactInstanceManager which has field
@@ -85,7 +82,6 @@ class ReactInstanceManagerHolder {
WebRTCModule.Options options = new WebRTCModule.Options();
AudioDeviceModule adm = JavaAudioDeviceModule.builder(reactContext)
.setEnableVolumeLogger(false)
.createAudioDeviceModule();
options.setAudioDeviceModule(adm);
@@ -104,81 +100,6 @@ class ReactInstanceManagerHolder {
);
}
static List<ReactPackage> getReactNativePackages() {
List<ReactPackage> packages
= new ArrayList<>(Arrays.asList(
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
new com.ocetnik.timer.BackgroundTimerPackage(),
new com.calendarevents.RNCalendarEventsPackage(),
new com.corbt.keepawake.KCKeepAwakePackage(),
new com.facebook.react.shell.MainReactPackage(),
new com.reactnativecommunity.clipboard.ClipboardPackage(),
new com.reactnativecommunity.netinfo.NetInfoPackage(),
new com.reactnativepagerview.PagerViewPackage(),
new com.oblador.performance.PerformancePackage(),
new com.reactnativecommunity.slider.ReactSliderPackage(),
new com.brentvatne.react.ReactVideoPackage(),
new org.reactnative.maskedview.RNCMaskedViewPackage(),
new com.reactnativecommunity.webview.RNCWebViewPackage(),
new com.kevinresol.react_native_default_preference.RNDefaultPreferencePackage(),
new com.learnium.RNDeviceInfo.RNDeviceInfo(),
new com.swmansion.gesturehandler.react.RNGestureHandlerPackage(),
new org.linusu.RNGetRandomValuesPackage(),
new com.rnimmersive.RNImmersivePackage(),
new com.swmansion.rnscreens.RNScreensPackage(),
new com.zmxv.RNSound.RNSoundPackage(),
new com.th3rdwave.safeareacontext.SafeAreaContextPackage(),
new com.horcrux.svg.SvgPackage(),
new org.wonday.orientation.OrientationPackage(),
new ReactPackageAdapter() {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return ReactInstanceManagerHolder.createNativeModules(reactContext);
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return ReactInstanceManagerHolder.createViewManagers(reactContext);
}
}));
// AmplitudeReactNativePackage
try {
Class<?> amplitudePackageClass = Class.forName("com.amplitude.reactnative.AmplitudeReactNativePackage");
Constructor constructor = amplitudePackageClass.getConstructor();
packages.add((ReactPackage)constructor.newInstance());
} catch (Exception e) {
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
Log.d(TAG, "Not loading AmplitudeReactNativePackage");
}
// GiphyReactNativeSdkPackage
try {
Class<?> giphyPackageClass = Class.forName("com.giphyreactnativesdk.GiphyReactNativeSdkPackage");
Constructor constructor = giphyPackageClass.getConstructor();
packages.add((ReactPackage)constructor.newInstance());
} catch (Exception e) {
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
Log.d(TAG, "Not loading GiphyReactNativeSdkPackage");
}
// RNGoogleSignInPackage
try {
Class<?> googlePackageClass = Class.forName("com.reactnativegooglesignin.RNGoogleSigninPackage");
Constructor constructor = googlePackageClass.getConstructor();
packages.add((ReactPackage)constructor.newInstance());
} catch (Exception e) {
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
Log.d(TAG, "Not loading RNGoogleSignInPackage");
}
return packages;
}
static JSCExecutorFactory getReactNativeJSFactory() {
// Keep on using JSC, the jury is out on Hermes.
return new JSCExecutorFactory("", "");
}
/**
* Helper function to send an event to JavaScript.
*
@@ -239,35 +160,6 @@ class ReactInstanceManagerHolder {
return reactInstanceManager;
}
/**
* Internal method to initialize the React Native instance manager. We
* create a single instance in order to load the JavaScript bundle a single
* time. All {@code ReactRootView} instances will be tied to the one and
* only {@code ReactInstanceManager}.
*
* This method is only meant to be called when integrating with {@code JitsiReactNativeHost}.
*
* @param app {@code Application} current running Application.
*/
static void initReactInstanceManager(Application app) {
if (reactInstanceManager != null) {
return;
}
Log.d(TAG, "initializing RN with Application");
reactInstanceManager
= ReactInstanceManager.builder()
.setApplication(app)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index.android")
.setJavaScriptExecutorFactory(getReactNativeJSFactory())
.addPackages(getReactNativePackages())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
.build();
}
/**
* Internal method to initialize the React Native instance manager. We
* create a single instance in order to load the JavaScript bundle a single
@@ -281,7 +173,62 @@ class ReactInstanceManagerHolder {
return;
}
Log.d(ReactInstanceManagerHolder.class.getCanonicalName(), "initializing RN with Activity");
List<ReactPackage> packages
= new ArrayList<>(Arrays.asList(
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
new com.ocetnik.timer.BackgroundTimerPackage(),
new com.calendarevents.CalendarEventsPackage(),
new com.corbt.keepawake.KCKeepAwakePackage(),
new com.facebook.react.shell.MainReactPackage(),
new com.reactnativecommunity.clipboard.ClipboardPackage(),
new com.reactnativecommunity.netinfo.NetInfoPackage(),
new com.oblador.performance.PerformancePackage(),
new com.reactnativecommunity.slider.ReactSliderPackage(),
new com.brentvatne.react.ReactVideoPackage(),
new com.swmansion.reanimated.ReanimatedPackage(),
new org.reactnative.maskedview.RNCMaskedViewPackage(),
new com.reactnativecommunity.webview.RNCWebViewPackage(),
new com.kevinresol.react_native_default_preference.RNDefaultPreferencePackage(),
new com.learnium.RNDeviceInfo.RNDeviceInfo(),
new com.swmansion.gesturehandler.react.RNGestureHandlerPackage(),
new org.linusu.RNGetRandomValuesPackage(),
new com.rnimmersive.RNImmersivePackage(),
new com.swmansion.rnscreens.RNScreensPackage(),
new com.zmxv.RNSound.RNSoundPackage(),
new com.th3rdwave.safeareacontext.SafeAreaContextPackage(),
new com.horcrux.svg.SvgPackage(),
new ReactPackageAdapter() {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return ReactInstanceManagerHolder.createNativeModules(reactContext);
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return ReactInstanceManagerHolder.createViewManagers(reactContext);
}
}));
// AmplitudeReactNativePackage
try {
Class<?> amplitudePackageClass = Class.forName("com.amplitude.reactnative.AmplitudeReactNativePackage");
Constructor constructor = amplitudePackageClass.getConstructor();
packages.add((ReactPackage)constructor.newInstance());
} catch (Exception e) {
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
}
// RNGoogleSigninPackage
try {
Class<?> googlePackageClass = Class.forName("co.apptailor.googlesignin.RNGoogleSigninPackage");
Constructor constructor = googlePackageClass.getConstructor();
packages.add((ReactPackage)constructor.newInstance());
} catch (Exception e) {
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
}
// Keep on using JSC, the jury is out on Hermes.
JSCExecutorFactory jsFactory
= new JSCExecutorFactory("", "");
reactInstanceManager
= ReactInstanceManager.builder()
@@ -289,10 +236,20 @@ class ReactInstanceManagerHolder {
.setCurrentActivity(activity)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index.android")
.setJavaScriptExecutorFactory(getReactNativeJSFactory())
.addPackages(getReactNativePackages())
.setJavaScriptExecutorFactory(jsFactory)
.addPackages(packages)
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// Disable delta updates on Android, they have caused trouble.
DevInternalSettings devSettings
= (DevInternalSettings)reactInstanceManager.getDevSupportManager().getDevSettings();
if (devSettings != null) {
devSettings.setBundleDeltasEnabled(false);
}
// Register our uncaught exception handler.
JitsiMeetUncaughtExceptionHandler.register();
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright @ 2018-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk.incoming_call;
import androidx.annotation.NonNull;
public class IncomingCallInfo {
/**
* URL for the caller avatar.
*/
private final String callerAvatarURL;
/**
* Caller's name.
*/
private final String callerName;
/**
* Whether this is a regular call or a video call.
*/
private final boolean hasVideo;
public IncomingCallInfo(
@NonNull String callerName,
@NonNull String callerAvatarURL,
boolean hasVideo) {
this.callerName = callerName;
this.callerAvatarURL = callerAvatarURL;
this.hasVideo = hasVideo;
}
/**
* Gets the caller's avatar URL.
*
* @return - The URL as a string.
*/
public String getCallerAvatarURL() {
return callerAvatarURL;
}
/**
* Gets the caller's name.
*
* @return - The caller's name.
*/
public String getCallerName() {
return callerName;
}
/**
* Gets whether the call is a video call or not.
*
* @return - {@code true} if this call has video; {@code false}, otherwise.
*/
public boolean hasVideo() {
return hasVideo;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright @ 2018-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk.incoming_call;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReadableMap;
import org.jitsi.meet.sdk.BaseReactView;
import org.jitsi.meet.sdk.ListenerUtils;
import java.lang.reflect.Method;
import java.util.Map;
public class IncomingCallView
extends BaseReactView<IncomingCallViewListener> {
/**
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
* redux action types.
*/
private static final Map<String, Method> LISTENER_METHODS
= ListenerUtils.mapListenerMethods(IncomingCallViewListener.class);
public IncomingCallView(@NonNull Context context) {
super(context);
}
/**
* Handler for {@link ExternalAPIModule} events.
*
* @param name The name of the event.
* @param data The details/specifics of the event to send determined
* by/associated with the specified {@code name}.
*/
@Override
protected void onExternalAPIEvent(String name, ReadableMap data) {
onExternalAPIEvent(LISTENER_METHODS, name, data);
}
/**
* Sets the information for the incoming call this {@code IncomingCallView}
* represents.
*
* @param callInfo - {@link IncomingCallInfo} object representing the caller
* information.
*/
public void setIncomingCallInfo(IncomingCallInfo callInfo) {
Bundle props = new Bundle();
props.putString("callerAvatarURL", callInfo.getCallerAvatarURL());
props.putString("callerName", callInfo.getCallerName());
props.putBoolean("hasVideo", callInfo.hasVideo());
createReactRootView("IncomingCallApp", props);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright @ 2018-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk.incoming_call;
import java.util.Map;
/**
* Interface for listening to events coming from Jitsi Meet, related to
* {@link IncomingCallView}.
*/
public interface IncomingCallViewListener {
/**
* Called when the user presses the "Answer" button on the
* {@link IncomingCallView}.
*
* @param data - Unused at the moment.
*/
void onIncomingCallAnswered(Map<String, Object> data);
/**
* Called when the user presses the "Decline" button on the
* {@link IncomingCallView}.
*
* @param data - Unused at the moment.
*/
void onIncomingCallDeclined(Map<String, Object> data);
}

View File

@@ -1,13 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/jitsi_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".JitsiMeetActivity">
<org.jitsi.meet.sdk.JitsiMeetView
android:id="@+id/jitsiView"
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:name="org.jitsi.meet.sdk.JitsiMeetFragment"
android:id="@+id/jitsiFragment"/>
</FrameLayout>

View File

@@ -1,3 +0,0 @@
<resources>
<style name="JitsiMeetActivityStyle" parent="Theme.AppCompat.Light.NoActionBar"/>
</resources>

View File

@@ -1,8 +1,6 @@
rootProject.name = 'jitsi-meet'
include ':app', ':sdk'
includeBuild('../node_modules/react-native-gradle-plugin')
include ':react-native-amplitude'
project(':react-native-amplitude').projectDir = new File(rootProject.projectDir, '../node_modules/@amplitude/react-native//android')
include ':react-native-async-storage'
@@ -23,22 +21,18 @@ include ':react-native-gesture-handler'
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')
include ':react-native-get-random-values'
project(':react-native-get-random-values').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-get-random-values/android')
include ':react-native-giphy'
project(':react-native-giphy').projectDir = new File(rootProject.projectDir, '../node_modules/@giphy/react-native-sdk/android')
include ':react-native-google-signin'
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-google-signin/google-signin/android')
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/google-signin/android')
include ':react-native-immersive'
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
include ':react-native-keep-awake'
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
include ':react-native-masked-view_masked-view'
project(':react-native-masked-view_masked-view').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-masked-view/masked-view/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'
project(':react-native-pager-view').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-pager-view/android')
include ':react-native-performance'
project(':react-native-performance').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-performance/android')
include ':react-native-reanimated'
project(':react-native-reanimated').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-reanimated/android')
include ':react-native-safe-area-context'
project(':react-native-safe-area-context').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-safe-area-context/android')
include ':react-native-screens'

6
app.js
View File

@@ -1,10 +1,6 @@
/* application specific logic */
// Re-export jQuery
// FIXME: Remove this requirement from torture tests.
import $ from 'jquery';
window.$ = window.jQuery = $;
import 'jquery';
import '@matrix-org/olm';

View File

@@ -15,8 +15,8 @@ import Recorder from './modules/recorder/Recorder';
import { createTaskQueue } from './modules/util/helpers';
import {
createDeviceChangedEvent,
createScreenSharingEvent,
createStartSilentEvent,
createScreenSharingEvent,
createTrackMutedEvent,
sendAnalytics
} from './react/features/analytics';
@@ -27,17 +27,14 @@ import {
} from './react/features/app/actions';
import { showModeratedNotification } from './react/features/av-moderation/actions';
import { shouldShowModeratedNotification } from './react/features/av-moderation/functions';
import { setAudioOnly } from './react/features/base/audio-only';
import {
AVATAR_URL_COMMAND,
CONFERENCE_LEAVE_REASONS,
EMAIL_COMMAND,
_conferenceWillJoin,
authStatusChanged,
commonUserJoinedHandling,
commonUserLeftHandling,
conferenceFailed,
conferenceJoinInProgress,
conferenceJoined,
conferenceLeft,
conferenceSubjectChanged,
@@ -46,19 +43,15 @@ import {
conferenceWillJoin,
conferenceWillLeave,
dataChannelOpened,
e2eRttChanged,
getConferenceOptions,
kickedOut,
lockStateChanged,
nonParticipantMessageReceived,
onStartMutedPolicyChanged,
p2pStatusChanged,
sendLocalParticipant
sendLocalParticipant,
nonParticipantMessageReceived
} from './react/features/base/conference';
import {
getMultipleVideoSendingSupportFeatureFlag,
getReplaceParticipant
} from './react/features/base/config/functions';
import { getReplaceParticipant } from './react/features/base/config/functions';
import {
checkAndNotifyForNewDevice,
getAvailableDevices,
@@ -69,23 +62,23 @@ import {
updateDeviceList
} from './react/features/base/devices';
import {
browser,
isFatalJitsiConnectionError,
JitsiConferenceErrors,
JitsiConferenceEvents,
JitsiConnectionErrors,
JitsiConnectionEvents,
JitsiE2ePingEvents,
JitsiMediaDevicesEvents,
JitsiParticipantConnectionStatus,
JitsiTrackErrors,
JitsiTrackEvents,
browser
JitsiRecordingConstants
} from './react/features/base/lib-jitsi-meet';
import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-meet/functions';
import {
MEDIA_TYPE,
getStartWithAudioMuted,
getStartWithVideoMuted,
isVideoMutedByUser,
MEDIA_TYPE,
setAudioAvailable,
setAudioMuted,
setAudioUnmutePermissions,
@@ -97,8 +90,6 @@ import {
dominantSpeakerChanged,
getLocalParticipant,
getNormalizedDisplayName,
getVirtualScreenshareParticipantByOwnerId,
localParticipantAudioLevelChanged,
localParticipantConnectionStatusChanged,
localParticipantRoleChanged,
participantConnectionStatusChanged,
@@ -107,7 +98,6 @@ import {
participantPresenceChanged,
participantRoleChanged,
participantUpdated,
screenshareParticipantDisplayNameChanged,
updateRemoteParticipantFeatures
} from './react/features/base/participants';
import {
@@ -115,7 +105,6 @@ import {
updateSettings
} from './react/features/base/settings';
import {
addLocalTrack,
createLocalPresenterTrack,
createLocalTracksF,
destroyLocalTracks,
@@ -137,31 +126,29 @@ import {
maybeOpenFeedbackDialog,
submitFeedback
} from './react/features/feedback';
import { maybeSetLobbyChatMessageListener } from './react/features/lobby/actions.any';
import { setNoiseSuppressionEnabled } from './react/features/noise-suppression/actions';
import {
NOTIFICATION_TIMEOUT_TYPE,
isModerationNotificationDisplayed,
showNotification
showNotification,
NOTIFICATION_TIMEOUT_TYPE
} from './react/features/notifications';
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
import { mediaPermissionPromptVisibilityChanged, toggleSlowGUMOverlay } from './react/features/overlay';
import { suspendDetected } from './react/features/power-monitor';
import {
initPrejoin,
isPrejoinPageEnabled,
isPrejoinPageVisible,
makePrecallTest,
setJoiningInProgress
setJoiningInProgress,
setPrejoinPageVisibility
} from './react/features/prejoin';
import { getActiveSession } from './react/features/recording/functions';
import { disableReceiver, stopReceiver } from './react/features/remote-control';
import { isScreenAudioShared, setScreenAudioShareState } from './react/features/screen-share/';
import { setScreenAudioShareState, isScreenAudioShared } from './react/features/screen-share/';
import { toggleScreenshotCaptureSummary } from './react/features/screenshot-capture';
import { isScreenshotCaptureEnabled } from './react/features/screenshot-capture/functions';
import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect';
import { createPresenterEffect } from './react/features/stream-effects/presenter';
import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
import { endpointMessageReceived } from './react/features/subtitles';
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
import { muteLocal } from './react/features/video-menu/actions.any';
import UIEvents from './service/UI/UIEvents';
const logger = Logger.getLogger(__filename);
@@ -383,10 +370,11 @@ class ConferenceConnector {
// FIXME the conference should be stopped by the library and not by
// the app. Both the errors above are unrecoverable from the library
// perspective.
room.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).then(() => connection.disconnect());
room.leave().then(() => connection.disconnect());
break;
case JitsiConferenceErrors.CONFERENCE_MAX_USERS:
connection.disconnect();
APP.UI.notifyMaxUsersLimitReached();
break;
@@ -466,7 +454,7 @@ function _connectionFailedHandler(error) {
_connectionFailedHandler);
if (room) {
APP.store.dispatch(conferenceWillLeave(room));
room.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR);
room.leave();
}
}
}
@@ -498,7 +486,7 @@ export default {
// Always get a handle on the audio input device so that we have statistics (such as "No audio input" or
// "Are you trying to speak?" ) even if the user joins the conference muted.
const initialDevices = config.disableInitialGUM ? [] : [ MEDIA_TYPE.AUDIO ];
const initialDevices = config.disableInitialGUM ? [] : [ 'audio' ];
const requestedAudio = !config.disableInitialGUM;
let requestedVideo = false;
@@ -506,7 +494,7 @@ export default {
&& !options.startWithVideoMuted
&& !options.startAudioOnly
&& !options.startScreenSharing) {
initialDevices.push(MEDIA_TYPE.VIDEO);
initialDevices.push('video');
requestedVideo = true;
}
@@ -519,40 +507,32 @@ export default {
);
}
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMediaDevicesEvents.SLOW_GET_USER_MEDIA,
() => APP.store.dispatch(toggleSlowGUMOverlay(true))
);
let tryCreateLocalTracks;
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
// spend much time displaying the overlay screen. If GUM is not resolved within 15 seconds it will
// probably never resolve.
const timeout = browser.isElectron() ? 15000 : 60000;
const audioOptions = {
devices: [ MEDIA_TYPE.AUDIO ],
timeout,
firePermissionPromptIsShownEvent: true
};
// FIXME is there any simpler way to rewrite this spaghetti below ?
if (options.startScreenSharing) {
// This option has been deprecated since it is no longer supported as per the w3c spec.
// https://w3c.github.io/mediacapture-screen-share/#dom-mediadevices-getdisplaymedia. If the user has not
// interacted with the webpage before the getDisplayMedia call, the promise will be rejected by the
// browser. This has already been implemented in Firefox and Safari and will be implemented in Chrome soon.
// https://bugs.chromium.org/p/chromium/issues/detail?id=1198918
// Please note that Spot uses the same config option to use an external video input device label as
// screenshare and calls getUserMedia instead of getDisplayMedia for capturing the media. Therefore it
// needs to be supported here if _desktopSharingSourceDevice is provided.
const errMessage = new Error('startScreenSharing config option is no longer supported for web browsers');
const desktopPromise = config._desktopSharingSourceDevice
? this._createDesktopTrack()
: Promise.reject(errMessage);
tryCreateLocalTracks = desktopPromise
tryCreateLocalTracks = this._createDesktopTrack()
.then(([ desktopStream ]) => {
if (!requestedAudio) {
return [ desktopStream ];
}
return createLocalTracksF(audioOptions)
return createLocalTracksF({
devices: [ 'audio' ],
timeout,
firePermissionPromptIsShownEvent: true,
fireSlowPromiseEvent: true
})
.then(([ audioStream ]) =>
[ desktopStream, audioStream ])
.catch(error => {
@@ -565,7 +545,14 @@ export default {
logger.error('Failed to obtain desktop stream', error);
errors.screenSharingError = error;
return requestedAudio ? createLocalTracksF(audioOptions) : [];
return requestedAudio
? createLocalTracksF({
devices: [ 'audio' ],
timeout,
firePermissionPromptIsShownEvent: true,
fireSlowPromiseEvent: true
})
: [];
})
.catch(error => {
errors.audioOnlyError = error;
@@ -579,7 +566,8 @@ export default {
tryCreateLocalTracks = createLocalTracksF({
devices: initialDevices,
timeout,
firePermissionPromptIsShownEvent: true
firePermissionPromptIsShownEvent: true,
fireSlowPromiseEvent: true
})
.catch(err => {
if (requestedAudio && requestedVideo) {
@@ -599,7 +587,13 @@ export default {
return [];
}
return createLocalTracksF(audioOptions);
return (
createLocalTracksF({
devices: [ 'audio' ],
timeout,
firePermissionPromptIsShownEvent: true,
fireSlowPromiseEvent: true
}));
} else if (requestedAudio && !requestedVideo) {
errors.audioOnlyError = err;
@@ -621,8 +615,9 @@ export default {
// Try video only...
return requestedVideo
? createLocalTracksF({
devices: [ MEDIA_TYPE.VIDEO ],
firePermissionPromptIsShownEvent: true
devices: [ 'video' ],
firePermissionPromptIsShownEvent: true,
fireSlowPromiseEvent: true
})
: [];
})
@@ -643,6 +638,7 @@ export default {
// the user inputs their credentials, but the dialog would be
// overshadowed by the overlay.
tryCreateLocalTracks.then(tracks => {
APP.store.dispatch(toggleSlowGUMOverlay(false));
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
return tracks;
@@ -782,6 +778,7 @@ export default {
startAudioOnly: config.startAudioOnly,
startScreenSharing: config.startScreenSharing,
startWithAudioMuted: getStartWithAudioMuted(APP.store.getState())
|| config.startSilent
|| isUserInteractionRequiredForUnmute(APP.store.getState()),
startWithVideoMuted: getStartWithVideoMuted(APP.store.getState())
|| isUserInteractionRequiredForUnmute(APP.store.getState())
@@ -800,21 +797,7 @@ export default {
logger.warn('initial device list initialization failed', error);
}
const handleStartAudioMuted = (options, tracks) => {
if (options.startWithAudioMuted) {
// Always add the track on Safari because of a known issue where audio playout doesn't happen
// if the user joins audio and video muted, i.e., if there is no local media capture.
if (browser.isWebKitBased()) {
this.muteAudio(true, true);
} else {
return tracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
}
}
return tracks;
};
if (isPrejoinPageVisible(APP.store.getState())) {
if (isPrejoinPageEnabled(APP.store.getState())) {
_connectionPromise = connect(roomName).then(c => {
// we want to initialize it early, in case of errors to be able
// to gather logs
@@ -845,28 +828,25 @@ export default {
this._displayErrorsForCreateInitialLocalTracks(errors);
let localTracks = handleStartAudioMuted(initialOptions, tracks);
// in case where gum is slow and resolves after the startAudio/VideoMuted coming from jicofo, we can be
// join unmuted even though jicofo had instruct us to mute, so let's respect that before passing the tracks
if (!browser.isWebKitBased()) {
if (room?.isStartAudioMuted()) {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
}
}
if (room?.isStartVideoMuted()) {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
}
return this._setLocalAudioVideoStreams(localTracks);
return this._setLocalAudioVideoStreams(tracks);
}
const [ tracks, con ] = await this.createInitialLocalTracksAndConnect(roomName, initialOptions);
let localTracks = tracks;
this._initDeviceList(true);
return this.startConference(con, handleStartAudioMuted(initialOptions, tracks));
if (initialOptions.startWithAudioMuted) {
// Always add the track on Safari because of a known issue where audio playout doesn't happen
// if the user joins audio and video muted, i.e., if there is no local media capture.
if (browser.isWebKitBased()) {
this.muteAudio(true, true);
} else {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
}
}
return this.startConference(con, localTracks);
},
/**
@@ -916,35 +896,6 @@ export default {
: isVideoMutedByUser(APP.store);
},
/**
* Verify if there is an ongoing system audio sharing session and apply to the provided track
* as a AudioMixer effect.
*
* @param {*} localAudioTrack - track to which system audio track will be applied as an effect, most likely
* microphone local audio track.
*/
async _maybeApplyAudioMixerEffect(localAudioTrack) {
// At the time of writing this comment there were two separate flows for toggling screen-sharing
// and system audio sharing, the first is the legacy method using the functionality from conference.js
// the second is used when both sendMultipleVideoStreams and sourceNameSignaling flags are set to true.
// The second flow uses functionality from base/conference/middleware.web.js.
// We check if system audio sharing was done using the first flow by verifying this._desktopAudioStream and
// for the second by checking 'features/screen-share' state.
const { desktopAudioTrack } = APP.store.getState()['features/screen-share'];
const currentDesktopAudioTrack = this._desktopAudioStream || desktopAudioTrack;
// If system audio is already being sent, mix it with the provided audio track.
if (currentDesktopAudioTrack) {
// In case system audio sharing was done in the absence of an initial mic audio track, there is no
// AudioMixerEffect so we have to remove system audio track from the room before setting it as an effect.
await room.replaceTrack(currentDesktopAudioTrack, null);
this._mixerEffect = new AudioMixerEffect(currentDesktopAudioTrack);
logger.debug('Mixing new audio track with existing screen audio track.');
await localAudioTrack.setEffect(this._mixerEffect);
}
},
/**
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
* @param {boolean} mute true for mute and false for unmute.
@@ -998,11 +949,7 @@ export default {
// Rollback the audio muted status by using null track
return null;
})
.then(async audioTrack => {
await this._maybeApplyAudioMixerEffect(audioTrack);
this.useAudioStream(audioTrack);
});
.then(audioTrack => this.useAudioStream(audioTrack));
} else {
muteLocalAudio(mute);
}
@@ -1143,13 +1090,9 @@ export default {
* Simulates toolbar button click for video mute. Used by shortcuts and API.
* @param {boolean} [showUI] when set to false will not display any error
* dialogs in case of media permissions error.
* @param {boolean} ensureTrack - True if we want to ensure that a new track is
* created if missing.
*/
toggleVideoMuted(showUI = true, ensureTrack = false) {
const mute = !this.isLocalVideoMuted();
APP.store.dispatch(handleToggleVideoMuted(mute, showUI, ensureTrack));
toggleVideoMuted(showUI = true) {
this.muteVideo(!this.isLocalVideoMuted(), showUI);
},
/**
@@ -1181,12 +1124,8 @@ export default {
return room.getParticipants();
},
/**
* Used by Jibri to detect when it's alone and the meeting should be terminated.
*/
get membersCount() {
return room.getParticipants()
.filter(p => !p.isHidden() || !(config.iAmRecorder && p.isHiddenFromRecorder())).length + 1;
return room.getParticipants().length + 1;
},
/**
@@ -1482,13 +1421,11 @@ export default {
* @returns {Promise}
*/
useVideoStream(newTrack) {
const state = APP.store.getState();
logger.debug(`useVideoStream: ${newTrack}`);
return new Promise((resolve, reject) => {
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
const oldTrack = getLocalJitsiVideoTrack(state);
const oldTrack = getLocalJitsiVideoTrack(APP.store.getState());
logger.debug(`useVideoStream: Replacing ${oldTrack} with ${newTrack}`);
@@ -1499,26 +1436,6 @@ export default {
return;
}
// In the multi-stream mode, add the track to the conference if there is no existing track, replace it
// otherwise.
if (getMultipleVideoSendingSupportFeatureFlag(state)) {
const trackAction = oldTrack
? replaceLocalTrack(oldTrack, newTrack, room)
: addLocalTrack(newTrack);
APP.store.dispatch(trackAction)
.then(() => {
this.setVideoMuteStatus();
})
.then(resolve)
.catch(error => {
logger.error(`useVideoStream failed: ${error}`);
reject(error);
})
.then(onFinish);
return;
}
APP.store.dispatch(
replaceLocalTrack(oldTrack, newTrack, room))
.then(() => {
@@ -1637,7 +1554,9 @@ export default {
this._stopProxyConnection();
APP.store.dispatch(toggleScreenshotCaptureSummary(false));
if (config.enableScreenshotCapture) {
APP.store.dispatch(toggleScreenshotCaptureSummary(false));
}
const tracks = APP.store.getState()['features/base/tracks'];
const duration = getLocalVideoTrack(tracks)?.jitsiTrack.getDuration() ?? 0;
@@ -1647,26 +1566,12 @@ export default {
let promise = _prevMutePresenterVideo = _prevMutePresenterVideo.then(() => {
// mute the presenter track if it exists.
if (this.localPresenterVideo) {
return (
this.localPresenterVideo.dispose().then(() => {
APP.store.dispatch(trackRemoved(this.localPresenterVideo));
this.localPresenterVideo = null;
})
.then(() => {
APP.store.dispatch(setVideoMuted(true, MEDIA_TYPE.PRESENTER));
// This is needed only for setting the correct muted state in features/base/media.
// NOTE: It is important to be executed after we have disposed and removed the presenter track.
// This way all the side effects won't be executed and we won't start additional O/A cycle for
// replacing the track with video with the one without video. This O/A cycle is not needed since
// we are trying to destroy all tracks. Also due to the current async nature of muting the
// presenter, the final removal of the screen sharing track (see the code at the end of the
// function) can be executed between the removal of the stream with video and adding the
// original screen sharing stream to the peer connection. This will lead to a failure to remove
// the screen sharing track, compromising the screen sharing state in jitsi-meet and the user
// won't be able to turn off the screen sharing.
APP.store.dispatch(setVideoMuted(true, MEDIA_TYPE.PRESENTER));
})
);
return this.localPresenterVideo.dispose().then(() => {
APP.store.dispatch(trackRemoved(this.localPresenterVideo));
this.localPresenterVideo = null;
});
}
});
@@ -1682,8 +1587,7 @@ export default {
// In case there was no local audio when screen sharing was started the fact that we set the audio stream to
// null will take care of the desktop audio stream cleanup.
} else if (this._desktopAudioStream) {
await room.replaceTrack(this._desktopAudioStream, null);
this._desktopAudioStream.dispose();
await this.useAudioStream(null);
this._desktopAudioStream = undefined;
}
@@ -1738,12 +1642,9 @@ export default {
* toggles between screen sharing and camera video.
* @param {Object} [options] - Screen sharing options that will be passed to
* createLocalTracks.
* @param {boolean} [options.audioOnly] - Whether or not audioOnly is enabled.
* @param {Array<string>} [options.desktopSharingSources] - Array with the
* sources that have to be displayed in the desktop picker window ('screen',
* 'window', etc.).
* @param {Object} [options.desktopStream] - An existing desktop stream to
* use instead of creating a new desktop stream.
* @param {boolean} ignoreDidHaveVideo - if true ignore if video was on when sharing started.
* @return {Promise.<T>}
*/
@@ -1756,12 +1657,13 @@ export default {
return Promise.reject('Cannot toggle screen sharing: not supported.');
}
if (this.isAudioOnly()) {
return Promise.reject('No screensharing in audio only mode');
}
if (toggle) {
try {
await this._switchToScreenSharing(options);
if (this.isAudioOnly()) {
APP.store.dispatch(setAudioOnly(false));
}
return;
} catch (err) {
@@ -2014,34 +1916,29 @@ export default {
}
if (this._desktopAudioStream) {
// Noise suppression doesn't work with desktop audio because we can't chain
// track effects yet, disable it first.
// We need to to wait for the effect to clear first or it might interfere with the audio mixer.
await APP.store.dispatch(setNoiseSuppressionEnabled(false));
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
// If there is a localAudio stream, mix in the desktop audio stream captured by the screen sharing
// api.
if (localAudio) {
this._mixerEffect = new AudioMixerEffect(this._desktopAudioStream);
logger.debug(`_switchToScreenSharing is mixing ${this._desktopAudioStream} and ${localAudio}`
+ ' as a single audio stream');
await localAudio.setEffect(this._mixerEffect);
} else {
// If no local stream is present ( i.e. no input audio devices) we use the screen share audio
// stream as we would use a regular stream.
logger.debug(`_switchToScreenSharing is using ${this._desktopAudioStream} for replacing it as`
+ ' the only audio track on the conference');
await room.replaceTrack(null, this._desktopAudioStream);
await this.useAudioStream(this._desktopAudioStream);
}
APP.store.dispatch(setScreenAudioShareState(true));
}
})
.then(() => {
this.videoSwitchInProgress = false;
if (isScreenshotCaptureEnabled(APP.store.getState(), false, true)) {
APP.store.dispatch(toggleScreenshotCaptureSummary(true));
if (config.enableScreenshotCapture) {
if (getActiveSession(APP.store.getState(), JitsiRecordingConstants.mode.FILE)) {
APP.store.dispatch(toggleScreenshotCaptureSummary(true));
}
}
sendAnalytics(createScreenSharingEvent('started'));
logger.log('Screen sharing started');
@@ -2122,9 +2019,9 @@ export default {
room.on(JitsiConferenceEvents.CONFERENCE_JOINED, () => {
this._onConferenceJoined();
});
room.on(
JitsiConferenceEvents.CONFERENCE_JOIN_IN_PROGRESS,
() => APP.store.dispatch(conferenceJoinInProgress(room)));
room.on(JitsiConferenceEvents.CONFERENCE_JOIN_IN_PROGRESS, () => {
APP.store.dispatch(setPrejoinPageVisibility(false));
});
room.on(
JitsiConferenceEvents.CONFERENCE_LEFT,
@@ -2151,10 +2048,6 @@ export default {
APP.store.dispatch(updateRemoteParticipantFeatures(user));
});
room.on(JitsiConferenceEvents.USER_JOINED, (id, user) => {
if (config.iAmRecorder && user.isHiddenFromRecorder()) {
return;
}
// The logic shared between RN and web.
commonUserJoinedHandling(APP.store, room, user);
@@ -2192,10 +2085,6 @@ export default {
if (this.isLocalId(id)) {
logger.info(`My role changed, new role: ${role}`);
if (role === 'moderator') {
APP.store.dispatch(maybeSetLobbyChatMessageListener());
}
APP.store.dispatch(localParticipantRoleChanged(role));
APP.API.notifyUserRoleChanged(id, role);
} else {
@@ -2208,14 +2097,6 @@ export default {
return;
}
if (config.iAmRecorder) {
const participant = room.getParticipantById(track.getParticipantId());
if (participant.isHiddenFromRecorder()) {
return;
}
}
APP.store.dispatch(trackAdded(track));
});
@@ -2231,10 +2112,6 @@ export default {
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
let newLvl = lvl;
if (this.isLocalId(id)) {
APP.store.dispatch(localParticipantAudioLevelChanged(lvl));
}
if (this.isLocalId(id) && localAudio?.isMuted()) {
newLvl = 0;
}
@@ -2281,9 +2158,7 @@ export default {
room.on(
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
(dominant, previous, silence) => {
APP.store.dispatch(dominantSpeakerChanged(dominant, previous, Boolean(silence), room));
});
(dominant, previous) => APP.store.dispatch(dominantSpeakerChanged(dominant, previous, room)));
room.on(
JitsiConferenceEvents.CONFERENCE_CREATED_TIMESTAMP,
@@ -2314,15 +2189,6 @@ export default {
id,
name: formattedDisplayName
}));
const virtualScreenshareParticipantId = getVirtualScreenshareParticipantByOwnerId(state, id)?.id;
if (virtualScreenshareParticipantId) {
APP.store.dispatch(
screenshareParticipantDisplayNameChanged(virtualScreenshareParticipantId, formattedDisplayName)
);
}
APP.API.notifyDisplayNameChanged(id, {
displayName: formattedDisplayName,
formattedDisplayName:
@@ -2411,15 +2277,11 @@ export default {
APP.store.dispatch(setVideoUnmutePermissions(disableVideoMuteChange));
});
room.on(
JitsiE2ePingEvents.E2E_RTT_CHANGED,
(...args) => APP.store.dispatch(e2eRttChanged(...args)));
APP.UI.addListener(UIEvents.AUDIO_MUTED, muted => {
this.muteAudio(muted);
});
APP.UI.addListener(UIEvents.VIDEO_MUTED, (muted, showUI = false) => {
this.muteVideo(muted, showUI);
APP.UI.addListener(UIEvents.VIDEO_MUTED, muted => {
this.muteVideo(muted);
});
room.addCommandListener(this.commands.defaults.ETHERPAD,
@@ -2575,16 +2437,16 @@ export default {
return stream;
})
.then(stream => {
logger.info(`Switching the local video device to ${cameraDeviceId}.`);
logger.log('Switching the local video device.');
return this.useVideoStream(stream);
})
.then(() => {
logger.info(`Switched local video device to ${cameraDeviceId}.`);
logger.log('Switched local video device.');
this._updateVideoDeviceId();
})
.catch(error => {
logger.error(`Failed to switch to selected camera:${cameraDeviceId}, error:${error}`);
logger.error(`Switching the local video device failed: ${error}`);
return APP.store.dispatch(notifyCameraError(error));
});
@@ -2594,26 +2456,23 @@ export default {
APP.UI.addListener(
UIEvents.AUDIO_DEVICE_CHANGED,
async micDeviceId => {
micDeviceId => {
const audioWasMuted = this.isLocalAudioMuted();
// Disable noise suppression if it was enabled on the previous track.
await APP.store.dispatch(setNoiseSuppressionEnabled(false));
// When the 'default' mic needs to be selected, we need to
// pass the real device id to gUM instead of 'default' in order
// to get the correct MediaStreamTrack from chrome because of the
// following bug.
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689
const hasDefaultMicChanged = micDeviceId === 'default';
// When the 'default' mic needs to be selected, we need to pass the real device id to gUM instead of
// 'default' in order to get the correct MediaStreamTrack from chrome because of the following bug.
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689.
const isDefaultMicSelected = micDeviceId === 'default';
const selectedDeviceId = isDefaultMicSelected
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: micDeviceId;
logger.info(`Switching audio input device to ${selectedDeviceId}`);
sendAnalytics(createDeviceChangedEvent('audio', 'input'));
createLocalTracksF({
devices: [ 'audio' ],
cameraDeviceId: null,
micDeviceId: selectedDeviceId
micDeviceId: hasDefaultMicChanged
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: micDeviceId
})
.then(([ stream ]) => {
// if audio was muted before changing the device, mute
@@ -2626,31 +2485,61 @@ export default {
return stream;
})
.then(async stream => {
await this._maybeApplyAudioMixerEffect(stream);
// In case screen sharing audio is also shared we mix it with new input stream. The old _mixerEffect
// will be cleaned up when the existing track is replaced.
if (this._mixerEffect) {
this._mixerEffect = new AudioMixerEffect(this._desktopAudioStream);
await stream.setEffect(this._mixerEffect);
}
return this.useAudioStream(stream);
})
.then(() => {
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
if (localAudio && isDefaultMicSelected) {
if (localAudio && hasDefaultMicChanged) {
// workaround for the default device to be shown as selected in the
// settings even when the real device id was passed to gUM because of the
// above mentioned chrome bug.
localAudio._realDeviceId = localAudio.deviceId = 'default';
}
logger.info(`switched local audio input device to: ${selectedDeviceId}`);
logger.log(`switched local audio device: ${localAudio?.getDeviceId()}`);
this._updateAudioDeviceId();
})
.catch(err => {
logger.error(`Failed to switch to selected audio input device ${selectedDeviceId}, error=${err}`);
APP.store.dispatch(notifyMicError(err));
});
}
);
APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, () => {
// Immediately update the UI by having remote videos and the large video update themselves.
APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, audioOnly => {
// FIXME On web video track is stored both in redux and in
// 'localVideo' field, video is attempted to be unmuted twice when
// turning off the audio only mode. This will crash the app with
// 'unmute operation is already in progress'.
// Because there's no logic in redux about creating new track in
// case unmute when not track exists the things have to go through
// muteVideo logic in such case.
const tracks = APP.store.getState()['features/base/tracks'];
const isTrackInRedux
= Boolean(
tracks.find(
track => track.jitsiTrack
&& track.jitsiTrack.getType() === 'video'));
if (!isTrackInRedux) {
this.muteVideo(audioOnly);
}
// Immediately update the UI by having remote videos and the large
// video update themselves instead of waiting for some other event
// to cause the update, usually PARTICIPANT_CONN_STATUS_CHANGED.
// There is no guarantee another event will trigger the update
// immediately and in all situations, for example because a remote
// participant is having connection trouble so no status changes.
const displayedUserId = APP.UI.getLargeVideoID();
if (displayedUserId) {
@@ -2691,24 +2580,13 @@ export default {
* @returns {void}
*/
_onConferenceJoined() {
const { dispatch } = APP.store;
APP.UI.initConference();
if (!config.disableShortcuts) {
APP.keyboardshortcut.init();
}
dispatch(conferenceJoined(room));
const jwt = APP.store.getState()['features/base/jwt'];
if (jwt?.user?.hiddenFromRecorder) {
dispatch(muteLocal(true, MEDIA_TYPE.AUDIO));
dispatch(muteLocal(true, MEDIA_TYPE.VIDEO));
dispatch(setAudioUnmutePermissions(true, true));
dispatch(setVideoUnmutePermissions(true, true));
}
APP.store.dispatch(conferenceJoined(room));
},
/**
@@ -2975,6 +2853,7 @@ export default {
const available = audioDeviceCount > 0 || Boolean(localAudio);
APP.store.dispatch(setAudioAvailable(available));
APP.API.notifyAudioAvailabilityChanged(available);
},
/**
@@ -3055,15 +2934,14 @@ export default {
/**
* Leaves the room.
*
* @param {boolean} doDisconnect - Whether leaving the room should also terminate the connection.
* @param {string} reason - reason for leaving the room.
* @param {boolean} doDisconnect - Wether leaving the room should also terminate the connection.
* @returns {Promise}
*/
async leaveRoom(doDisconnect = true, reason = '') {
async leaveRoom(doDisconnect = true) {
APP.store.dispatch(conferenceWillLeave(room));
if (room && room.isJoined()) {
return room.leave(reason).finally(() => {
return room.leave().finally(() => {
if (doDisconnect) {
return disconnect();
}
@@ -3080,12 +2958,34 @@ export default {
* @param email {string} the new email
*/
changeLocalEmail(email = '') {
const localParticipant = getLocalParticipant(APP.store.getState());
const formattedEmail = String(email).trim();
if (formattedEmail === localParticipant.email) {
return;
}
const localId = localParticipant.id;
APP.store.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without
// stating the JitsiConference instance (i.e. participant property
// `conference` for a remote participant) because the local
// participant is uniquely identified by the very fact that there is
// only one local participant.
id: localId,
local: true,
email: formattedEmail
}));
APP.store.dispatch(updateSettings({
email: formattedEmail
}));
APP.API.notifyEmailChanged(localId, {
email: formattedEmail
});
sendData(commands.EMAIL, formattedEmail);
},
@@ -3094,12 +2994,29 @@ export default {
* @param url {string} the new url
*/
changeLocalAvatarUrl(url = '') {
const { avatarURL, id } = getLocalParticipant(APP.store.getState());
const formattedUrl = String(url).trim();
if (formattedUrl === avatarURL) {
return;
}
APP.store.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without
// stating the JitsiConference instance (i.e. participant property
// `conference` for a remote participant) because the local
// participant is uniquely identified by the very fact that there is
// only one local participant.
id,
local: true,
avatarURL: formattedUrl
}));
APP.store.dispatch(updateSettings({
avatarURL: formattedUrl
}));
sendData(commands.AVATAR_URL, url);
},
@@ -3115,6 +3032,15 @@ export default {
room.sendEndpointMessage(to, payload);
},
/**
* Sends a facial expression as a string and its duration as a number
* @param {object} payload - Object containing the {string} facialExpression
* and {number} duration
*/
sendFacialExpression(payload) {
room.sendFacialExpression(payload);
},
/**
* Adds new listener.
* @param {String} eventName the name of the event
@@ -3140,6 +3066,23 @@ export default {
*/
changeLocalDisplayName(nickname = '') {
const formattedNickname = getNormalizedDisplayName(nickname);
const { id, name } = getLocalParticipant(APP.store.getState());
if (formattedNickname === name) {
return;
}
APP.store.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without
// stating the JitsiConference instance (i.e. participant property
// `conference` for a remote participant) because the local
// participant is uniquely identified by the very fact that there is
// only one local participant.
id,
local: true,
name: formattedNickname
}));
APP.store.dispatch(updateSettings({
displayName: formattedNickname
@@ -3238,6 +3181,7 @@ export default {
*/
setAudioMuteStatus(muted) {
APP.UI.setAudioMuted(this.getMyUserId(), muted);
APP.API.notifyAudioMutedStatusChanged(muted);
},
/**

659
config.js

File diff suppressed because it is too large Load Diff

View File

@@ -8,21 +8,18 @@ import { LoginDialog } from './react/features/authentication/components';
import { isTokenAuthEnabled } from './react/features/authentication/functions';
import {
connectionEstablished,
connectionFailed,
constructOptions
connectionFailed
} from './react/features/base/connection/actions';
import { openDialog } from './react/features/base/dialog/actions';
import { setJWT } from './react/features/base/jwt';
import {
isFatalJitsiConnectionError,
JitsiConnectionErrors,
JitsiConnectionEvents
} from './react/features/base/lib-jitsi-meet';
import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-meet/functions';
import { getCustomerDetails } from './react/features/jaas/actions.any';
import { getJaasJWT, isVpaasMeeting } from './react/features/jaas/functions';
import {
setPrejoinDisplayNameRequired
} from './react/features/prejoin/actions';
import { isVpaasMeeting, getJaasJWT } from './react/features/jaas/functions';
import { setPrejoinDisplayNameRequired } from './react/features/prejoin/actions';
const logger = Logger.getLogger(__filename);
/**
@@ -84,10 +81,12 @@ function checkForAttachParametersAndConnect(id, password, connection) {
* Try to open connection using provided credentials.
* @param {string} [id]
* @param {string} [password]
* @param {string} [roomName]
* @returns {Promise<JitsiConnection>} connection if
* everything is ok, else error.
*/
export async function connect(id, password) {
export async function connect(id, password, roomName) {
const connectionConfig = Object.assign({}, config);
const state = APP.store.getState();
let { jwt } = state['features/base/jwt'];
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
@@ -101,7 +100,19 @@ export async function connect(id, password) {
}
}
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, constructOptions(state));
// Use Websocket URL for the web app if configured. Note that there is no 'isWeb' check, because there's assumption
// that this code executes only on web browsers/electron. This needs to be changed when mobile and web are unified.
let serviceUrl = connectionConfig.websocket || connectionConfig.bosh;
serviceUrl += `?room=${roomName}`;
connectionConfig.serviceUrl = serviceUrl;
if (connectionConfig.websocketKeepAliveUrl) {
connectionConfig.websocketKeepAliveUrl += `?room=${roomName}`;
}
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, connectionConfig);
if (config.iAmRecorder) {
connection.addFeature(DISCO_JIBRI_FEATURE);

View File

@@ -4,12 +4,11 @@
text-align: center;
h2 {
font-size: 48px;
color : #f2f2f2;
font-size: 36px;
}
&__message {
font-size: 24px;
margin-top: 20px;
}
}
}

View File

@@ -24,11 +24,19 @@
bottom: calc(#{$newToolbarSizeWithPadding}) !important;
}
/**
* Override @atlaskit/theme styling for the top toolbar so it displays over
* the video thumbnail while obscuring as little as possible.
*/
.videocontainer__toptoolbar > div > div {
background: none;
}
/**
* Keep overflow menu within screen vertical bounds and make it scrollable.
*/
.toolbox-button-wth-dialog > div:nth-child(2) {
.toolbox-button-wth-dialog > div:nth-child(2) {
background: $menuBG;
max-height: calc(100vh - #{$newToolbarSizeWithPadding} - 46px);
margin-bottom: 4px;
@@ -36,22 +44,19 @@
overflow-y: auto;
}
/**
* Remove background color and box-shadow for the context menu container.
*/
.toolbox-button-wth-dialog.context-menu > div:nth-child(2) {
background: transparent;
box-shadow: none;
overflow-y: initial;
}
.audio-preview > div:nth-child(2),
.video-preview > div:nth-child(2) {
.video-preview > div:nth-child(2),
.reactions-menu-popup > div:nth-child(2) {
margin-bottom: 4px;
outline: none;
padding: 0;
}
.reactions-menu-popup > div:nth-child(2) {
margin-bottom: 6px;
box-shadow: none;
}
/**
* The following selectors keep the chat modal full-size anywhere between 100px
* and 580px for desktop or 680px for mobile.

View File

@@ -121,18 +121,9 @@
}
}
&--nometer {
.audio-preview-entry-text {
max-width: 238px;
}
.audio-preview-entry-text {
max-width: 178px;
}
&--withmeter {
.audio-preview-entry-text {
max-width: 178px;
}
}
}
&-icon {

View File

@@ -48,7 +48,6 @@ canvas,
progress,
video {
display: inline-block;
transition: object-position 0.5s ease 0s;
vertical-align: baseline;
}
audio:not([controls]) {

50
css/_avatar.scss Normal file
View File

@@ -0,0 +1,50 @@
.avatar {
background-color: #AAA;
border-radius: 50%;
color: rgba(255, 255, 255, 1);
font-weight: 100;
object-fit: cover;
&.avatar-small {
height: 28px !important;
width: 28px !important;
}
&.avatar-xsmall {
height: 16px !important;
width: 16px !important;
}
.jitsi-icon {
transform: translateY(50%);
}
}
.avatar-svg {
height: 100%;
width: 100%;
}
.avatar-badge {
position: relative;
&-available::after {
@include avatarBadge;
background-color: $presence-available;
}
&-away::after {
@include avatarBadge;
background-color: $presence-away;
}
&-busy::after {
@include avatarBadge;
background-color: $presence-busy;
}
&-idle::after {
@include avatarBadge;
background-color: $presence-idle;
}
}

View File

@@ -6,10 +6,6 @@
* {
-webkit-user-select: none;
user-select: none;
// Firefox only
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, .5) transparent;
}
input,
@@ -43,6 +39,18 @@ body {
outline: none;
}
/**
* AtlasKit sets a default margin on the rendered modals, so
* when the shift-right class is set when the chat opens, we
* pad the modal container in order for the modals to be centered
* while also taking the chat size into consideration.
*/
@media (min-width: 581px) {
.shift-right .atlaskit-portal > div:not(.Tooltip) {
padding-left: $sidebarWidth;
}
}
.jitsi-icon {
&-default svg {
fill: white;
@@ -152,6 +160,16 @@ form {
font-size: 12px;
}
/**
* Dialogs fade
*/
.aui-blanket {
background: #000;
transition: opacity 0.2s, visibility 0.2s;
transition-delay: 0.1s;
visibility: visible;
}
#inviteLinkRef {
-webkit-user-select: text;
user-select: text;

View File

@@ -1,20 +1,15 @@
#sideToolbarContainer {
background-color: $chatBackgroundColor;
flex-shrink: 0;
overflow: hidden;
position: relative;
transition: width .16s ease-in-out;
box-sizing: border-box;
color: #FFF;
height: 100%;
position: absolute;
top: 0;
width: $sidebarWidth;
z-index: $sideToolbarContainerZ;
@media (max-width: 580px) {
height: 100vh;
height: -webkit-fill-available;
left: 0;
position: fixed;
right: 0;
top: 0;
width: auto;
width: 100%;
}
}
@@ -22,7 +17,7 @@
display: flex;
flex-direction: column;
// extract header + tabs height
height: calc(100% - 119px);
height: calc(100% - 102px);
}
.chat-panel-no-tabs {
@@ -30,18 +25,12 @@
height: calc(100% - 70px);
}
#chat-conversation-container {
// extract message input height
height: calc(100% - 68px);
overflow: hidden;
position: relative;
}
#chatconversation {
box-sizing: border-box;
flex: 1;
font-size: 10pt;
height: 100%;
// extract message input height
height: calc(100% - 68px);
line-height: 20px;
overflow: auto;
padding: 16px;
@@ -96,10 +85,6 @@
fill: white;
}
}
&.lobby-chat-recipient {
background-color: $chatLobbyMessageBackgroundColor;
}
}
@@ -125,18 +110,63 @@
.chat-input-container {
padding: 0 16px 16px;
&.populated {
#chat-input {
.send-button {
background: #1B67EC;
cursor: pointer;
margin-left: 0.3rem;
@media (hover: hover) and (pointer: fine) {
&:hover {
background: #3D82FB;
}
}
&:active {
background: #0852D4;
}
path {
fill: #fff;
}
}
}
}
}
#chat-input {
border: 1px solid $chatInputSeparatorColor;
display: flex;
align-items: flex-end;
padding: 4px;
position: relative;
border-radius: 3px;
&:focus-within {
border: 1px solid #619CF4;
}
* {
background-color: transparent;
}
}
.chat-input {
flex: 1;
margin-right: 8px;
.send-button-container {
display: flex;
align-items: center;
}
.send-button {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
width: 40px;
border-radius: 3px;
path {
fill: $chatInputSeparatorColor;
}
}
.smiley-button {
@@ -356,9 +386,7 @@
.smiley-input {
display: flex;
position: absolute;
top: 0;
left: 0;
position: relative;
}
.smileys-panel {
@@ -366,7 +394,7 @@
box-sizing: border-box;
background-color: rgba(0, 0, 0, .6) !important;
height: auto;
display: flex;
display: none;
overflow: hidden;
position: absolute;
width: calc(#{$sidebarWidth} - 32px);
@@ -381,6 +409,11 @@
*/
transition: max-height 0.3s;
&.show-smileys {
display: flex;
max-height: 500%;
}
#smileysContainer {
background-color: $chatBackgroundColor;
border-top: 1px solid $chatInputSeparatorColor;
@@ -422,9 +455,6 @@
&.privatemessage {
background-color: $chatPrivateMessageBackgroundColor;
}
&.lobbymessage {
background-color: $chatLobbyMessageBackgroundColor;
}
}
.display-name {
@@ -464,10 +494,6 @@
justify-content: center;
padding: 5px;
&.lobbychatmessageactions {
border-left-color: $chatLobbyActionsSeparatorColor;
}
.toolbox-icon {
cursor: pointer;
}
@@ -485,9 +511,6 @@
&.privatemessage {
background-color: $chatPrivateMessageBackgroundColor;
}
&.lobbymessage {
background-color: $chatLobbyMessageBackgroundColor;
}
}
}
@@ -531,3 +554,41 @@
background: #36383C;
border-radius: 3px;
}
.chat-tabs-container {
width: 100%;
border-bottom: thin solid #292929;
display: flex;
justify-content: space-around;
}
.chat-tab {
font-size: 1.2em;
padding-bottom: 0.5em;
width: 50%;
text-align: center;
color: #8B8B8B;
cursor: pointer;
}
.chat-tab-focus {
border-bottom-style: solid;
color: #FFF;
}
.chat-tab-title {
margin-right: 8px;
}
.chat-tab-badge {
background-color: #165ecc;
border-radius: 50%;
box-sizing: border-box;
font-weight: 700;
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
vertical-align: middle;
padding: 0 4px;
color: #FFF;
}

50
css/_connection-info.scss Normal file
View File

@@ -0,0 +1,50 @@
%connection-info {
font-size: 12px;
font-weight: 400;
td {
padding: 2px 0;
}
}
.connection-info
{
@extend %connection-info;
> table {
white-space: nowrap;
@extend %connection-info;
}
td:nth-child(n-1) {
padding-left: 5px;
}
&__icon {
margin-right: 2px;
}
&__download
{
@extend .connection-info__icon;
}
&__status
{
font-weight: bold;
}
&__upload
{
@extend .connection-info__icon;
}
&__mobile {
margin: 15px;
}
.connection-actions {
margin: 10px auto;
text-align: center;
}
}

View File

@@ -24,7 +24,10 @@
}
.drawer-menu {
overflow-y: auto;
max-height: calc(80vh - 64px);
background: #242528;
border-radius: 16px 16px 0 0;
overflow-y: scroll;
margin-bottom: env(safe-area-inset-bottom, 0);
width: 100%;
@@ -40,6 +43,15 @@
}
}
.popupmenu {
margin: auto;
width: 100%;
}
.popupmenu__item {
height: 48px;
}
&#{&} .overflow-menu {
margin: auto;
font-size: 1.2em;

View File

@@ -0,0 +1,10 @@
.shortcuts-list {
list-style-type: none;
padding: 0;
&__item {
display: flex;
justify-content: space-between;
margin-bottom: em(7, 14);
}
}

View File

@@ -195,15 +195,14 @@
}
/**
* Change the direction of the current element to LTR, but do not change the direction
* of its children; Keep them RTL.
* Avatar status badge mixin
*/
@mixin ltr {
body[dir=rtl] & {
direction: ltr;
& > * {
direction: rtl;
}
}
@mixin avatarBadge {
border-radius: 50%;
content: '';
display: block;
height: 35%;
position: absolute;
bottom: 0;
width: 35%;
}

View File

@@ -6,6 +6,10 @@
transition: width .16s ease-in-out;
width: 315px;
z-index: $zindex0;
&--closed {
width: 0;
}
}
.participants_pane-content {
@@ -21,6 +25,10 @@
}
}
.participant-avatar {
margin: 8px 16px 8px 0;
}
@media (max-width: 580px) {
.participants_pane {
height: 100vh;
@@ -30,6 +38,11 @@
right: 0;
top: 0;
width: auto;
&--closed {
display: none;
width: auto;
}
}
.participants_pane-content {

View File

@@ -28,23 +28,32 @@
}
.poll-answer-container {
display: flex;
padding: 4px;
background: #3D3D3D;
border-radius: 3px;
margin-bottom: 8px;
@media (max-width: 580px) {
&> span {
padding: 8px 0;
}
&> span {
padding: 8px 0;
}
svg {
margin-top: 6px;
}
svg {
margin-top: 6px;
}
}
}
.poll-answer-option {
font-weight: 400;
display: block;
margin: 4px;
@media (max-width: 580px) {
font-size: 16px;
margin: 11px 8px
}
}
.poll-answer-field-list, .poll-answer-list, .poll-result-list {
list-style-type: none;
padding: 0;
@@ -75,11 +84,11 @@ ol.poll-result-list {
}
.poll-create-option-row {
display: flex;
display: 'flex';
margin-bottom: 4;
}
// Needed to override atlaskit default blue color
// Needeed to override atlaskit default blue color
.poll-create-container .jsYMHu {
background: #292929;
border-color: #808090;
@@ -282,10 +291,6 @@ ol.poll-result-list {
h1, strong ,span {
color: #fff;
}
button > span {
color: inherit;
}
}
.poll-create-label {
@@ -310,7 +315,7 @@ ol.poll-result-list {
}
#polls-panel {
height: calc(100% - 119px);
height: calc(100% - 102px);
}
.poll-container {
@@ -358,3 +363,82 @@ ol.poll-result-list {
.poll-answer-footer {
padding: 8px 0 0 0;
}
.poll-button {
align-items: center;
border: 0;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
display: flex;
justify-content: center;
min-height: 40px;
width: 100%;
&:disabled {
cursor: initial;
}
@media (max-width: 580px) {
min-height: 48px;
}
}
.poll-button-primary {
background-color: #0056E0;
&:hover {
background-color: #246FE5;
}
&:active {
background-color: #0045B3;
}
&:focus {
background-color: #0045B3;
border: 3px solid #99BBF3;
}
&:disabled {
background-color: #00225A;
color: #858585;
}
}
.poll-button-secondary {
background-color: #3D3D3D;
&:hover {
background-color: #525252;
}
&:active {
background-color: #292929;
}
&:focus {
background-color: #292929;
border: 3px solid #858585;
}
&:disabled {
background-color: #141414;
color: #858585;
}
}
.poll-button-short {
max-width: 132px;
}
.poll-button-shortest {
max-width: 117px;
}
.poll-button-short,
.poll-button-shortest {
@media (max-width: 580px) {
min-width: 49%;
}
}

View File

@@ -43,9 +43,10 @@
.popover {
margin: -16px -24px;
padding: 16px 24px;
z-index: $popoverZ;
}
.excalidraw .popover {
margin: 0;
.padded-content {
padding: 4px 8px;
}

View File

@@ -2,18 +2,122 @@
* Initialize
**/
.popupmenu__contents {
.popupmenu__volume-slider {
&::-webkit-slider-runnable-track {
background-color: $popupSliderColor;
}
.popupmenu {
background-color: $menuBG;
border-radius: 3px;
list-style-type: none;
min-width: 150px;
text-align: left;
padding: 0px;
white-space: nowrap;
&::-moz-range-track {
background-color: $popupSliderColor;
}
&__item {
list-style-type: none;
height: 35px;
}
&::-ms-fill-lower {
background-color: $popupSliderColor;
// Link Appearance
&__link,
&__contents {
display: block;
box-sizing: border-box;
text-decoration: none;
height: 100%;
font-size: 9pt;
width: 100%;
cursor: pointer;
padding: 0 5px;
color: $popupMenuColor;
&:hover {
background-color: $popupMenuHoverBackground;
color: $popupMenuHoverColor;
}
&.disabled {
pointer-events: none;
}
}
&__list {
margin: 0;
padding: 0;
}
&__text {
display: inline-block;
margin-left: 8px;
vertical-align: middle;
}
&__link {
i {
cursor: pointer;
}
}
&__contents {
display: flex;
/**
* Positioning styles on the slider and its container are used to make
* the container fit the popup width, by removing the slider from the
* page flow, and then making the slider fit the container.
*/
.popupmenu__slider_container {
position: relative;
width: 100%;
.popupmenu__slider {
position: absolute;
top: 50%;
transform: translate(0, -50%);
width: 100%;
&::-webkit-slider-runnable-track {
background-color: $popupSliderColor;
}
&::-moz-range-track {
background-color: $popupSliderColor;
}
&::-ms-fill-lower {
background-color: $popupSliderColor;
}
}
}
}
&__icon {
vertical-align: middle;
position: relative;
display: inline-block;
min-width: 20px;
height: 100%;
padding-right: 10px;
> * {
@include absoluteAligning();
}
}
.icon-kick,
.icon-play,
.icon-stop {
font-size: 8pt;
}
}
/**
* Override reset css styling modifying all lists and set negative margin to
* reduce the visibility of padding on AtlasKit
* InlineDialogs.
*/
ul.popupmenu {
margin: -16px -24px;
}
span.localvideomenu:hover ul.popupmenu, span.remotevideomenu:hover ul.popupmenu, ul.popupmenu:hover {
display:block !important;
}

View File

@@ -7,20 +7,13 @@
border-radius: 3px;
padding: 16px;
&.with-gif {
width: 328px;
.reactions-row .toolbox-button:last-of-type {
top: 3px;
& .toolbox-icon.toggled {
background-color: #000000;
}
}
}
&.overflow {
width: 100%;
width: auto;
padding-bottom: max(env(safe-area-inset-bottom, 0), 16px);
background-color: #141414;
box-shadow: none;
border-radius: 0;
position: relative;
.toolbox-icon {
width: 48px;
@@ -40,10 +33,6 @@
.toolbox-button {
margin-right: 0;
}
.toolbox-button:last-of-type {
top: 0;
}
}
}
@@ -73,7 +62,6 @@
.toolbox-button {
margin-right: 8px;
touch-action: manipulation;
position: relative;
}
.toolbox-button:last-of-type {
@@ -104,10 +92,6 @@
}
}
.reactions-menu-container {
padding-bottom: 6px;
}
.reactions-animations-container {
position: absolute;
width: 20%;
@@ -116,7 +100,8 @@
height: 0;
}
.reactions-menu-popup-container {
.reactions-menu-popup-container,
.reactions-menu-popup {
display: inline-block;
position: relative;
}

View File

@@ -7,46 +7,22 @@
flex-direction: column;
.recording-header {
align-items: center;
display: flex;
flex: 0;
flex-direction: row;
justify-content: space-between;
padding-top: 32px;
.recording-title {
display: inline-flex;
align-items: center;
font-size: 14px;
font-size: 16px;
margin-left: 16px;
}
&.space-top {
margin-top: 10px;
}
}
.recording-header-line {
border-top: 1px solid #5e6d7a;
padding-top: 16px;
margin-top: 16px;
}
.local-recording-warning {
margin-top: 8px;
display: block;
font-size: 14px;
line-height: 20px;
padding: 8px 16px;
&.text {
color: #fff;
background-color: #3D3D3D;
}
&.notification {
color: #040404;
background-color: #F8AE1A;
}
}
.recording-switch-disabled {
@@ -58,79 +34,10 @@
align-items: center;
}
.file-sharing-icon-container {
background-color: #525252;
border-radius: 4px;
height: 40px;
justify-content: center;
width: 42px;
}
.cloud-content-recording-icon-container {
background-color: #FFFFFF;
border-radius: 4px;
height: 40px;
justify-content: center;
width: 40px;
}
.jitsi-recording-header {
margin-bottom: 16px;
}
.jitsi-content-recording-icon-container-with-switch {
background-color: #FFFFFF;
border-radius: 4px;
height: 40px;
width: 56px;
}
.jitsi-content-recording-icon-container-without-switch {
background-color: #FFFFFF;
border-radius: 4px;
height: 40px;
width: 46px;
}
.recording-icon {
height: 40px;
width: 32px;
height: 32px;
object-fit: contain;
width: 40px;
}
.content-recording-icon {
height: 18px;
margin: 10px 0 0 10px;
object-fit: contain;
width: 18px;
}
.recording-file-sharing-icon {
height: 18px;
object-fit: contain;
width: 18px;
}
.recording-info{
background-color: #FFD740;
color: black;
display: inline-flex;
margin: 32px 0;
width: 100%;
}
.recording-info-icon {
align-self: center;
height: 14px;
margin: 0 24px 0 16px;
object-fit: contain;
width: 14px;
}
.recording-info-title {
display: inline-flex;
font-size: 14px;
width: 290px
}
.recording-switch {

View File

@@ -60,15 +60,3 @@
}
}
}
.settings-button-small-icon-container {
position: absolute;
right: -4px;
top: -3px;
& .settings-button-small-icon {
position: relative;
top: 0;
right: 0;
}
}

View File

@@ -1,26 +1,11 @@
.subject {
color: #fff;
transition: opacity .6s ease-in-out;
z-index: $toolbarZ + 2;
margin-top: 20px;
opacity: 0;
margin-top: -120px;
transition: margin-top .3s ease-in;
z-index: $zindex3;
&.visible {
opacity: 1;
}
&#autoHide.with-always-on {
overflow: hidden;
animation: hideSubject forwards .6s ease-out;
margin-left: 4px;
& > .subject-info-container {
justify-content: flex-start;
}
&.visible {
animation: showSubject forwards .6s ease-out;
}
margin-top: 20px;
}
}
@@ -51,7 +36,10 @@
line-height: 28px;
padding: 0 16px;
height: 28px;
max-width: 324px;
@media (max-width: 700px) {
max-width: 100px;
}
@media (max-width: 300px) {
display: none;
@@ -86,25 +74,8 @@
position: absolute;
top: 0;
height: 48px;
max-width: calc(100% - 24px);
}
@keyframes hideSubject {
0% {
max-width: 100%;
}
100% {
max-width: 0;
}
}
@keyframes showSubject {
0% {
max-width: 0%;
}
100% {
max-width: 100%;
}
.shift-right .details-container {
margin-left: calc(#{$sidebarWidth} / 2);
}

View File

@@ -30,7 +30,7 @@
transition: bottom .3s ease-in;
width: 100%;
pointer-events: none;
z-index: $toolbarZ + 2;
&.visible {
bottom: 0;
@@ -39,6 +39,13 @@
&.no-buttons {
display: none;
}
@media (min-width: 581px) {
&.shift-right {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
}
}
}
.toolbox-content {
@@ -92,10 +99,6 @@
max-width: 100%;
pointer-events: all;
border-radius: 6px;
.toolbox-content-items {
@include ltr;
}
}
.toolbox-content-wrapper::after {
@@ -134,20 +137,6 @@
}
}
.hangup-menu-button {
background-color: $hangupMenuButtonColor;
@media (hover: hover) and (pointer: fine) {
&:hover {
background-color: $hangupMenuButtonHoverColor;
}
}
svg {
fill: #fff;
}
}
.profile-button-avatar {
align-items: center;
}
@@ -194,7 +183,6 @@
}
.toolbox-content-items {
@include ltr;
border-radius: 0;
display: flex;
justify-content: space-evenly;

View File

@@ -6,8 +6,6 @@
$baseFontFamily: -apple-system, BlinkMacSystemFont, 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
$hangupColor:#DD3849;
$hangupHoverColor: #F25363;
$hangupMenuButtonColor:#0056E0;;
$hangupMenuButtonHoverColor: #246FE5;
$hangupFontSize: 2em;
/**
@@ -15,6 +13,9 @@ $hangupFontSize: 2em;
*/
// Video layout.
$thumbnailToolbarHeight: 22px;
$thumbnailIndicatorBorder: 2px;
$thumbnailIndicatorSize: $thumbnailToolbarHeight;
$thumbnailVideoMargin: 2px;
$thumbnailsBorder: 2px;
$thumbnailVideoBorder: 2px;
@@ -30,6 +31,10 @@ $defaultSemiDarkColor: #ACACAC;
$defaultDarkColor: #2b3d5c;
$defaultWarningColor: rgb(215, 121, 118);
$participantsPaneBgColor: #141414;
$presence-available: rgb(110, 176, 5);
$presence-away: rgb(250, 201, 20);
$presence-busy: rgb(233, 0, 27);
$presence-idle: rgb(172, 172, 172);
/**
* Toolbar
@@ -51,12 +56,19 @@ $overflowMenuItemBackground: #36383C;
/**
* Video layout
*/
$videoThumbnailHovered: rgba(22, 94, 204, .4);
$videoThumbnailSelected: #165ECC;
$participantNameColor: #fff;
$thumbnailPictogramColor: #fff;
$dominantSpeakerBg: #165ecc;
$raiseHandBg: #F8AE1A;
$audioLevelBg: #44A5FF;
$connectionIndicatorBg: #165ecc;
$audioLevelShadow: rgba(9, 36, 77, 0.9);
$videoStateIndicatorColor: $defaultColor;
$videoStateIndicatorBackground: $toolbarBackground;
$videoStateIndicatorSize: 40px;
$remoteVideoMenuIconMargin: initial;
/**
* Feedback Modal
@@ -81,8 +93,6 @@ $modalTextColor: #333;
$chatActionsSeparatorColor: rgb(173, 105, 112);
$chatBackgroundColor: #131519;
$chatInputSeparatorColor: #A4B8D1;
$chatLobbyMessageBackgroundColor: #6A50D3;
$chatLobbyActionsSeparatorColor: #6A50D3;
$chatLocalMessageBackgroundColor: #484A4F;
$chatPrivateMessageBackgroundColor: rgb(153, 69, 77);
$chatRemoteMessageBackgroundColor: #242528;
@@ -92,6 +102,7 @@ $sidebarWidth: 315px;
* Misc.
*/
$borderRadius: 4px;
$popoverMenuPadding: 13px;
$happySoftwareBackground: transparent;
$desktopAppDragBarHeight: 25px;
$scrollHeight: 7px;
@@ -107,6 +118,7 @@ $toolbarBackgroundZ: 4;
$labelsZ: 5;
$subtitlesZ: 7;
$popoverZ: 8;
$zindex10: 10;
$reloadZ: 20;
$poweredByZ: 100;
$ringingZ: 300;

View File

@@ -1,9 +1,9 @@
.video-preview {
background: none;
display: inline-block;
max-height: 344px;
&-container {
max-height: 344px;
background: $menuBG;
border-radius: 3px;
overflow: auto;

View File

@@ -6,7 +6,6 @@
}
#layout_wrapper {
@include ltr;
display: flex;
height: 100%;
}
@@ -44,7 +43,174 @@
.videocontainer {
position: relative;
text-align: center;
overflow: 'hidden';
&__background {
@include topLeft();
background-color: black;
border-radius: $borderRadius;
width: 100%;
height: 100%;
}
/**
* The toolbar of the video thumbnail.
*/
&__toolbar,
&__toptoolbar {
position: absolute;
left: 0;
pointer-events: none;
z-index: $zindex10;
width: 100%;
box-sizing: border-box; // Includes the padding in the 100% width.
/**
* FIXME (lenny): Disabling pointer-events is a pretty big sin that
* sidesteps the problems. There are z-index wars occurring within
* videocontainer and AtlasKit Tooltips rely on their parent z-indexe
* being higher than whatever they need to appear over. So set a higher
* z-index for the tooltip containers but make any empty space not block
* mouse overs for various mouseover triggers.
*/
pointer-events: none;
* {
pointer-events: auto;
}
.indicator-container {
display: inline-block;
float: left;
pointer-events: all;
}
}
&__toolbar {
bottom: 0;
padding: 0 5px 0 5px;
}
&__toptoolbar {
$toolbarIconMargin: 5px;
top: 0;
padding-bottom: 0;
/**
* Override text-align center as icons need to be left justified.
*/
text-align: left;
/**
* Intentionally use margin on the icon itself as AtlasKit InlineDialog
* positioning depends on the trigger (indicator icon).
*/
.indicator {
margin-left: 5px;
margin-top: $toolbarIconMargin;
}
.indicator-container:nth-child(1) .indicator {
margin-left: $toolbarIconMargin;
}
.indicator-container {
display: inline-block;
vertical-align: top;
.popover-trigger {
display: inline-block;
}
}
.connection-indicator,
.indicator {
position: relative;
font-size: 8px;
text-align: center;
line-height: $thumbnailIndicatorSize;
padding: 0;
@include circle($thumbnailIndicatorSize);
box-sizing: border-box;
z-index: $zindex3;
background: $dominantSpeakerBg;
color: $thumbnailPictogramColor;
border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor;
.indicatoricon {
@include absoluteAligning();
}
.connection {
position: relative;
display: inline-block;
margin: 0 auto;
left: 0;
@include transform(translate(0, -50%));
&_empty,
&_lost
{
color: #8B8B8B;/*#FFFFFF*/
overflow: hidden;
}
&_full
{
@include topLeft();
color: #FFFFFF;/*#15A1ED*/
overflow: hidden;
}
&_ninja
{
font-size: 1.5em;
}
}
.icon-gsm-bars {
cursor: pointer;
font-size: 1em;
}
}
.hide-connection-indicator {
display: none;
}
}
&__hoverOverlay {
background: rgba(0,0,0,.6);
border-radius: $borderRadius;
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
visibility: hidden;
z-index: $zindex2;
}
&__participant-name {
color: #fff;
background-color: rgba(0,0,0,.4);
padding: 3px 7px;
border-radius: 3px;
max-width: calc(100% - 32px);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
height: 16px;
display: inline-block;
text-align: right;
}
@media (min-width: 581px) {
&.shift-right {
&#largeVideoContainer {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
}
}
}
}
#localVideoWrapper {
@@ -70,10 +236,6 @@
#largeVideoContainer {
overflow: hidden;
text-align: center;
&.transition {
transition: width 1s, height 1s, top 1s;
}
}
#largeVideoContainer {
@@ -126,6 +288,16 @@
z-index: $zindex0;
}
/**
* Positions video thumbnail display name and editor.
*/
#alwaysOnTop .displayname,
.videocontainer .displayname,
.videocontainer .editdisplayname {
font-weight: 100;
color: $participantNameColor;
}
#alwaysOnTop .displayname {
font-size: 15px;
position: inherit;
@@ -135,6 +307,146 @@
margin-top: 10px;
}
/**
* Positions video thumbnail display name editor.
*/
.videocontainer .editdisplayname {
outline: none;
border: none;
background: none;
box-shadow: none;
padding: 0;
}
#localVideoContainer .displayname:hover {
cursor: text;
}
.videocontainer .displayname {
pointer-events: none;
padding: 0 3px 0 3px;
}
.videocontainer .editdisplayname {
height: auto;
}
#localDisplayName {
pointer-events: auto !important;
}
.videocontainer>a.displayname {
display: inline-block;
position: absolute;
color: #FFFFFF;
bottom: 0;
right: 0;
padding: 3px 5px;
font-size: 9pt;
cursor: pointer;
z-index: $zindex2;
}
/**
* Video thumbnail toolbar icon.
*/
.videocontainer .toolbar-icon {
font-size: 8pt;
text-align: center;
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
color: #FFFFFF;
width: 12px;
line-height: $thumbnailToolbarHeight;
height: $thumbnailToolbarHeight;
padding: 0;
border: 0;
margin: 0px 5px 0px 0px;
}
/**
* Toolbar icon internal i elements (font icons).
*/
.toolbar-icon>div {
height: $thumbnailToolbarHeight;
display: flex;
flex-direction: column;
justify-content: center;
}
/**
* Toolbar icons positioned on the right.
*/
.moderator-icon {
display: inline-block;
&.right {
float: right;
margin: 0px 0px 0px 5px;
}
.toolbar-icon {
margin: 0;
}
}
.raisehandindicator {
background: $raiseHandBg !important;
}
.connection-indicator {
background: $connectionIndicatorBg;
&.status-high {
background: green;
}
&.status-med {
background: #FFD740;
}
&.status-lost {
background: gray;
}
&.status-low {
background: #BF2117;
}
&.status-other {
background: $connectionIndicatorBg;
}
&.status-disabled {
background: transparent;
border: none
}
}
.local-video-menu-trigger,
.remote-video-menu-trigger,
.localvideomenu,
.remotevideomenu
{
display: inline-block;
position: absolute;
top: 0px;
right: 0;
z-index: $zindex2;
width: 18px;
height: 18px;
color: #FFF;
font-size: 10pt;
margin-right: $remoteVideoMenuIconMargin;
>i{
cursor: hand;
}
}
.local-video-menu-trigger,
.remote-video-menu-trigger {
margin-top: 7px;
}
/**
* Audio indicator on video thumbnails.
*/
@@ -311,11 +623,74 @@
display: none;
}
.display-avatar-with-name {
.avatar-container {
visibility: visible;
}
.displayNameContainer {
visibility: visible;
}
.videocontainer__hoverOverlay {
visibility: visible;
}
video {
visibility: hidden;
}
}
.display-name-on-black {
.avatar-container {
visibility: hidden;
}
.displayNameContainer {
visibility: visible;
}
.videocontainer__hoverOverlay {
visibility: hidden;
}
video {
opacity: 0.2;
visibility: visible;
}
}
.display-video {
.avatar-container {
visibility: hidden;
}
.displayNameContainer {
visibility: hidden;
}
.videocontainer__hoverOverlay {
visibility: hidden;
}
video {
visibility: visible;
}
}
.display-name-on-video {
.avatar-container {
visibility: hidden;
}
.displayNameContainer {
visibility: visible;
}
.videocontainer__hoverOverlay {
visibility: visible;
}
video {
visibility: visible;
}
@@ -326,6 +701,14 @@
visibility: visible;
}
.displayNameContainer {
visibility: hidden;
}
.videocontainer__hoverOverlay {
visibility: hidden;
}
video {
visibility: hidden;
}

View File

@@ -0,0 +1,68 @@
.select2-container.aui-select2-container {
background-color: transparent !important;
margin-top: 2px;
a.select2-choice {
height: 28px !important;
line-height: 18px !important;
width: 100% !important;
background-color: $selectBg !important;
border-color: $selectBg !important;
color: $selectFontColor !important;
text-shadow: none !important;
font-size: 12px !important;
margin: 0 auto !important;
&:after {
border-top-color: $selectFontColor;
}
}
&.select2-dropdown-open{
a.select2-choice {
background-color: $selectActiveBg !important;
border-color: $selectActiveBg !important;
}
}
}
.select2-drop.aui-select2-drop.aui-style-default {
z-index: $dropdownZ;
background-color: $selectActiveBg;
border-color: $selectActiveBg;
.select2-results{
background-color: $selectActiveBg;
border-color: $selectActiveBg;
&::-webkit-scrollbar {
background-color: transparent;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-track-piece {
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: $selectActiveItemBg;
}
.select2-result{
&.select2-highlighted{
background-color: $selectActiveItemBg;
}
.select2-result-label{
font-size: 12px;
color: $selectFontColor !important;
line-height: 20px;
}
}
}
}
.select2-drop-mask {
z-index: $dropdownMaskZ;
}

View File

@@ -68,12 +68,6 @@
}
.buttons {
margin-top: 16px;
display: flex;
align-items: center;
&>button:first-child {
margin-right: 8px;
}
}
}
}

View File

@@ -0,0 +1,30 @@
.filmstrip__toolbar {
@include flex();
flex-direction: column-reverse;
flex-wrap: nowrap;
position: relative;
width: $filmstripToggleButtonWidth;
button {
font-size: 14px;
line-height: 1.2;
text-align: center;
background: transparent;
opacity: 0.7;
height: auto;
width: 100%;
padding: 0;
margin: 0;
border: none;
-webkit-appearance: none;
&:hover {
opacity: 1;
}
i {
cursor: pointer;
}
}
}

View File

@@ -6,8 +6,11 @@
}
.horizontal-filmstrip .filmstrip {
bottom: 0;
right: 0;
padding: 10px 5px;
@extend %align-right;
transition: bottom .3s;
z-index: $filmstripVideosZ;
box-sizing: border-box;
width: 100%;
@@ -48,15 +51,14 @@
/**
* The local video identifier.
*/
&#filmstripLocalVideo,
&#filmstripLocalScreenShare {
&#filmstripLocalVideo {
align-self: flex-end;
display: block;
margin-bottom: 8px;
}
&.hidden {
bottom: calc(-196px - #{$newToolbarSizeWithPadding} + 50px);
bottom: calc(-196px - #{$newToolbarSizeWithPadding});
}
}

View File

@@ -6,10 +6,37 @@
border-radius: $borderRadius;
margin: 0 $thumbnailVideoMargin;
&:hover {
&.videoContainerFocused, &:hover {
cursor: hand;
}
/**
* Focused video thumbnail.
*/
&.videoContainerFocused {
border: $thumbnailVideoBorder solid $videoThumbnailSelected;
box-shadow: inset 0 0 3px $videoThumbnailSelected,
0 0 3px $videoThumbnailSelected;
}
.remotevideomenu > .icon-menu, .localvideomenu > .icon-menu {
display: none;
}
/**
* Hovered video thumbnail.
*/
&:hover:not(.videoContainerFocused):not(.active-speaker) {
cursor: hand;
border: $thumbnailVideoBorder solid $videoThumbnailHovered;
box-shadow: inset 0 0 3px $videoThumbnailHovered,
0 0 3px $videoThumbnailHovered;
.remotevideomenu > .icon-menu, .localvideomenu > .icon-menu {
display: inline-block;
}
}
& > video {
cursor: hand;
border-radius: $borderRadius;

View File

@@ -2,9 +2,19 @@
* CSS styles that are specific to the filmstrip that shows the thumbnail tiles.
*/
.tile-view {
/**
* Add a border around the active speaker to make the thumbnail easier to
* see.
*/
.active-speaker {
box-shadow: 0px 0px 1px 1.5px black, 0px 0px 1.3px 4px $videoThumbnailSelected;
}
.remote-videos {
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
overscroll-behavior: contain;
}
@@ -39,6 +49,17 @@
top: 0;
width: 100%;
@media (min-width: 581px) {
&.shift-right {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
.remote-videos {
width: calc(100vw - #{$sidebarWidth});
}
}
}
&.collapse {
#remoteVideos {
height: calc(100% - #{$newToolbarSizeMobile}) !important;
@@ -60,10 +81,6 @@
display: block;
}
.filmstrip__videos.has-scroll {
padding-left: 7px;
}
.remote-videos {
box-sizing: border-box;
@@ -81,6 +98,7 @@
margin-top: auto;
margin-bottom: auto;
justify-content: center;
position: absolute;
.videocontainer {
border: 0;
@@ -88,6 +106,19 @@
display: block;
margin: 2px;
}
video {
object-fit: contain;
}
/**
* Max-width corresponding to the ASPECT_RATIO_BREAKPOINT from features/filmstrip/constants.
*/
@media only screen and (max-width: 500px) {
video {
object-fit: cover;
}
}
}
}
}
@@ -103,3 +134,7 @@
}
}
}
.indicator-icon-container {
display: inline-block;
}

View File

@@ -2,9 +2,7 @@
* Various overrides outside of the filmstrip to style the app to support a
* tiled thumbnail experience.
*/
.tile-view,
.whiteboard-container,
.stage-filmstrip {
.tile-view {
/**
* Let the avatar grow with the tile.
*/
@@ -17,9 +15,10 @@
* Hide various features that should not be displayed while in tile view.
*/
#dominantSpeaker,
#filmstripLocalVideoThumbnail,
#largeVideoElementsContainer,
#sharedVideo,
.stage-participant-label {
.filmstrip__toolbar {
display: none;
}
@@ -36,4 +35,16 @@
#remotePresenceMessage {
display: none !important;
}
/**
* Thumbnail popover menus can overlap other thumbnails. Setting an auto
* z-index will allow AtlasKit InlineDialog's large z-index to be
* respected and thereby display over elements in other thumbnails,
* specifically the various status icons.
*/
.remotevideomenu,
.localvideomenu,
.videocontainer__toptoolbar {
z-index: auto;
}
}

View File

@@ -1,177 +1,128 @@
.vertical-filmstrip, .stage-filmstrip {
span:not(.tile-view) .filmstrip {
&.hide-videos {
.remote-videos {
& > div {
opacity: 0;
pointer-events: none;
}
}
}
/*
* Firefox sets flex items to min-height: auto and min-width: auto,
* preventing flex children from shrinking like they do on other browsers.
* Setting min-height and min-width 0 is a workaround for the issue so
* Firefox behaves like other browsers.
* https://bugzilla.mozilla.org/show_bug.cgi?id=1043520
*/
@mixin minHWAutoFix() {
min-height: 0;
min-width: 0;
}
@extend %align-right;
align-items: flex-end;
bottom: 0;
box-sizing: border-box;
display: flex;
flex-direction: column-reverse;
height: 100%;
width: 100%;
padding: 0;
/**
* fixed positioning is necessary for remote menus and tooltips to pop
* out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
* a library called popper which will position its elements fixed if
* any parent is also fixed.
*/
position: fixed;
top: 0;
right: 0;
z-index: $filmstripVideosZ;
&.no-vertical-padding {
padding: 0;
}
/**
* Hide videos by making them slight to the right.
*/
.filmstrip__videos {
@extend %align-right;
bottom: 0;
padding: 0;
position:relative;
right: 0;
width: auto;
/**
* An id selector is used to match id specificity with existing
* filmstrip styles.
*/
&#remoteVideos {
border: $thumbnailsBorder solid transparent;
padding-left: 0;
border-left: 0;
width: 100%;
height: 100%;
justify-content: center;
}
}
/**
* Re-styles the local Video to better fit vertical filmstrip layout.
*/
#filmstripLocalVideo {
align-self: initial;
margin-bottom: 5px;
display: flex;
flex-direction: column-reverse;
height: auto;
justify-content: flex-start;
width: 100%;
#filmstripLocalVideoThumbnail {
width: calc(100% - 15px);
.videocontainer {
height: 0px;
width: 100%;
}
}
}
#filmstripLocalScreenShare {
align-self: initial;
margin-bottom: 5px;
display: flex;
flex-direction: column-reverse;
height: auto;
justify-content: flex-start;
width: 100%;
#filmstripLocalScreenShareThumbnail {
width: calc(100% - 15px);
.videocontainer {
height: 0px;
width: 100%;
}
}
}
/**
* Remove unnecessary padding that is normally used to prevent horizontal
* filmstrip from overlapping the left edge of the screen.
*/
#filmstripLocalVideo,
#filmstripLocalScreenShare,
.vertical-filmstrip .filmstrip {
&.hide-videos {
.remote-videos {
padding: 0;
}
#remoteVideos {
@include minHWAutoFix();
flex-direction: column;
flex-grow: 1;
}
.resizable-filmstrip #remoteVideos .videocontainer {
border-left: 0;
margin: 0;
}
&.reduce-height {
height: calc(100% - calc(#{$newToolbarSizeWithPadding} + #{$scrollHeight}));
}
.filmstrip__videos.vertical-view-grid#remoteVideos {
align-items: 'center';
border: 0px;
padding-right: 7px;
&.has-scroll {
padding-right: 0px;
}
.remote-videos > div {
left: 0px; // fixes an issue on FF - the div is aligned to the right by default for some reason
}
.videocontainer {
border: 0px;
margin: 2px;
}
}
.remote-videos {
display: flex;
overscroll-behavior: contain;
&.height-transition {
transition: height .3s ease-in;
}
& > div {
position: absolute;
transition: opacity 1s;
}
&.is-not-overflowing > div {
bottom: 0px;
opacity: 0;
pointer-events: none;
}
}
}
/*
* Firefox sets flex items to min-height: auto and min-width: auto,
* preventing flex children from shrinking like they do on other browsers.
* Setting min-height and min-width 0 is a workaround for the issue so
* Firefox behaves like other browsers.
* https://bugzilla.mozilla.org/show_bug.cgi?id=1043520
*/
@mixin minHWAutoFix() {
min-height: 0;
min-width: 0;
}
@extend %align-right;
align-items: flex-end;
bottom: 0;
box-sizing: border-box;
display: flex;
flex-direction: column-reverse;
height: 100%;
width: 100%;
padding: ($desktopAppDragBarHeight - 5px) 5px calc(env(safe-area-inset-bottom, 0) + 10px);
/**
* fixed positioning is necessary for remote menus and tooltips to pop
* out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
* a library called popper which will position its elements fixed if
* any parent is also fixed.
*/
position: fixed;
top: 0;
transition: height .3s ease-in;
right: 0;
z-index: $filmstripVideosZ;
/**
* Hide videos by making them slight to the right.
*/
.filmstrip__videos {
@extend %align-right;
bottom: 0;
padding: 0;
position:relative;
right: 0;
width: auto;
&.hidden {
bottom: auto;
right: -196px;
}
/**
* An id selector is used to match id specificity with existing
* filmstrip styles.
*/
&#remoteVideos {
border: $thumbnailsBorder solid transparent;
padding-left: 0;
transition: right 2s;
width: 100%;
}
}
/**
* Re-styles the local Video to better fit vertical filmstrip layout.
*/
#filmstripLocalVideo {
align-self: initial;
bottom: 5px;
display: flex;
flex-direction: column-reverse;
height: auto;
justify-content: flex-start;
#filmstripLocalVideoThumbnail {
width: calc(100% - 15px);
.videocontainer {
height: 0px;
width: 100%;
}
}
}
/**
* Remove unnecssary padding that is normally used to prevent horizontal
* filmstrip from overlapping the left edge of the screen.
*/
#filmstripLocalVideo,
.remote-videos {
padding: 0;
}
#remoteVideos {
@include minHWAutoFix();
flex-direction: column;
flex-grow: 1;
}
&.reduce-height {
height: calc(100% - calc(#{$newToolbarSizeWithPadding} + #{$scrollHeight}));
}
.remote-videos {
display: flex;
transition: height .3s ease-in;
overscroll-behavior: contain;
& > div {
position: absolute;
transition: opacity 1s;
}
&.is-not-overflowing > div {
bottom: 0px;
}
}
}

View File

@@ -1,19 +1,90 @@
/**
* Rotate the hide filmstrip icon so it points towards the right edge
* of the screen.
*/
.vertical-filmstrip .filmstrip__toolbar {
transform: rotate(-90deg);
}
/**
* Overrides for video containers that should not be centered aligned to avoid=
* clashing with the filmstrip.
*/
.vertical-filmstrip #etherpad,
.stage-filmstrip #etherpad,
.vertical-filmstrip #sharedvideo,
.stage-filmstrip #sharedvideo {
.vertical-filmstrip #sharedvideo {
text-align: left;
}
/**
* Overrides for small videos in vertical filmstrip mode.
*/
.vertical-filmstrip .filmstrip__videos .videocontainer,
.stage-filmstrip .filmstrip__videos .videocontainer {
.vertical-filmstrip .filmstrip__videos .videocontainer {
/**
* Move status icons to the bottom right of the thumbnail.
*/
.videocontainer__toolbar {
/**
* FIXME: disable pointer to allow any elements moved below to still
* be clickable. The real fix would to make sure those moved elements
* are actually part of the toolbar instead of positioning being faked.
*/
pointer-events: none;
text-align: right;
> div {
pointer-events: none;
}
.right {
float: none;
margin: auto;
}
.toolbar-icon {
pointer-events: all;
}
}
/**
* Apply hardware acceleration to prevent flickering on scroll. The
* selectors are specific to icon wrappers to prevent fixed position dialogs
* and tooltips from getting a new location context due to translate3d.
*/
.connection-indicator,
.local-video-menu-trigger,
.remote-video-menu-trigger,
.indicator-icon-container {
transform: translate3d(0, 0, 0);
}
.indicator-icon-container {
display: inline-block;
}
.indicator-container {
float: none;
}
/**
* Move the remote video menu trigger to the bottom left of the video
* thumbnail.
*/
.localvideomenu,
.remotevideomenu,
.local-video-menu-trigger,
.remote-video-menu-trigger {
bottom: 0;
left: 0;
top: auto;
right: auto;
}
.local-video-menu-trigger,
.remote-video-menu-trigger {
margin-bottom: 3px;
margin-left: $remoteVideoMenuIconMargin;
}
.self-view-mobile-portrait video {
object-fit: contain;
}
@@ -30,8 +101,7 @@
* The class opening is for when the filmstrip is transitioning from hidden
* to visible.
*/
.vertical-filmstrip .large-video-labels,
.stage-filmstrip .large-video-labels {
.vertical-filmstrip .large-video-labels {
&.with-filmstrip {
right: 150px;
}
@@ -51,7 +121,6 @@
* Overrides for self view when in portrait mode on mobile.
* This is done in order to keep the aspect ratio.
*/
.vertical-filmstrip .self-view-mobile-portrait #localVideo_container,
.stage-filmstrip .self-view-mobile-portrait #localVideo_container {
.vertical-filmstrip .self-view-mobile-portrait #localVideo_container {
object-fit: contain;
}

View File

@@ -24,7 +24,8 @@ $flagsImagePath: "../images/";
/* Flags END */
/* Modules BEGIN */
@import 'reset';
@import 'aui_reset';
@import 'atlaskit_overrides';
@import 'base';
@import 'utils';
@@ -38,8 +39,12 @@ $flagsImagePath: "../images/";
@import 'modals/embed-meeting/embed-meeting';
@import 'modals/feedback/feedback';
@import 'modals/invite/info';
@import 'modals/settings/settings';
@import 'modals/screen-share/share-audio';
@import 'modals/screen-share/share-screen-warning';
@import 'modals/speaker_stats/speaker_stats';
@import 'modals/virtual-background/virtual-background';
@import 'modals/local-recording/local-recording';
@import 'videolayout_default';
@import 'notice';
@import 'subject';
@@ -52,15 +57,19 @@ $flagsImagePath: "../images/";
@import 'welcome_page_content';
@import 'welcome_page_settings_toolbar';
@import 'toolbars';
@import 'keyboard-shortcuts';
@import 'redirect_page';
@import 'components/form-control';
@import 'components/link';
@import 'components/button-control';
@import 'components/input-control';
@import 'components/input-slider';
@import "connection-info";
@import 'aui-components/dropdown';
@import '404';
@import 'policy';
@import 'popover';
@import 'filmstrip/filmstrip_toolbar';
@import 'filmstrip/horizontal_filmstrip';
@import 'filmstrip/small_video';
@import 'filmstrip/tile_view';
@@ -75,6 +84,7 @@ $flagsImagePath: "../images/";
@import 'navigate_section_list';
@import 'third-party-branding/google';
@import 'third-party-branding/microsoft';
@import 'avatar';
@import 'promotional-footer';
@import 'chrome-extension-banner';
@import 'settings-button';
@@ -85,6 +95,7 @@ $flagsImagePath: "../images/";
@import 'country-picker';
@import 'modals/invite/invite_more';
@import 'modals/security/security';
@import 'modals/mute/mute-dialog';
@import 'e2ee';
@import 'responsive';
@import 'drawer';

View File

@@ -1,3 +1,114 @@
.dialog {
box-sizing: border-box;
height: auto;
min-height: 131px;
overflow: visible;
visibility: visible;
width: 400px;
h1, h2, h3, h4, h5, h6 {
color: $auiDialogColor;
}
.aui {
&-dialog2 {
&-header, &-footer {
background-color: $auiDialogBg;
border: none;
}
&-header {
border-bottom: 1px solid $auiBorderColor;
border-radius: 5px 5px 0 0;
box-sizing: border-box;
color: #333;
display: table;
font-weight: normal;
height: em(58, 12);
margin-top: -69px;
padding: 0 20px;
width: 100%;
h2 {
font-size: em(20, 12);
font-weight: $dialogTitleFontWeight;
color: $auiDialogColor;
}
&-main {
display: table-cell;
padding-right: 0;
max-width: 400px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
}
}
&-footer {
border-top: 1px solid $auiBorderColor;
border-radius: 0 0 5px 5px;
box-sizing: border-box;
height: 51px;
overflow: hidden;
padding: 10px 20px;
width: 100%;
&:empty {
height: 5px;
padding: 0;
}
}
&-content {
background-color: $auiDialogBg;
box-sizing: border-box;
color: $auiDialogColor;
font-size: em(14, 12);
overflow: auto;
max-height: 100%;
padding: 20px;
p,span, h3 {
font-weight: $labelFontWeight;
}
&:last-child {
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
}
&:first-child {
border-top-right-radius: 5px;
border-top-left-radius: 5px;
}
}
}
&-hide {
display: none;
}
}
.input-control {
background-color: $auiDialogContentBg;
color: $auiDialogColor;
}
.form-control:not(:last-child) {
border-bottom: 1px solid $auiBorderColor;
}
}
@media all and (max-width: 420px) {
.aui-dialog2-small .aui-dialog2-content {
height: 100%;
}
}
.modal-dialog-form {
margin-top: 5px !important;
@@ -29,12 +140,3 @@
margin: 16px auto 0 auto;
}
}
/**
* Styling shared video dialog errors.
*/
.shared-video-dialog-error {
color: #E04757;
margin-top: 2px;
display: block;
}

View File

@@ -46,8 +46,6 @@
}
.feedback-dialog {
margin-bottom: 5px;
.details {
textarea {
min-height: 100px;

View File

@@ -38,6 +38,19 @@
color: #fff;
}
.info-password-input {
width: 100%;
background-color: #0E1624;
border-radius: 3px;
border: 2px solid #202B3D;
color: inherit;
padding-left: 0;
}
.info-password-input:focus ,
.info-password-input:active {
border: 2px solid #B8C7E0;
}
.info-password-local {
user-select: text;
}

View File

@@ -0,0 +1,92 @@
.localrec-participant-stats {
list-style: none;
padding: 0;
width: 100%;
font-weight: 500;
.localrec-participant-stats-item__status-dot {
position: relative;
display: block;
width: 9px;
height: 9px;
border-radius: 50%;
margin: 0 auto;
&.status-on {
background: green;
}
&.status-off {
background: gray;
}
&.status-unknown {
background: darkgoldenrod;
}
&.status-error {
background: darkred;
}
}
.localrec-participant-stats-item__status,
.localrec-participant-stats-item__name,
.localrec-participant-stats-item__sessionid {
display: inline-block;
margin: 5px 0;
vertical-align: middle;
}
.localrec-participant-stats-item__status {
width: 5%;
}
.localrec-participant-stats-item__name {
width: 40%;
}
.localrec-participant-stats-item__sessionid {
width: 55%;
}
.localrec-participant-stats-item__name,
.localrec-participant-stats-item__sessionid {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.localrec-control-info-label {
font-weight: bold;
}
.localrec-control-info-label:after {
content: ' ';
}
.localrec-control-action-link {
display: inline-block;
line-height: 1.5em;
a {
cursor: pointer;
vertical-align: middle;
}
}
.localrec-control-action-link:before {
color: $linkFontColor;
content: '\2022';
font-size: 1.5em;
padding: 0 10px;
vertical-align: middle;
}
.localrec-control-action-link:first-child:before {
content: '';
padding: 0;
}
.localrec-control-action-links {
font-weight: bold;
margin-top: 10px;
white-space: nowrap;
}

View File

@@ -0,0 +1,19 @@
.mute-dialog {
.separator-line {
margin: 24px 0 24px -20px;
padding: 0 20px;
width: 100%;
height: 1px;
background: #5E6D7A;
}
.control-row {
display: flex;
justify-content: space-between;
margin-top: 15px;
label {
font-size: 14px;
}
}
}

View File

@@ -1,13 +1,11 @@
.share-audio-dialog-container {
max-height: none;
}
.share-audio-dialog {
.share-audio-animation {
width: 100%;
height: 90%;
object-fit: contain;
margin-bottom: 10px;
}
input[type="checkbox"] + svg + span {
color: #9FB0CC;
}
.separator-line {

View File

@@ -13,14 +13,12 @@
}
.password {
align-items: flex-start;
align-items: center;
display: flex;
justify-content: flex-start;
justify-content: space-between;
margin-top: 15px;
flex-direction: column;
&-actions {
margin-top: 10px;
a {
cursor: pointer;
text-decoration: none;
@@ -28,8 +26,8 @@
color: #6FB1EA;
}
& > :not(:last-child) {
margin-right: 24px;
& > :first-child:not(:last-child) {
margin-right: 24px;
}
}
}

View File

@@ -0,0 +1,102 @@
.settings-pane {
display: flex;
width: 100%;
&.profile-pane {
flex-direction: column;
}
.auth-name {
margin-bottom: 4px;
}
.calendar-tab,
.device-selection {
margin-top: 20px;
}
.mock-atlaskit-label {
color: #b8c7e0;
font-size: 12px;
font-weight: 600;
line-height: 1.33;
padding: 20px 0px 4px 0px;
}
input[type="checkbox"]:checked + svg {
--checkbox-background-color: #6492e7;
--checkbox-border-color: #6492e7;
}
input[type="checkbox"] + svg + span {
color: #b8c7e0;
}
input[type="checkbox"] + svg + span {
color: #9FB0CC;
}
.calendar-tab,
.more-tab,
.box {
display: flex;
justify-content: space-between;
width: 100%;
}
.profile-edit {
display: flex;
width: 100%;
}
.profile-edit-field {
flex: 1;
}
.settings-sub-pane {
flex: 1;
}
.settings-sub-pane .right {
flex: 1;
}
.settings-sub-pane .left {
flex: 1;
}
.settings-sub-pane-element {
text-align: left;
flex: 1;
}
.moderator-settings-wrapper {
padding-top: 20px;
}
.profile-edit-field {
margin-right: 20px;
}
.calendar-tab {
align-items: center;
flex-direction: column;
font-size: 14px;
min-height: 100px;
text-align: center;
}
.calendar-tab-sign-in {
margin-top: 20px;
}
.sign-out-cta {
margin-bottom: 20px;
}
@media only screen and (max-width: $smallScreen) {
.device-selection {
display: flex;
flex-direction: column;
}
.more-tab {
flex-direction: column;
}
}
}

View File

@@ -0,0 +1,80 @@
.speaker-stats {
list-style: none;
padding: 0;
width: 100%;
font-weight: 500;
.speaker-stats-item__status-dot {
position: relative;
display: block;
width: 9px;
height: 9px;
border-radius: 50%;
margin: 0 auto;
&.status-active {
background: green;
}
&.status-inactive {
background: gray;
}
}
.status-user-left {
color: $placeHolderColor;
}
.speaker-stats-item__status,
.speaker-stats-item__name,
.speaker-stats-item__time,
.speaker-stats-item__name_expressions_on,
.speaker-stats-item__time_expressions_on,
.speaker-stats-item__expression {
display: inline-block;
margin: 5px 0;
vertical-align: middle;
}
.speaker-stats-item__status {
width: 5%;
}
.speaker-stats-item__name {
width: 40%;
}
.speaker-stats-item__time {
width: 55%;
}
.speaker-stats-item__name_expressions_on {
width: 20%;
}
.speaker-stats-item__time_expressions_on {
width: 25%;
}
.speaker-stats-item__expression {
width: 7%;
text-align: center;
}
@media(max-width: 750px) {
.speaker-stats-item__name_expressions_on {
width: 25%;
}
.speaker-stats-item__time_expressions_on {
width: 30%;
}
.speaker-stats-item__expression {
width: 10%;
}
}
.speaker-stats-item__name,
.speaker-stats-item__time,
.speaker-stats-item__name_expressions_on,
.speaker-stats-item__time_expressions_on,
.speaker-stats-item__expression {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

View File

@@ -0,0 +1,203 @@
.virtual-background-dialog {
margin-left: -10px;
position: relative;
max-height: 300px;
color: white;
display: inline-grid;
grid-template-columns: auto auto auto auto auto;
column-gap: 9px;
cursor: pointer;
.desktop-share:hover,
.thumbnail:hover,
.blur:hover,
.slight-blur:hover,
.virtual-background-none:hover {
opacity: 0.5;
border: 2px solid #99bbf3;
@media (max-width: 632px) {
height: 60px;
width: 60px;
}
}
.background-option {
margin-top: 8px;
border-radius: 6px;
height: 60px;
width: 107px;
text-align: center;
justify-content: center;
font-weight: bold;
box-sizing: border-box;
display: flex;
align-items: center;
}
.thumbnail {
object-fit: cover;
}
.thumbnail:hover ~ .delete-image-icon {
display: block;
}
.thumbnail-selected {
object-fit: cover;
border: 2px solid #246fe5;
}
.blur {
box-shadow: inset 0 0 12px #000000;
background: #7e8287;
padding: 0 10px;
}
.blur-selected {
box-shadow: inset 0 0 12px #000000;
background: #7e8287;
border: 2px solid #246fe5;
padding: 0 10px;
}
.slight-blur {
box-shadow: inset 0 0 12px #000000;
background: #a4a4a4;
padding: 0 10px;
}
.slight-blur-selected {
box-shadow: inset 0 0 12px #000000;
background: #a4a4a4;
border: 2px solid #246fe5;
padding: 0 10px;
}
.virtual-background-none {
background: #525252;
padding: 0 10px;
}
.none-selected {
background: #525252;
border: 2px solid #246fe5;
padding: 0 10px;
}
.desktop-share {
background: #525252;
}
.desktop-share-selected {
background: #525252;
border: 2px solid #246fe5;
padding: 0 10px;
}
@media (max-width: 632px) {
font-size: 1.5vw;
.desktop-share,
.virtual-background-none,
.thumbnail,
.blur,
.slight-blur {
height: 60px;
width: 60px;
}
.desktop-share-selected,
.thumbnail-selected,
.none-selected,
.blur-selected,
.slight-blur-selected {
height: 60px;
width: 60px;
}
}
@media (max-width: 360px) {
grid-template-columns: auto auto auto;
}
}
.modal-dialog-form .virtual-background-loading {
overflow: hidden;
position: fixed;
left: 50%;
margin-top: 10px;
transform: translateX(-50%);
}
.modal-dialog-form .video-preview {
height: 250px;
}
.file-upload-btn {
display: none;
}
.file-upload-label {
font-size: 14px;
font-weight: 600;
line-height: 20px;
margin-left: -10px;
margin-top: 16px;
margin-bottom: 8px;
color: #669aec;
display: inline-flex;
cursor: pointer;
}
.delete-image-icon {
background: #3d3d3d;
position: absolute;
display: none;
left: 96;
bottom: 51;
@media (max-width: 632px) {
left: 51px;
}
}
.delete-image-icon:hover {
display: block;
}
.thumbnail-container {
position: relative;
&:focus-within {
.thumbnail ~ .delete-image-icon {
display: block;
}
}
}
.add-background {
margin-right: 8px;
}
.apply-background-btn {
margin-top: 16px;
float: right;
}
.video-background-preview-entry {
margin-left: -10px;
height: 250px;
width: 570px;
margin-bottom: 8px;
z-index: 2;
@media (max-width: 632px) {
max-width: 336;
}
}
.virtual-background-preview-video {
margin-left: -10;
border-radius: 6px;
height: 100%;
object-fit: cover;
width: 100%;
}
.video-preview-loader {
border-radius: 6px;
background-color: transparent;
height: 250px;
margin-bottom: 8px;
width: 572px;
position: fixed;
z-index: 2;
@media (min-width: 432px) and (max-width: 632px) {
width: 340px;
}
}
.video-preview-loader svg {
position: absolute;
top: 40%;
left: 45%;
}

View File

@@ -0,0 +1,71 @@
.con-status {
border-radius: 6px;
color: #fff;
font-size: 12px;
letter-spacing: 0.16px;
line-height: 16px;
position: absolute;
width: 100%;
&-header {
background-color: rgba(0, 0, 0, 0.7);
align-items: center;
display: flex;
padding: 14px 16px;
}
&-circle {
border-radius: 50%;
display: inline-block;
padding: 4px;
margin-right: 16px;
}
&--good {
background: #31B76A;
}
&--poor {
background: #E12D2D;
}
&--non-optimal {
background: #E39623;
}
&-arrow {
margin-left: auto;
transition: background-color 0.16s ease-out;
&--up {
transform: rotate(180deg);
}
&>svg {
cursor: pointer;
}
&:hover {
background-color: rgba(1,1,1, 0.1);
}
}
&-text {
text-align: center;
}
&-details {
background-color: rgba(0, 0, 0, 0.7);
border-top: 1px solid #5E6D7A;
padding: 16px;
transition: opacity 0.16s ease-out;
&-visible {
opacity: 1;
}
&-hidden {
opacity: 0;
}
}
}

View File

@@ -0,0 +1,38 @@
.device {
&-status {
align-items: center;
color: #fff;
display: flex;
font-size: 14px;
line-height: 20px;
padding: 6px;
text-align: center;
&-error {
align-items: flex-start;
background-color: #F8AE1A;
border-radius: 6px;
color: #040404;
padding: 12px 16px;
text-align: left;
}
span {
margin-left: 16px;
}
}
&-icon {
background-position: center;
background-repeat: no-repeat;
display: inline-block;
height: 16px;
width: 16px;
&--ok {
svg path {
fill: #189b55;
}
}
}
}

View File

@@ -12,29 +12,11 @@
margin: 8px;
}
.lobby-chat-container {
background-color: $chatBackgroundColor;
width: 100%;
height: 314px;
display: flex;
flex-direction: column;
align-items: stretch;
margin-bottom: 16px;
border-radius: 5px;
.lobby-chat-header {
display: none;
}
}
.joining-message {
color: white;
margin: 24px auto;
text-align: center;
}
.open-chat-button {
display: none;
}
}
}
@@ -81,10 +63,6 @@
top: 120px;
}
&.avoid-chat {
left: 315px;
}
.title {
background-color: rgba(0, 0, 0, .2);
font-size: 1.2em;
@@ -165,42 +143,3 @@
}
}
}
@media (max-width: 1000px) {
.lobby-screen-content {
.lobby-chat-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 255;
&.hidden {
display: none;
}
.lobby-chat-header {
display: flex;
flex-direction: row;
padding-top: 20px;
padding-left: 16px;
padding-right: 16px;
.title {
flex: 1;
color: #fff;
font-size: 20px;
font-weight: 600;
line-height: 28px;
letter-spacing: -1.2%;
}
}
}
.open-chat-button {
display: block;
}
}
}

View File

@@ -1,4 +1,7 @@
@import 'connection-status';
@import 'device-status';
@import 'lobby';
@import 'premeeting-screens';
@import 'prejoin';
@import 'prejoin-dialog';
@import 'prejoin-third-party';

View File

@@ -0,0 +1,187 @@
.prejoin-dialog {
background: #1C2025;
box-shadow: 0px 2px 20px rgba(0, 0, 0, 0.5);
border-radius: 5px;
color: #fff;
height: 400px;
width: 375px;
&--small {
height: 300;
width: 400;
}
&-label {
font-size: 15px;
line-height: 24px;
&-num {
background: #2b3b4b;
border: 1px solid #A4B8D1;
border-radius: 50%;
color: #fff;
display: inline-block;
height: 24px;
margin-right: 8px;
width: 24px;
}
}
&-container {
align-items: center;
background: rgba(0,0,0,0.6);
display: flex;
height: 100vh;
justify-content: center;
left: 0;
position: absolute;
top: 0;
width: 100vw;
z-index: 3;
}
&-flag {
display: inline-block;
margin-right: 8px;
transform: scale(1.2);
}
&-title {
display: inline-block;
font-size: 24px;
line-height: 32px;
}
&-icon {
cursor: pointer;
> svg {
fill: #A4B8D1;
}
}
&-btn {
width: 309px;
}
&-dialin-container {
text-align: center;
}
&-delimiter {
background: #5f6266;
border: 0;
height: 1px;
margin: 0;
padding: 0;
width: 100%;
&-container {
margin: 16px 0 24px 0;
position: relative;
}
&-txt-container {
position: absolute;
text-align: center;
top: -8px;
width: 100%;
}
&-txt {
background: #1C2025;
color: #5f6266;
font-size: 11px;
text-transform: uppercase;
padding: 0 8px;
}
}
.prejoin-dialog-btn.primary,
.action-btn.prejoin-dialog-btn.text {
width: 310px;
}
}
.prejoin-dialog-callout {
padding: 16px;
&-header {
display: flex;
justify-content: space-between;
margin-bottom: 24px;
}
&-picker {
margin: 8px 0 16px 0;
}
}
.prejoin-dialog-dialin {
text-align: center;
&-header {
align-items: center;
margin: 16px 0 32px 16px;
display: flex;
}
&-icon {
margin-right: 16px;
}
&-num {
background: #3e474f;
border-radius: 4px;
display: inline-block;
font-size: 15px;
line-height: 24px;
margin: 4px;
padding: 8px;
&-container {
min-height: 48px;
margin: 8px 0;
}
}
&-link {
color: #6FB1EA;
cursor: pointer;
display: inline-block;
font-size: 13px;
line-height: 20px;
margin-bottom: 24px;
}
&-spaced-label {
margin-bottom: 16px;
margin-top: 28px;
}
&-btns {
&> div {
margin-bottom: 16px;
}
}
}
.prejoin-dialog-calling {
padding: 16px;
text-align: center;
&-header {
text-align: right;
}
&-label {
font-size: 15px;
margin: 8px 0 16px 0;
}
&-number {
font-size: 19px;
line-height: 28px;
margin: 16px 0;
}
}

View File

@@ -3,25 +3,6 @@
width: 100%;
}
&-avatar {
margin: 8px auto 16px;
&-name {
color: white;
font-size: 16px;
font-weight: 600;
line-height: 26px;
margin-bottom: 32px;
text-align: center;
}
&-container {
align-items: center;
display: flex;
flex-direction: column;
}
}
&-error {
background-color: #E04757;
border-radius: 6px;
@@ -42,6 +23,30 @@
padding: 8px 0;
}
&-dropdown-btn {
align-items: center;
color: #1C2025;
cursor: pointer;
display: flex;
height: 40px;
font-size: 15px;
line-height: 24px;
padding: 0 16px;
&:hover {
background-color: #DAEBFA;
}
}
&-dropdown-icon {
display: inline-block;
margin-right: 16px;
& > svg {
fill: #1C2025;
}
}
&-dropdown-container {
position: relative;
width: 100%;

View File

@@ -7,7 +7,7 @@
position: absolute;
right: 0;
top: 0;
z-index: $toolbarZ + 2;
z-index: $toolbarZ + 1;
.action-btn {
border-radius: 6px;
@@ -82,7 +82,7 @@
flex-direction: column;
flex-shrink: 0;
height: 100%;
margin: 0 30px;
margin: 0 110px;
padding: 24px 0 16px;
position: relative;
width: $prejoinDefaultContentWidth;
@@ -101,7 +101,7 @@
font-weight: 600;
letter-spacing: -0.015;
line-height: 36px;
margin-bottom: 16px;
margin-bottom: 32px;
text-align: center;
}
@@ -137,7 +137,6 @@
}
.toolbox-content-items {
@include ltr;
background: transparent;
box-shadow: none;
display: flex;
@@ -155,20 +154,33 @@
}
}
@media (max-width: 720px) {
@media (max-width: 1000px) {
flex-direction: column-reverse;
.content {
height: auto;
margin: 0 auto;
}
.con-status {
margin: 24px auto;
position: fixed;
top: 0;
width: $prejoinDefaultContentWidth;
}
}
// mobile phone landscape
@media (max-height: 420px) {
flex-direction: row;
div.content {
padding: 16px 16px 0 16px;
}
.con-status {
display: none;
}
}
@media (max-width: 400px) {
@@ -188,6 +200,11 @@
}
}
.con-status {
margin: 0;
width: 100%;
}
.device-status-error {
border-radius: 0;
margin: 0 -16px;

View File

@@ -46,6 +46,11 @@ $reloadProgressBarBg: #0074E0;
/**
* Dialog colors
**/
$auiDialogColor: #eceef1;
$auiDialogBg: #253858;
$auiDialogContentBg: #344563;
$auiBorderColor: #253858;
$dialogTitleFontWeight: 400;
$dialogErrorText: #344563;
/**
@@ -70,7 +75,11 @@ $errorColor: #c61600;
$feedbackCancelFontColor: #333;
// Popover colors
$popoverBg: initial;
$popoverFontColor: #ffffff !important;
$popupMenuColor: #ffffff !important;
$popupMenuHoverColor: #ffffff !important;
$popupMenuHoverBackground: rgba(255, 255, 255, 0.1);
$popupSliderColor: #0376da;
// Toolbar

4
custom.d.ts vendored
View File

@@ -1,4 +0,0 @@
declare module '*.svg' {
const content: any;
export default content;
}

9
debian/control vendored
View File

@@ -20,7 +20,7 @@ Description: WebRTC JavaScript video conferences
Package: jitsi-meet-web-config
Architecture: all
Depends: openssl, nginx | nginx-full | nginx-extras | apache2, curl
Depends: openssl, nginx | nginx-full | nginx-extras | apache2
Description: Configuration for web serving of Jitsi Meet
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
Videobridge to provide high quality, scalable video conferences.
@@ -33,7 +33,7 @@ Description: Configuration for web serving of Jitsi Meet
Package: jitsi-meet-prosody
Architecture: all
Depends: openssl, prosody (>= 0.11.7) | prosody-trunk | prosody-0.12 | prosody-0.11, lua-sec, lua-basexx, lua-luaossl, lua-cjson, lua-inspect
Depends: openssl, prosody (>= 0.11.0) | prosody-trunk | prosody-0.11, lua-sec
Replaces: jitsi-meet-tokens
Description: Prosody configuration for Jitsi Meet
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
@@ -47,11 +47,12 @@ Description: Prosody configuration for Jitsi Meet
Package: jitsi-meet-tokens
Architecture: all
Depends: ${misc:Depends}, prosody-trunk | prosody-0.11 | prosody-0.12 | prosody (>= 0.11.7), jitsi-meet-prosody
Depends: ${misc:Depends}, prosody-trunk (>= 1nightly747) | prosody-0.11 | prosody (>= 0.11.2), libssl1.0-dev | libssl-dev, luarocks, jitsi-meet-prosody, git
Description: Prosody token authentication plugin for Jitsi Meet
Package: jitsi-meet-turnserver
Architecture: all
Breaks: apache2
Pre-Depends: jitsi-meet-web-config
Depends: ${misc:Depends}, jitsi-meet-prosody, coturn, dnsutils
Depends: ${misc:Depends}, nginx (>= 1.13.10) | nginx-full (>= 1.13.10) | nginx-extras (>= 1.13.10), jitsi-meet-prosody, coturn, dnsutils
Description: Configures coturn to be used with Jitsi Meet

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