Compare commits
1 Commits
6947
...
remote-con
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9235000a7f |
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "Jitsi Meet Dev Container",
|
||||
"image": "mcr.microsoft.com/devcontainers/universal:2",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"hostRequirements": {
|
||||
"cpus": 4,
|
||||
"memory": "8gb",
|
||||
"storage": "32gb"
|
||||
},
|
||||
"postCreateCommand": "bash -i -c 'nvm use && npm install && cp tsconfig.web.json tsconfig.json'"
|
||||
}
|
||||
@@ -3,15 +3,10 @@ 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/*
|
||||
|
||||
# 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
|
||||
# remaining JavaScript source code.
|
||||
!.eslintrc.js
|
||||
|
||||
# Not worth it.
|
||||
actionTypes.ts
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
module.exports = {
|
||||
'extends': [
|
||||
'@jitsi/eslint-config'
|
||||
],
|
||||
'ignorePatterns': [ '*.d.ts' ]
|
||||
'eslint-config-jitsi'
|
||||
]
|
||||
};
|
||||
|
||||
118
.flowconfig
Normal 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
|
||||
25
.github/workflows/ci-lua.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Lua CI
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
luacheck:
|
||||
name: Luacheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install luarocks
|
||||
run: sudo apt-get --install-recommends -y install luarocks
|
||||
|
||||
- name: Install luacheck
|
||||
run: sudo luarocks install luacheck
|
||||
|
||||
- name: Check lua codes
|
||||
run: |
|
||||
set -o pipefail && luacheck . | awk -F: '
|
||||
{
|
||||
print $0
|
||||
printf "::warning file=%s,line=%s,col=%s::%s\n", $1, $2, $3, $4
|
||||
}
|
||||
'
|
||||
40
.github/workflows/ci.yml
vendored
@@ -3,42 +3,14 @@ name: Simple CI
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
run-ci:
|
||||
name: Build Frontend
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- name: Check git status
|
||||
run: git status
|
||||
- name: Normalize lang files to ensure sorted
|
||||
run: npm run lang-sort
|
||||
- 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: npm run lint:ci
|
||||
linux-build:
|
||||
name: Build Frontend (Linux)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'npm'
|
||||
node-version: '12.x'
|
||||
- run: npm install
|
||||
- run: npm run lint
|
||||
- run: make
|
||||
|
||||
2
.gitignore
vendored
@@ -69,7 +69,6 @@ buck-out/
|
||||
*.framework
|
||||
android/app/debug
|
||||
android/app/release
|
||||
ios/sdk/out
|
||||
|
||||
# precommit-hook
|
||||
.jshintignore
|
||||
@@ -92,4 +91,3 @@ twa/*.apk
|
||||
twa/*.aab
|
||||
twa/assetlinks.json
|
||||
|
||||
tsconfig.json
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
global = false
|
||||
unused = false
|
||||
redefined = false
|
||||
ignore = { "581" }
|
||||
max_line_length = false
|
||||
color = false
|
||||
formatter = "plain"
|
||||
quiet = 1
|
||||
2
.npmrc
@@ -1,3 +1 @@
|
||||
package-lock=true
|
||||
; FIXME Set legacy-peer-deps=false when we upgrade RN.
|
||||
legacy-peer-deps=true
|
||||
|
||||
@@ -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
|
||||
@@ -141,7 +141,7 @@ react/features/sample/
|
||||
```
|
||||
|
||||
The middleware must be imported in `react/features/app/` specifically
|
||||
in `middlewares.any.ts`, `middlewares.native.ts` or `middlewares.web.ts` where appropriate.
|
||||
in `middlewares.any`, `middlewares.native.js` or `middlewares.web.js` where appropriate.
|
||||
Likewise for the reducer.
|
||||
|
||||
An `index.js` file must not be provided for exporting actions, action types and
|
||||
@@ -152,20 +152,3 @@ this model but new features should follow this new layout.
|
||||
|
||||
When working on an old feature, adding the necessary changes to migrate to the new
|
||||
model is encouraged.
|
||||
|
||||
|
||||
### Avoiding bundle bloat
|
||||
|
||||
When adding a new feature it's possible that it triggers a build failure due to the increased bundle size. We have safeguards inplace to avoid bundles growing disproportionatelly. While there are legit reasons for increasing the limits, please analyze the bundle first to make sure no unintended dependencies have been included, causing the increase in size.
|
||||
|
||||
First, make a production build with bundle-analysis enabled:
|
||||
|
||||
```
|
||||
npx webpack -p --analyze-bundle
|
||||
```
|
||||
|
||||
Then open the interactive bundle analyzer tool:
|
||||
|
||||
```
|
||||
npx webpack-bundle-analyzer build/app-stats.json
|
||||
```
|
||||
|
||||
108
Makefile
@@ -1,40 +1,29 @@
|
||||
BUILD_DIR = build
|
||||
CLEANCSS = ./node_modules/.bin/cleancss
|
||||
DEPLOY_DIR = libs
|
||||
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet
|
||||
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
|
||||
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
|
||||
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
|
||||
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
|
||||
OLM_DIR = node_modules/olm
|
||||
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
|
||||
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-dev-server
|
||||
|
||||
all: compile deploy clean
|
||||
|
||||
compile:
|
||||
NODE_OPTIONS=--max-old-space-size=8192 \
|
||||
$(WEBPACK)
|
||||
$(WEBPACK) -p
|
||||
|
||||
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-lib-jitsi-meet deploy-libflac deploy-olm deploy-css deploy-local
|
||||
|
||||
deploy-init:
|
||||
rm -fr $(DEPLOY_DIR)
|
||||
@@ -43,91 +32,66 @@ deploy-init:
|
||||
deploy-appbundle:
|
||||
cp \
|
||||
$(BUILD_DIR)/app.bundle.min.js \
|
||||
$(BUILD_DIR)/app.bundle.min.js.map \
|
||||
$(BUILD_DIR)/app.bundle.min.map \
|
||||
$(BUILD_DIR)/do_external_connect.min.js \
|
||||
$(BUILD_DIR)/do_external_connect.min.js.map \
|
||||
$(BUILD_DIR)/do_external_connect.min.map \
|
||||
$(BUILD_DIR)/external_api.min.js \
|
||||
$(BUILD_DIR)/external_api.min.js.map \
|
||||
$(BUILD_DIR)/external_api.min.map \
|
||||
$(BUILD_DIR)/flacEncodeWorker.min.js \
|
||||
$(BUILD_DIR)/flacEncodeWorker.min.map \
|
||||
$(BUILD_DIR)/device_selection_popup_bundle.min.js \
|
||||
$(BUILD_DIR)/device_selection_popup_bundle.min.map \
|
||||
$(BUILD_DIR)/dial_in_info_bundle.min.js \
|
||||
$(BUILD_DIR)/dial_in_info_bundle.min.js.map \
|
||||
$(BUILD_DIR)/dial_in_info_bundle.min.map \
|
||||
$(BUILD_DIR)/alwaysontop.min.js \
|
||||
$(BUILD_DIR)/alwaysontop.min.js.map \
|
||||
$(BUILD_DIR)/alwaysontop.min.map \
|
||||
$(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 \
|
||||
$(DEPLOY_DIR)
|
||||
cp \
|
||||
$(BUILD_DIR)/analytics-ga.min.map \
|
||||
$(BUILD_DIR)/video-blur-effect.min.js \
|
||||
$(BUILD_DIR)/video-blur-effect.min.map \
|
||||
$(BUILD_DIR)/rnnoise-processor.min.js \
|
||||
$(BUILD_DIR)/rnnoise-processor.min.map \
|
||||
$(BUILD_DIR)/close3.min.js \
|
||||
$(BUILD_DIR)/close3.min.js.map \
|
||||
$(DEPLOY_DIR) || true
|
||||
$(BUILD_DIR)/close3.min.map \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
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 \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-tflite:
|
||||
cp \
|
||||
$(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:
|
||||
cp \
|
||||
$(FACE_MODELS_DIR)/blazeface-front.bin \
|
||||
$(FACE_MODELS_DIR)/blazeface-front.json \
|
||||
$(FACE_MODELS_DIR)/emotion.bin \
|
||||
$(FACE_MODELS_DIR)/emotion.json \
|
||||
$(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
|
||||
$(WEBPACK_DEV_SERVER)
|
||||
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-olm
|
||||
$(WEBPACK_DEV_SERVER) --detect-circular-deps
|
||||
|
||||
source-package:
|
||||
mkdir -p source_package/jitsi-meet/css && \
|
||||
|
||||
115
README.md
@@ -1,87 +1,80 @@
|
||||
# <p align="center">Jitsi Meet</p>
|
||||
# Jitsi Meet - Secure, Simple and Scalable Video Conferences
|
||||
|
||||
Jitsi Meet is a set of Open Source projects which empower users to use and deploy
|
||||
video conferencing platforms with state-of-the-art video quality and features.
|
||||
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, [secure](https://jitsi.org/security) and scalable video conferences. Jitsi Meet in action can be seen at [here at the session #482 of the VoIP Users Conference](http://youtu.be/7vFUVClsNh0).
|
||||
|
||||
<hr />
|
||||
The Jitsi Meet client runs in your browser, without installing anything else on your computer. You can try it out at https://meet.jit.si.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/jitsi/jitsi-meet/master/readme-img1.png" width="900" />
|
||||
</p>
|
||||
Jitsi Meet allows very efficient collaboration. Users can stream their desktop or only some windows. It also supports shared document editing with Etherpad.
|
||||
|
||||
<hr />
|
||||
## Installation
|
||||
|
||||
Amongst others here are the main features Jitsi Meet offers:
|
||||
On the client side, no installation is necessary. You just point your browser to the URL of your deployment. This section is about installing a Jitsi Meet suite on your server and hosting your own conferencing service.
|
||||
|
||||
* Support for all current browsers
|
||||
* Mobile applications
|
||||
* Web and native SDKs for integration
|
||||
* HD audio and video
|
||||
* Content sharing
|
||||
* Raise hand and reactions
|
||||
* Chat with private conversations
|
||||
* Polls
|
||||
* Virtual backgrounds
|
||||
Installing Jitsi Meet is a simple experience. For Debian-based system, following the [quick install](https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-quickstart) document, which uses the package system. You can also see a demonstration of the process in [this tutorial video](https://jitsi.org/tutorial).
|
||||
|
||||
And many more!
|
||||
For other systems, or if you wish to install all components manually, see the [detailed manual installation instructions](https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-manual).
|
||||
|
||||
## Using Jitsi Meet
|
||||
Installation with Docker is also available. Please see the [instruction](https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker).
|
||||
|
||||
Using Jitsi Meet is straightforward, as it's browser based. Head over to [meet.jit.si](https://meet.jit.si) and give it a try. It's anonymous, scalable and free to use. All browsers are supported!
|
||||
## Download
|
||||
|
||||
Using mobile? No problem, you can either use your mobile web browser or our fully-featured
|
||||
mobile apps:
|
||||
| Latest stable release | [](https://github.com/jitsi/jitsi-meet/releases/latest) |
|
||||
|---|---|
|
||||
|
||||
| Android | Android (F-Droid) | iOS |
|
||||
|:-:|:-:|:-:|
|
||||
| [<img src="resources/img/google-play-badge.png" height="50">](https://play.google.com/store/apps/details?id=org.jitsi.meet) | [<img src="resources/img/f-droid-badge.png" height="50">](https://f-droid.org/en/packages/org.jitsi.meet/) | [<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905) |
|
||||
You can download Debian/Ubuntu binaries:
|
||||
* [stable](https://download.jitsi.org/stable/) ([instructions](https://jitsi.org/downloads/ubuntu-debian-installations-instructions/))
|
||||
* [testing](https://download.jitsi.org/testing/) ([instructions](https://jitsi.org/downloads/ubuntu-debian-installations-instructions-for-testing/))
|
||||
* [nightly](https://download.jitsi.org/unstable/) ([instructions](https://jitsi.org/downloads/ubuntu-debian-installations-instructions-nightly/))
|
||||
|
||||
If you are feeling adventurous and want to get an early scoop of the features as they are being
|
||||
developed you can also sign up for our open beta testing here:
|
||||
You can download source archives (produced by ```make source-package```):
|
||||
* [source builds](https://download.jitsi.org/jitsi-meet/src/)
|
||||
|
||||
### Mobile apps
|
||||
|
||||
* [Android](https://play.google.com/store/apps/details?id=org.jitsi.meet)
|
||||
|
||||
[<img src="resources/img/google-play-badge.png" height="50">](https://play.google.com/store/apps/details?id=org.jitsi.meet)
|
||||
|
||||
* [Android (F-Droid)](https://f-droid.org/en/packages/org.jitsi.meet/)
|
||||
|
||||
[<img src="resources/img/f-droid-badge.png" height="50">](https://f-droid.org/en/packages/org.jitsi.meet/)
|
||||
|
||||
* [iOS](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
|
||||
|
||||
[<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
|
||||
|
||||
You can also sign up for our open beta testing here:
|
||||
|
||||
* [Android](https://play.google.com/apps/testing/org.jitsi.meet)
|
||||
* [iOS](https://testflight.apple.com/join/isy6ja7S)
|
||||
|
||||
## Running your own instance
|
||||
## Release notes
|
||||
|
||||
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.
|
||||
Release notes for Jitsi Meet are maintained on [this repository](https://github.com/jitsi/jitsi-meet-release-notes).
|
||||
|
||||
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.
|
||||
## Development
|
||||
|
||||
You can check the latest releases [here](https://jitsi.github.io/handbook/docs/releases).
|
||||
|
||||
## Jitsi as a Service
|
||||
|
||||
If you like the branding capabilities of running your own instance but you'd like
|
||||
to avoid dealing with the complexity of monitoring, scaling and updates, JaaS might be
|
||||
for you.
|
||||
|
||||
[8x8 Jitsi as a Service (JaaS)](https://jaas.8x8.vc) is an enterprise-ready video meeting platform that allows developers, organizations and businesses to easily build and deploy video solutions. With Jitsi as a Service we now give you all the power of Jitsi running on our global platform so you can focus on building secure and branded video experiences.
|
||||
|
||||
## Documentation
|
||||
|
||||
All the Jitsi Meet documentation is available in [the handbook](https://jitsi.github.io/handbook/).
|
||||
|
||||
## Security
|
||||
|
||||
For a comprehensive description of all Jitsi Meet's security aspects, please check [this link](https://jitsi.org/security).
|
||||
|
||||
For a detailed description of Jitsi Meet's End-to-End Encryption (E2EE) implementation,
|
||||
please check [this link](https://jitsi.org/e2ee-whitepaper/).
|
||||
|
||||
For information on reporting security vulnerabilities in Jitsi Meet, see [SECURITY.md](./SECURITY.md).
|
||||
For web development see [here](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-web), and for mobile see [here](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-mobile).
|
||||
|
||||
## Contributing
|
||||
|
||||
If you are looking to contribute to Jitsi Meet, first of all, thank you! Please
|
||||
see our [guidelines for contributing](CONTRIBUTING.md).
|
||||
|
||||
<br />
|
||||
<br />
|
||||
## Embedding in external applications
|
||||
|
||||
<footer>
|
||||
<p align="center" style="font-size: smaller;">
|
||||
Built with ❤️ by the Jitsi team at <a href="https://8x8.com" target="_blank">8x8</a> and our community.
|
||||
</p>
|
||||
</footer>
|
||||
Jitsi Meet provides a very flexible way of embedding in external applications by using the [Jitsi Meet API](doc/api.md).
|
||||
|
||||
## Security
|
||||
|
||||
The security section here was starting to feel a bit too succinct for the complexity of the topic, so we created a post that covers the topic much more broadly here: https://jitsi.org/security
|
||||
|
||||
The section on end-to-end encryption in that document is likely going to be one of the key points of interest: https://jitsi.org/security/#e2ee
|
||||
|
||||
## Security issues
|
||||
|
||||
For information on reporting security vulnerabilities in Jitsi Meet, see [SECURITY.md](./SECURITY.md).
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by ESTOS' developer Philipp Hancke who then contributed it to the community where development continues with joint forces!
|
||||
|
||||
@@ -76,9 +76,9 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
||||
@@ -109,7 +109,6 @@ gradle.projectsEvaluated {
|
||||
def dropboxActivity = """
|
||||
<activity
|
||||
android:configChanges="keyboard|orientation"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:name="com.dropbox.core.android.AuthActivity">
|
||||
<intent-filter>
|
||||
@@ -123,7 +122,7 @@ gradle.projectsEvaluated {
|
||||
android.applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
output.getProcessManifestProvider().get().doLast {
|
||||
def outputDir = multiApkManifestOutputDirectory.get().asFile
|
||||
def outputDir = manifestOutputDirectory.get().asFile
|
||||
def manifestPath = new File(outputDir, 'AndroidManifest.xml')
|
||||
def charset = 'UTF-8'
|
||||
def text
|
||||
|
||||
7
android/app/proguard-rules.pro
vendored
@@ -85,9 +85,4 @@
|
||||
# ^^^ We added the above when we switched minifyEnabled on.
|
||||
|
||||
# Rule to avoid build errors related to SVGs.
|
||||
-keep public class com.horcrux.svg.** {*;}
|
||||
|
||||
# https://github.com/facebook/fresco/issues/2638
|
||||
-keep public class com.facebook.imageutils.** {
|
||||
public *;
|
||||
}
|
||||
-keep public class com.horcrux.svg.** {*;}
|
||||
@@ -4,18 +4,15 @@
|
||||
android:installLocation="auto">
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
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:exported="true"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:name=".MainActivity"
|
||||
|
||||
@@ -38,7 +38,7 @@ import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The one and only Activity that the Jitsi Meet app needs. The
|
||||
@@ -82,7 +82,7 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
JitsiMeet.showSplashScreen(this);
|
||||
super.onCreate(null);
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -150,8 +150,8 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
// Set default options
|
||||
JitsiMeetConferenceOptions defaultOptions
|
||||
= new JitsiMeetConferenceOptions.Builder()
|
||||
.setWelcomePageEnabled(true)
|
||||
.setServerURL(buildURL(defaultURL))
|
||||
.setFeatureFlag("welcomepage.enabled", true)
|
||||
.setFeatureFlag("call-integration.enabled", false)
|
||||
.setFeatureFlag("resolution", 360)
|
||||
.setFeatureFlag("server-url-change.enabled", !configurationByRestrictions)
|
||||
@@ -182,6 +182,11 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceTerminated(Map<String, Object> data) {
|
||||
Log.d(TAG, "Conference terminated: " + data);
|
||||
}
|
||||
|
||||
// Activity lifecycle method overrides
|
||||
//
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
BIN
android/app/src/main/res/drawable-hdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 659 B |
BIN
android/app/src/main/res/drawable-mdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 379 B |
BIN
android/app/src/main/res/drawable-xhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 960 B |
BIN
android/app/src/main/res/drawable-xxhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
@@ -1,70 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="262.91376dp"
|
||||
android:height="262.91376dp"
|
||||
android:viewportWidth="262.91376"
|
||||
android:viewportHeight="262.91376">
|
||||
<group android:scaleX="0.75" android:scaleY="0.75" android:translateX="35" android:translateY="35">
|
||||
<clip-path
|
||||
android:pathData="m0,0 l262.914,-0L262.914,262.914 0,262.914 0,0Z"/>
|
||||
<path
|
||||
android:pathData="m142.646,105.099c0.117,0.026 0.255,0.036 0.406,0.036 3.186,-0 10.297,-4.615 11.617,-6.721l0.1,-0.17 0.153,-0.135c0.451,-0.441 1.746,-2.773 2.374,-4.17 -6.751,-2.023 -7.49,-5.677 -8.153,-8.919 -0.069,-0.376 -0.138,-0.717 -0.204,-1.019 -0.074,-0.397 -0.153,-0.8 -0.226,-1.112C138.668,86.221 135.593,88.094 133.921,89.483 133.056,90.201 132.542,92.251 135.042,97.926 136.323,100.816 140.727,104.733 142.646,105.099"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="m115.413,146.042c5.934,-0 18.464,-3.543 26.748,-5.887 1.21,-0.336 2.33,-0.66 3.351,-0.944 0.166,-0.046 0.321,-0.091 0.472,-0.124 -0.463,-0.461 -1.239,-1.159 -2.497,-2.216 -5.521,-3.741 -10.736,-5.484 -16.403,-5.484 -1.237,-0 -2.522,0.071 -3.923,0.231 -4.801,0.55 -8.8,1.69 -10.722,2.237 -0.967,0.284 -1.263,0.366 -1.567,0.366 -0.58,-0 -1.079,-0.341 -1.273,-0.878 -0.194,-0.534 -0.027,-1.121 0.425,-1.507l0.024,-0.011c3.316,-2.784 9.489,-7.951 21.198,-10.256 2.027,-0.401 4.202,-0.605 6.454,-0.605 5.242,-0 10.67,1.086 16.125,3.219 7.436,2.899 12.521,6.625 16.602,9.62 2.199,1.609 4.105,3.007 5.755,3.771 0.421,0.2 0.637,0.255 0.746,0.265 0.074,-0.095 0.23,-0.365 0.474,-1.069 0.066,-0.185 0.529,-2.161 -2.806,-13.374 -1.931,-6.51 -4.264,-13.156 -5.479,-16.104 -2.356,-5.711 -1.778,-9.76 -1.051,-12.125 -1.999,0.735 -4.033,1.87 -6.174,3.446L161.758,98.711C160.694,99.506 159.599,100.404 158.426,101.454 151.517,107.64 146.344,110.864 143.035,111.04l-0.093,0.004 -0.093,-0.009c-2.912,-0.245 -7.324,-4.489 -9.133,-6.634 -0.373,-0.251 -0.8,-0.366 -1.366,-0.366 -0.564,-0 -1.202,0.116 -1.82,0.235C130.086,104.354 129.623,104.441 129.167,104.489 127.708,104.632 125.668,105.106 123.694,105.561 122.746,105.777 121.762,106.005 120.864,106.189 120.851,106.19 120.463,106.272 119.774,106.454 114.903,107.891 111.228,109.55 109.432,111.111 109.414,111.127 109.352,111.174 109.266,111.242 108.048,112.105 105.124,114.567 104.248,118.762L104.237,118.795C102.398,126.516 105.187,136.087 108.892,141.554 110.636,144.125 112.513,145.727 114.048,145.959 114.437,146.015 114.891,146.042 115.413,146.042"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="m90.093,173.175c-1.252,-1.472 -1.783,-3.324 -1.574,-5.521 0.884,-10.642 -0.329,-13.215 -0.891,-13.829 -0.131,-0.144 -0.207,-0.144 -0.265,-0.144 -0.022,-0 -0.041,0.003 -0.064,0.003 -1.044,0.248 -8.066,5.002 -9.615,19.171 -0.749,6.845 0.561,15.63 1.679,20.974 0.897,-3.155 2.314,-6.624 5.057,-10.204 2.556,-3.326 5.345,-5.955 8.801,-8.253C92.143,174.93 90.991,174.235 90.093,173.175"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="m94.906,156.389c-0.03,2.229 -0.326,4.36 -0.61,6.445 -0.151,1.119 -0.314,2.286 -0.434,3.46 -0.161,2.341 0.346,3.166 0.571,3.406 0.127,0.136 0.326,0.287 0.76,0.287 0.339,-0 0.741,-0.091 1.161,-0.268 4.202,-1.756 8.195,-4.815 10.115,-6.515C103.522,161.892 98.995,159.058 94.906,156.389"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="m154.002,81.595c-0.031,0.074 -0.065,0.148 -0.101,0.216 -0.821,2.403 0.306,5.664 2.419,6.898 0.561,0.327 1.106,0.526 1.624,0.596 0.072,0.006 0.148,0.009 0.219,0.009 1.645,-0 2.971,-1.199 3.961,-3.561C162.752,83.959 162.836,81.827 162.37,79.904 162.003,78.409 161.057,76.627 160.453,75.738 159.332,76.509 157.111,78.207 155.585,79.553 154.518,80.582 154.136,81.229 154.002,81.595"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="M148.97,77.699C153.957,73.194 156.988,65.754 158.253,61.334 153.915,65.513 148.633,67.758 145.25,69.198 144.084,69.695 143.08,70.124 142.477,70.476 142.224,70.623 141.965,70.77 141.708,70.919 139.654,72.109 136.55,73.905 136.1,75.011l-0.012,0.036 -0.012,0.034c-1.406,2.956 -2.199,7.401 -2.457,9.95 3.266,-1.99 6.625,-3.322 9.416,-4.42C145.628,79.585 147.863,78.703 148.97,77.699"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="m164.464,51.921c-0.84,5.539 -2.205,10.799 -4.751,16.347 2.781,-3.144 4.396,-6.568 4.941,-10.401C164.886,56.275 165.097,54.756 164.464,51.921"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="M148.749,142.639C148.718,142.598 148.684,142.56 148.658,142.519 148.523,142.539 148.307,142.584 147.972,142.683l-0.14,0.04c-1.726,0.644 -4.899,1.708 -8.556,2.946 -4.396,1.479 -9.365,3.154 -13.526,4.649 -5.297,1.975 -7.021,2.755 -7.557,3.024 -0.098,0.266 -0.203,0.599 -0.327,0.965 -1.254,3.816 -4.125,12.541 -18.276,18.653 2.928,2.956 9.289,8.27 21.809,8.27 1.082,-0 2.21,-0.036 3.341,-0.12 9.451,-0.666 18.342,-4.855 25.026,-11.78 6.087,-6.291 9.538,-14.136 9.585,-21.7C157.876,147.509 155.367,147.135 153.043,146.033 153.014,146.02 150.361,144.745 148.749,142.639"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="m189.478,117.853c-0.523,9.749 -2.122,18.424 -4.744,25.8 -2.128,5.988 -4.94,11.134 -8.356,15.316 -5.676,6.931 -11.555,9.256 -12.804,9.304 -0.866,-0 -1.313,-0.309 -3.046,-1.528 -0.17,-0.114 -0.37,-0.252 -0.581,-0.4 -3.313,5.953 -8.505,11.097 -15.065,14.959 -7.079,4.144 -15.297,6.423 -23.157,6.423 -9.078,-0 -17.13,-2.924 -23.341,-8.456 -7.467,4.799 -12.31,9.074 -16.267,27.005l-1.363,6.17 -2.971,-5.564c-0.424,-0.786 -1.929,-3.731 -3.332,-8.887 -1.934,-7.104 -2.86,-15.181 -2.758,-24.01 0.117,-10.049 3.154,-16.526 5.68,-20.186 2.98,-4.314 6.837,-6.994 10.076,-6.994 0.216,-0 0.428,0.006 0.616,0.035 5.159,0.575 8.435,2.75 14.396,6.686l1.899,1.252c2.059,1.344 4.481,2.7 5.259,2.989 0.54,-0.284 1.749,-2.3 2.155,-5.271l0.069,-0.451c0.005,-0.045 0.009,-0.091 0.014,-0.131 -0.036,-0.02 -0.065,-0.029 -0.094,-0.041 -4.008,-1.375 -9.539,-7.7 -12.364,-17.134 -2.684,-9.382 -2.129,-17.185 1.644,-23.193 6.12,-9.736 19.198,-11.974 23.466,-12.702 1.331,-0.266 2.716,-0.511 4.041,-0.717 0.255,-0.061 0.469,-0.121 0.642,-0.168 -0.031,-0.126 -0.071,-0.265 -0.114,-0.43 -0.108,-0.417 -0.23,-0.891 -0.354,-1.447 -1.345,-6.035 -0.664,-11.069 0.181,-15.193 0.928,-4.546 1.489,-7.287 3.747,-9.936 3.029,-4.165 8.319,-5.936 11.479,-6.991 0.746,-0.249 1.511,-0.509 1.894,-0.689 8.988,-4.31 11.82,-8.739 12.615,-11.694 0.656,-2.451 1.699,-8.884 1.251,-13.335 -0.085,-0.805 0.129,-1.521 0.621,-2.065 0.45,-0.505 1.101,-0.794 1.778,-0.794 1.515,-0 2.82,-0 7.511,14.598 2.481,7.698 0.645,14.903 -5.45,21.424l-0.226,0.231c0.024,0.044 0.049,0.09 0.08,0.144 2.57,4.236 3.963,9.54 3.553,13.51 -0.099,0.906 -0.265,1.775 -0.419,2.549 -0.003,0.01 -0.003,0.016 -0.004,0.029 0.516,-0.032 1.119,-0.055 1.775,-0.055 3.052,-0 7.435,0.474 10.989,2.735 2.135,1.352 4.845,3.439 6.835,7.615C189.223,102.942 190.076,109.575 189.478,117.853m4.77,-23.191c-2.916,-6.1 -6.989,-9.177 -9.793,-10.96 -2.355,-1.494 -5.064,-2.584 -8.077,-3.24l-0.676,-0.146 -0.111,-0.689c-0.339,-2.119 -0.918,-4.275 -1.715,-6.406l-0.185,-0.49 0.292,-0.434c5.095,-7.594 6.323,-16.17 3.54,-24.802 -2.191,-6.824 -3.895,-11.211 -5.341,-13.799 -2.954,-5.305 -7.006,-6.417 -9.891,-6.417 -2.964,-0 -5.8,1.261 -7.789,3.457 -2.043,2.254 -2.993,5.207 -2.678,8.31 0.316,3.134 -0.494,8.516 -1.014,10.439 -0.04,0.117 -0.975,2.929 -8.201,6.428 -0.162,0.056 -0.512,0.179 -1.053,0.359 -3.729,1.246 -10.666,3.571 -15.258,9.64 -3.465,4.205 -4.332,8.441 -5.338,13.346 -0.586,2.865 -1.236,6.744 -1.079,11.344l0.026,0.841 -0.824,0.188c-11.646,2.585 -20.025,7.835 -24.909,15.605 -5.054,8.04 -5.919,18.055 -2.543,29.853 0.063,0.204 0.126,0.407 0.189,0.615l0.527,1.608 -1.665,-0.286c-0.561,-0.101 -1.135,-0.18 -1.729,-0.241 -0.493,-0.06 -1.001,-0.082 -1.509,-0.082 -5.633,-0 -11.663,3.585 -16.128,9.592 -3.451,4.641 -7.588,12.849 -7.735,25.601 -0.114,9.573 0.906,18.401 3.038,26.228 1.581,5.795 3.326,9.329 4.004,10.577l13.306,24.94 6.096,-27.619c2.454,-11.09 4.864,-15.262 7.725,-18.111l0.561,-0.563 0.679,0.411c6.605,3.977 14.466,6.084 22.73,6.084 9.286,-0 18.965,-2.682 27.259,-7.551 5.38,-3.16 9.974,-7.036 13.649,-11.531l0.45,-0.369 0.85,-0.02c2.156,-0.068 5.16,-1.164 8.222,-3.004 2.6,-1.555 6.543,-4.428 10.501,-9.262 3.997,-4.884 7.274,-10.854 9.716,-17.734 2.876,-8.073 4.625,-17.489 5.204,-28.004 0.689,-9.668 -0.434,-17.641 -3.327,-23.704"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="m180.026,98.414c-1.67,-2.596 -3.771,-4.206 -5.475,-4.206 -0.313,-0 -0.613,0.051 -0.895,0.161 -0.911,0.361 -2.356,4.532 -1.714,7.566 0.434,2.066 2.938,9.04 4.151,12.394 0.456,1.281 0.68,1.91 0.754,2.142 0.064,0.183 0.145,0.448 0.256,0.774 0.97,2.971 3.467,10.586 4.206,16.761 1.549,-6.579 2.424,-14.512 2.085,-23.997C183.235,105.662 182.04,101.538 180.026,98.414"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="M168.088,142.604C169.896,142.111 171.33,141.705 172.398,141.395 170.213,139.874 167.689,137.979 164.247,135.304c-8.418,-6.546 -17.449,-9.87 -26.839,-9.87 -5.135,-0 -9.611,0.991 -13.156,2.186 0.882,-0.05 1.779,-0.079 2.7,-0.079 1.1,-0 2.247,0.04 3.411,0.119 3.652,0.246 13.061,1.901 21.565,12.047 1.714,2.039 3.559,3.73 8.794,3.73 1.873,-0 4.051,-0.207 6.662,-0.645C167.544,142.751 167.793,142.678 168.088,142.604"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="m164.3,147.583c-0.122,1.563 -0.376,4.509 -0.782,6.76 -0.495,2.719 -1.31,5.02 -1.791,6.226 0.85,0.786 1.694,1.553 2.247,2.043 2.214,-1.447 9.47,-6.96 14.483,-19.474C176.847,144.229 174.59,145.178 171.671,146.018 168.701,146.861 165.82,147.357 164.3,147.583"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -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>
|
||||
@@ -2,5 +2,4 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
@@ -2,5 +2,4 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#17A0DB</color>
|
||||
<color name="navigationBarColor">#161618</color>
|
||||
<color name="colorPrimaryDark">#1081B2</color>
|
||||
</resources>
|
||||
@@ -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>
|
||||
<item name="android:navigationBarColor">@color/navigationBarColor</item>
|
||||
<item name="android:windowDisablePreview">true</item>
|
||||
</style>
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:navigationBarColor">@color/colorPrimaryDark</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -7,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.14'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
|
||||
classpath 'com.android.tools.build:gradle:4.0.2'
|
||||
classpath 'com.google.gms:google-services:4.3.3'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
buildToolsVersion = "31.0.0"
|
||||
compileSdkVersion = 32
|
||||
buildToolsVersion = "29.0.3"
|
||||
compileSdkVersion = 29
|
||||
minSdkVersion = 23
|
||||
targetSdkVersion = 32
|
||||
targetSdkVersion = 29
|
||||
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
|
||||
@@ -50,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
|
||||
@@ -125,7 +110,7 @@ allprojects {
|
||||
|
||||
project.version = "${json.version}-jitsi-${versionQualifierNumber}"
|
||||
|
||||
task jitsiAndroidSourcesJar(type: Jar) {
|
||||
task androidSourcesJar(type: Jar) {
|
||||
classifier = 'sources'
|
||||
from android.sourceSets.main.java.source
|
||||
}
|
||||
@@ -139,7 +124,7 @@ allprojects {
|
||||
artifact("${project.buildDir}/outputs/aar/${project.name}-release.aar") {
|
||||
extension "aar"
|
||||
}
|
||||
artifact(jitsiAndroidSourcesJar)
|
||||
artifact(androidSourcesJar)
|
||||
pom.withXml {
|
||||
def pomXml = asNode()
|
||||
pomXml.appendNode('name', project.name)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,7 +24,6 @@ android.enableDexingArtifactTransform.desugaring=false
|
||||
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.bundle.enableUncompressedNativeLibs=false
|
||||
|
||||
appVersion=99.0.0
|
||||
sdkVersion=99.0.0
|
||||
appVersion=20.5.0
|
||||
sdkVersion=2.11.0
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -3,7 +3,6 @@ apply plugin: 'maven-publish'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
@@ -26,6 +25,10 @@ android {
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
if (rootProject.ext.libreBuild) {
|
||||
srcDir "src"
|
||||
exclude "**/AmplitudeModule.java"
|
||||
}
|
||||
exclude "test/"
|
||||
}
|
||||
}
|
||||
@@ -34,9 +37,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.localbroadcastmanager:localbroadcastmanager:1.0.0'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.fragment:fragment:1.2.5'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
|
||||
//noinspection GradleDynamicVersion
|
||||
@@ -44,51 +47,31 @@ 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.dropbox.core:dropbox-core-sdk:3.0.8'
|
||||
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) {
|
||||
implementation project(':react-native-amplitude')
|
||||
implementation project(':react-native-giphy')
|
||||
implementation 'com.amplitude:android-sdk:2.14.1'
|
||||
implementation(project(":react-native-google-signin")) {
|
||||
exclude group: 'com.google.android.gms'
|
||||
exclude group: 'androidx'
|
||||
}
|
||||
}
|
||||
|
||||
implementation project(':react-native-async-storage')
|
||||
implementation project(':react-native-background-timer')
|
||||
implementation project(':react-native-calendar-events')
|
||||
implementation project(':react-native-community_clipboard')
|
||||
implementation project(':react-native-community-async-storage')
|
||||
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-safe-area-context')
|
||||
implementation project(':react-native-screens')
|
||||
implementation project(':react-native-slider')
|
||||
implementation project(':react-native-linear-gradient')
|
||||
implementation project(':react-native-sound')
|
||||
implementation project(':react-native-splash-screen')
|
||||
implementation project(':react-native-svg')
|
||||
implementation project(':react-native-video')
|
||||
implementation project(':react-native-webrtc')
|
||||
implementation project(':react-native-webview')
|
||||
implementation project(':react-native-splash-screen')
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
}
|
||||
@@ -167,9 +150,16 @@ android.libraryVariants.all { def variant ->
|
||||
// Bundle sounds
|
||||
//
|
||||
copy {
|
||||
from("${projectDir}/../../sounds")
|
||||
include("*.wav")
|
||||
include("*.mp3")
|
||||
from("${projectDir}/../../sounds/incomingMessage.wav")
|
||||
from("${projectDir}/../../sounds/joined.wav")
|
||||
from("${projectDir}/../../sounds/left.wav")
|
||||
from("${projectDir}/../../sounds/liveStreamingOn.mp3")
|
||||
from("${projectDir}/../../sounds/liveStreamingOff.mp3")
|
||||
from("${projectDir}/../../sounds/outgoingRinging.wav")
|
||||
from("${projectDir}/../../sounds/outgoingStart.wav")
|
||||
from("${projectDir}/../../sounds/recordingOn.mp3")
|
||||
from("${projectDir}/../../sounds/recordingOff.mp3")
|
||||
from("${projectDir}/../../sounds/rejected.wav")
|
||||
into("${assetsDir}/sounds")
|
||||
}
|
||||
|
||||
@@ -220,7 +210,7 @@ publishing {
|
||||
def groupId = it.moduleGroup
|
||||
def artifactId = it.moduleName
|
||||
|
||||
if (artifactId.startsWith('react-native-')) {
|
||||
if (artifactId.startsWith('react-native-') && groupId.equals('jitsi-meet')) {
|
||||
groupId = rootProject.ext.moduleGroupId
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.jitsi.meet.sdk">
|
||||
<!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
@@ -10,6 +9,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,17 +30,16 @@
|
||||
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"/>
|
||||
android:windowSoftInputMode="adjustResize"></activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
|
||||
<service
|
||||
android:name=".ConnectionService"
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
|
||||
android:exported="true">
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.telecom.ConnectionService" />
|
||||
</intent-filter>
|
||||
@@ -49,21 +48,6 @@
|
||||
<service
|
||||
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
|
||||
android:foregroundServiceType="mediaProjection" />
|
||||
|
||||
<provider
|
||||
android:name="com.reactnativecommunity.webview.RNCWebViewFileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:enabled="false"
|
||||
tools:replace="android:authorities">
|
||||
</provider>
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false">
|
||||
<meta-data android:name="org.jitsi.meet.sdk.JitsiInitializer"
|
||||
android:value="androidx.startup" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright @ 2019-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.sdk;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import com.amplitude.api.Amplitude;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Implements the react-native module for the Amplitude integration.
|
||||
*/
|
||||
@ReactModule(name = AmplitudeModule.NAME)
|
||||
class AmplitudeModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public static final String NAME = "Amplitude";
|
||||
public static final String JITSI_PREFERENCES = "jitsi-preferences";
|
||||
public static final String AMPLITUDE_DEVICE_ID_KEY = "amplitudeDeviceId";
|
||||
|
||||
public AmplitudeModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Amplitude SDK.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance. Should
|
||||
* be used only for multi-project logging.
|
||||
* @param apiKey The API_KEY of the Amplitude project.
|
||||
*/
|
||||
@ReactMethod
|
||||
@SuppressLint("HardwareIds")
|
||||
public void init(String instanceName, String apiKey) {
|
||||
Amplitude.getInstance(instanceName).initialize(getCurrentActivity(), apiKey);
|
||||
|
||||
// Set the device ID to something consistent.
|
||||
SharedPreferences sharedPreferences = getReactApplicationContext().getSharedPreferences(JITSI_PREFERENCES, Context.MODE_PRIVATE);
|
||||
String android_id = sharedPreferences.getString(AMPLITUDE_DEVICE_ID_KEY, "");
|
||||
if (!TextUtils.isEmpty(android_id)) {
|
||||
Amplitude.getInstance(instanceName).setDeviceId(android_id);
|
||||
} else {
|
||||
String amplitudeId = Amplitude.getInstance(instanceName).getDeviceId();
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(JITSI_PREFERENCES, amplitudeId).apply();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user ID for an Amplitude instance.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance.
|
||||
* @param userId The new value for the user ID.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setUserId(String instanceName, String userId) {
|
||||
Amplitude.getInstance(instanceName).setUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user properties for an Amplitude instance.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance.
|
||||
* @param userProps JSON string with user properties to be set.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setUserProperties(String instanceName, ReadableMap userProps) {
|
||||
if (userProps != null) {
|
||||
Amplitude.getInstance(instanceName).setUserProperties(
|
||||
new JSONObject(userProps.toHashMap()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an analytics event.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance.
|
||||
* @param eventType The event type.
|
||||
* @param eventPropsString JSON string with the event properties.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void logEvent(String instanceName, String eventType, String eventPropsString) {
|
||||
try {
|
||||
JSONObject eventProps = new JSONObject(eventPropsString);
|
||||
Amplitude.getInstance(instanceName).logEvent(eventType, eventProps);
|
||||
} catch (JSONException e) {
|
||||
JitsiMeetLogger.e(e, "Error logging event");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,6 @@ import android.os.Build;
|
||||
import android.telecom.CallAudioState;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -51,8 +49,6 @@ class AudioDeviceHandlerConnectionService implements
|
||||
*/
|
||||
private AudioModeModule module;
|
||||
|
||||
private RNConnectionService rcs;
|
||||
|
||||
/**
|
||||
* Converts any of the "DEVICE_" constants into the corresponding
|
||||
* {@link android.telecom.CallAudioState} "ROUTE_" number.
|
||||
@@ -145,8 +141,8 @@ class AudioDeviceHandlerConnectionService implements
|
||||
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
|
||||
|
||||
module = audioModeModule;
|
||||
rcs = module.getContext().getNativeModule(RNConnectionService.class);
|
||||
|
||||
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
|
||||
if (rcs != null) {
|
||||
rcs.setCallAudioStateListener(this);
|
||||
} else {
|
||||
@@ -156,9 +152,9 @@ class AudioDeviceHandlerConnectionService implements
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
|
||||
if (rcs != null) {
|
||||
rcs.setCallAudioStateListener(null);
|
||||
rcs = null;
|
||||
} else {
|
||||
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
|
||||
}
|
||||
|
||||
@@ -16,11 +16,8 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioFocusRequest;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -63,7 +60,7 @@ class AudioDeviceHandlerGeneric implements
|
||||
private AudioManager audioManager;
|
||||
|
||||
/**
|
||||
* {@link Runnable} for running audio device detection in the audio thread.
|
||||
* {@link Runnable} for running audio device detection the main thread.
|
||||
* This is only used on Android >= M.
|
||||
*/
|
||||
private final Runnable onAudioDeviceChangeRunner = new Runnable() {
|
||||
@@ -145,7 +142,7 @@ class AudioDeviceHandlerGeneric implements
|
||||
// Some other application potentially stole our audio focus
|
||||
// temporarily. Restore our mode.
|
||||
if (audioFocusLost) {
|
||||
module.resetAudioRoute();
|
||||
module.updateAudioRoute();
|
||||
}
|
||||
audioFocusLost = false;
|
||||
break;
|
||||
@@ -219,24 +216,8 @@ class AudioDeviceHandlerGeneric implements
|
||||
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
audioManager.setMicrophoneMute(false);
|
||||
|
||||
int gotFocus;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
gotFocus = audioManager.requestAudioFocus(new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
|
||||
.setAudioAttributes(
|
||||
new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
||||
.build()
|
||||
)
|
||||
.setAcceptsDelayedFocusGain(true)
|
||||
.setOnAudioFocusChangeListener(this)
|
||||
.build()
|
||||
);
|
||||
} else {
|
||||
gotFocus = audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN);
|
||||
}
|
||||
|
||||
if (gotFocus == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN)
|
||||
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
JitsiMeetLogger.w(TAG + " Audio focus request failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
@@ -26,13 +24,10 @@ import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
@@ -58,7 +53,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";
|
||||
@@ -84,6 +78,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;
|
||||
|
||||
@@ -152,16 +147,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.
|
||||
*
|
||||
@@ -199,7 +184,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
deviceInfo.putBoolean("selected", device.equals(selectedDevice));
|
||||
data.pushMap(deviceInfo);
|
||||
}
|
||||
getContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(DEVICE_CHANGE_EVENT, data);
|
||||
ReactInstanceManagerHolder.emitEvent(DEVICE_CHANGE_EVENT, data);
|
||||
JitsiMeetLogger.i(TAG + " Updating audio device list");
|
||||
}
|
||||
});
|
||||
@@ -215,10 +200,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public ReactContext getContext(){
|
||||
return this.getReactApplicationContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the audio device handler module. This function is called *after* all Catalyst
|
||||
* modules have been created, and that's why we use it, because {@link AudioDeviceHandlerConnectionService}
|
||||
@@ -275,7 +256,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
if (mode != -1) {
|
||||
JitsiMeetLogger.i(TAG + " User selected device set to: " + device);
|
||||
userSelectedDevice = device;
|
||||
updateAudioRoute(mode, false);
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -295,22 +276,13 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
return;
|
||||
}
|
||||
|
||||
Activity currentActivity = getCurrentActivity();
|
||||
if (currentActivity != null) {
|
||||
if (mode == DEFAULT) {
|
||||
currentActivity.setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
|
||||
} else {
|
||||
currentActivity.setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
|
||||
}
|
||||
}
|
||||
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean success;
|
||||
|
||||
try {
|
||||
success = updateAudioRoute(mode, false);
|
||||
success = updateAudioRoute(mode);
|
||||
} catch (Throwable e) {
|
||||
success = false;
|
||||
JitsiMeetLogger.e(e, TAG + " Failed to update audio route for mode: " + mode);
|
||||
@@ -349,7 +321,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
* @return {@code true} if the audio route was updated successfully;
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
private boolean updateAudioRoute(int mode, boolean force) {
|
||||
private boolean updateAudioRoute(int mode) {
|
||||
JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode);
|
||||
|
||||
if (!audioDeviceHandler.setMode(mode)) {
|
||||
@@ -384,7 +356,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
|
||||
// If the previously selected device and the current default one
|
||||
// match, do nothing.
|
||||
if (!force && selectedDevice != null && selectedDevice.equals(audioDevice)) {
|
||||
if (selectedDevice != null && selectedDevice.equals(audioDevice)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -449,16 +421,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
void updateAudioRoute() {
|
||||
if (mode != -1) {
|
||||
updateAudioRoute(mode, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-sets the current audio route. Needed when focus is lost and regained.
|
||||
*/
|
||||
void resetAudioRoute() {
|
||||
if (mode != -1) {
|
||||
updateAudioRoute(mode, true);
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
221
android/sdk/src/main/java/org/jitsi/meet/sdk/BaseReactView.java
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
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}.
|
||||
*/
|
||||
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}.
|
||||
*/
|
||||
protected abstract void onExternalAPIEvent(String name, ReadableMap data);
|
||||
|
||||
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}.
|
||||
*/
|
||||
public void setListener(ListenerT listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Wraps the name and extra data for events that were broadcasted locally.
|
||||
*/
|
||||
public class BroadcastAction {
|
||||
private static final String TAG = BroadcastAction.class.getSimpleName();
|
||||
|
||||
private final Type type;
|
||||
private final HashMap<String, Object> data;
|
||||
|
||||
public BroadcastAction(Intent intent) {
|
||||
this.type = Type.buildTypeFromAction(intent.getAction());
|
||||
this.data = buildDataFromBundle(intent.getExtras());
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public HashMap<String, Object> getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public WritableNativeMap getDataAsWritableNativeMap() {
|
||||
WritableNativeMap nativeMap = new WritableNativeMap();
|
||||
|
||||
for (String key : this.data.keySet()) {
|
||||
try {
|
||||
if (this.data.get(key) instanceof Boolean) {
|
||||
nativeMap.putBoolean(key, (Boolean) this.data.get(key));
|
||||
} else if (this.data.get(key) instanceof Integer) {
|
||||
nativeMap.putInt(key, (Integer) this.data.get(key));
|
||||
} else if (this.data.get(key) instanceof Double) {
|
||||
nativeMap.putDouble(key, (Double) this.data.get(key));
|
||||
} else if (this.data.get(key) instanceof String) {
|
||||
nativeMap.putString(key, (String) this.data.get(key));
|
||||
} else {
|
||||
throw new Exception("Unsupported extra data type");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
JitsiMeetLogger.w(TAG + " invalid extra data in event", e);
|
||||
}
|
||||
}
|
||||
|
||||
return nativeMap;
|
||||
}
|
||||
|
||||
private static HashMap<String, Object> buildDataFromBundle(Bundle bundle) {
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
if (bundle != null) {
|
||||
for (String key : bundle.keySet()) {
|
||||
map.put(key, bundle.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
enum Type {
|
||||
SET_AUDIO_MUTED("org.jitsi.meet.SET_AUDIO_MUTED"),
|
||||
HANG_UP("org.jitsi.meet.HANG_UP"),
|
||||
SEND_ENDPOINT_TEXT_MESSAGE("org.jitsi.meet.SEND_ENDPOINT_TEXT_MESSAGE"),
|
||||
TOGGLE_SCREEN_SHARE("org.jitsi.meet.TOGGLE_SCREEN_SHARE"),
|
||||
RETRIEVE_PARTICIPANTS_INFO("org.jitsi.meet.RETRIEVE_PARTICIPANTS_INFO"),
|
||||
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");
|
||||
|
||||
private final String action;
|
||||
|
||||
Type(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
private static Type buildTypeFromAction(String action) {
|
||||
for (Type type : Type.values()) {
|
||||
if (type.action.equalsIgnoreCase(action)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
/**
|
||||
* Class used to emit events through the LocalBroadcastManager, called when events
|
||||
* from JS occurred. Takes an action name from JS, builds and broadcasts the {@link BroadcastEvent}
|
||||
*/
|
||||
public class BroadcastEmitter {
|
||||
private final LocalBroadcastManager localBroadcastManager;
|
||||
|
||||
public BroadcastEmitter(Context context) {
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(context);
|
||||
}
|
||||
|
||||
public void sendBroadcast(String name, ReadableMap data) {
|
||||
BroadcastEvent event = new BroadcastEvent(name, data);
|
||||
|
||||
Intent intent = event.buildIntent();
|
||||
|
||||
if (intent != null) {
|
||||
localBroadcastManager.sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Wraps the name and extra data for the events that occur on the JS side and are
|
||||
* to be broadcasted.
|
||||
*/
|
||||
public class BroadcastEvent {
|
||||
|
||||
private static final String TAG = BroadcastEvent.class.getSimpleName();
|
||||
|
||||
private final Type type;
|
||||
private final HashMap<String, Object> data;
|
||||
|
||||
public BroadcastEvent(String name, ReadableMap data) {
|
||||
this.type = Type.buildTypeFromName(name);
|
||||
this.data = data.toHashMap();
|
||||
}
|
||||
|
||||
public BroadcastEvent(Intent intent) {
|
||||
this.type = Type.buildTypeFromAction(intent.getAction());
|
||||
this.data = buildDataFromBundle(intent.getExtras());
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public HashMap<String, Object> getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public Intent buildIntent() {
|
||||
if (type != null && type.action != null) {
|
||||
Intent intent = new Intent(type.action);
|
||||
|
||||
for (String key : this.data.keySet()) {
|
||||
try {
|
||||
intent.putExtra(key, this.data.get(key).toString());
|
||||
} catch (Exception e) {
|
||||
JitsiMeetLogger.w(TAG + " invalid extra data in event", e);
|
||||
}
|
||||
}
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static HashMap<String, Object> buildDataFromBundle(Bundle bundle) {
|
||||
if (bundle != null) {
|
||||
try {
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
for (String key : bundle.keySet()) {
|
||||
map.put(key, bundle.get(key));
|
||||
}
|
||||
|
||||
return map;
|
||||
} catch (Exception e) {
|
||||
JitsiMeetLogger.w(TAG + " invalid extra data", e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
CONFERENCE_JOINED("org.jitsi.meet.CONFERENCE_JOINED"),
|
||||
CONFERENCE_TERMINATED("org.jitsi.meet.CONFERENCE_TERMINATED"),
|
||||
CONFERENCE_WILL_JOIN("org.jitsi.meet.CONFERENCE_WILL_JOIN"),
|
||||
AUDIO_MUTED_CHANGED("org.jitsi.meet.AUDIO_MUTED_CHANGED"),
|
||||
PARTICIPANT_JOINED("org.jitsi.meet.PARTICIPANT_JOINED"),
|
||||
PARTICIPANT_LEFT("org.jitsi.meet.PARTICIPANT_LEFT"),
|
||||
ENDPOINT_TEXT_MESSAGE_RECEIVED("org.jitsi.meet.ENDPOINT_TEXT_MESSAGE_RECEIVED"),
|
||||
SCREEN_SHARE_TOGGLED("org.jitsi.meet.SCREEN_SHARE_TOGGLED"),
|
||||
PARTICIPANTS_INFO_RETRIEVED("org.jitsi.meet.PARTICIPANTS_INFO_RETRIEVED"),
|
||||
CHAT_MESSAGE_RECEIVED("org.jitsi.meet.CHAT_MESSAGE_RECEIVED"),
|
||||
CHAT_TOGGLED("org.jitsi.meet.CHAT_TOGGLED"),
|
||||
VIDEO_MUTED_CHANGED("org.jitsi.meet.VIDEO_MUTED_CHANGED"),
|
||||
READY_TO_CLOSE("org.jitsi.meet.READY_TO_CLOSE");
|
||||
|
||||
private static final String CONFERENCE_WILL_JOIN_NAME = "CONFERENCE_WILL_JOIN";
|
||||
private static final String CONFERENCE_JOINED_NAME = "CONFERENCE_JOINED";
|
||||
private static final String CONFERENCE_TERMINATED_NAME = "CONFERENCE_TERMINATED";
|
||||
private static final String AUDIO_MUTED_CHANGED_NAME = "AUDIO_MUTED_CHANGED";
|
||||
private static final String PARTICIPANT_JOINED_NAME = "PARTICIPANT_JOINED";
|
||||
private static final String PARTICIPANT_LEFT_NAME = "PARTICIPANT_LEFT";
|
||||
private static final String ENDPOINT_TEXT_MESSAGE_RECEIVED_NAME = "ENDPOINT_TEXT_MESSAGE_RECEIVED";
|
||||
private static final String SCREEN_SHARE_TOGGLED_NAME = "SCREEN_SHARE_TOGGLED";
|
||||
private static final String PARTICIPANTS_INFO_RETRIEVED_NAME = "PARTICIPANTS_INFO_RETRIEVED";
|
||||
private static final String CHAT_MESSAGE_RECEIVED_NAME = "CHAT_MESSAGE_RECEIVED";
|
||||
private static final String CHAT_TOGGLED_NAME = "CHAT_TOGGLED";
|
||||
private static final String VIDEO_MUTED_CHANGED_NAME = "VIDEO_MUTED_CHANGED";
|
||||
private static final String READY_TO_CLOSE_NAME = "READY_TO_CLOSE";
|
||||
|
||||
private final String action;
|
||||
|
||||
Type(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
private static Type buildTypeFromAction(String action) {
|
||||
for (Type type : Type.values()) {
|
||||
if (type.action.equalsIgnoreCase(action)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Type buildTypeFromName(String name) {
|
||||
switch (name) {
|
||||
case CONFERENCE_WILL_JOIN_NAME:
|
||||
return CONFERENCE_WILL_JOIN;
|
||||
case CONFERENCE_JOINED_NAME:
|
||||
return CONFERENCE_JOINED;
|
||||
case CONFERENCE_TERMINATED_NAME:
|
||||
return CONFERENCE_TERMINATED;
|
||||
case AUDIO_MUTED_CHANGED_NAME:
|
||||
return AUDIO_MUTED_CHANGED;
|
||||
case PARTICIPANT_JOINED_NAME:
|
||||
return PARTICIPANT_JOINED;
|
||||
case PARTICIPANT_LEFT_NAME:
|
||||
return PARTICIPANT_LEFT;
|
||||
case ENDPOINT_TEXT_MESSAGE_RECEIVED_NAME:
|
||||
return ENDPOINT_TEXT_MESSAGE_RECEIVED;
|
||||
case SCREEN_SHARE_TOGGLED_NAME:
|
||||
return SCREEN_SHARE_TOGGLED;
|
||||
case PARTICIPANTS_INFO_RETRIEVED_NAME:
|
||||
return PARTICIPANTS_INFO_RETRIEVED;
|
||||
case CHAT_MESSAGE_RECEIVED_NAME:
|
||||
return CHAT_MESSAGE_RECEIVED;
|
||||
case CHAT_TOGGLED_NAME:
|
||||
return CHAT_TOGGLED;
|
||||
case VIDEO_MUTED_CHANGED_NAME:
|
||||
return VIDEO_MUTED_CHANGED;
|
||||
case READY_TO_CLOSE_NAME:
|
||||
return READY_TO_CLOSE;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
public class BroadcastIntentHelper {
|
||||
public static Intent buildSetAudioMutedIntent(boolean muted) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SET_AUDIO_MUTED.getAction());
|
||||
intent.putExtra("muted", muted);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildHangUpIntent() {
|
||||
return new Intent(BroadcastAction.Type.HANG_UP.getAction());
|
||||
}
|
||||
|
||||
public static Intent buildSendEndpointTextMessageIntent(String to, String message) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SEND_ENDPOINT_TEXT_MESSAGE.getAction());
|
||||
intent.putExtra("to", to);
|
||||
intent.putExtra("message", message);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildToggleScreenShareIntent(boolean enabled) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.TOGGLE_SCREEN_SHARE.getAction());
|
||||
intent.putExtra("enabled", enabled);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildOpenChatIntent(String participantId) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.OPEN_CHAT.getAction());
|
||||
intent.putExtra("to", participantId);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildCloseChatIntent() {
|
||||
return new Intent(BroadcastAction.Type.CLOSE_CHAT.getAction());
|
||||
}
|
||||
|
||||
public static Intent buildSendChatMessageIntent(String participantId, String message) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SEND_CHAT_MESSAGE.getAction());
|
||||
intent.putExtra("to", participantId);
|
||||
intent.putExtra("message", message);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildSetVideoMutedIntent(boolean muted) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SET_VIDEO_MUTED.getAction());
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
/**
|
||||
* Listens for {@link BroadcastAction}s on LocalBroadcastManager. When one occurs,
|
||||
* it emits it to JS.
|
||||
*/
|
||||
public class BroadcastReceiver extends android.content.BroadcastReceiver {
|
||||
|
||||
public BroadcastReceiver(Context context) {
|
||||
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
|
||||
for (BroadcastAction.Type type : BroadcastAction.Type.values()) {
|
||||
intentFilter.addAction(type.getAction());
|
||||
}
|
||||
|
||||
localBroadcastManager.registerReceiver(this, intentFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
BroadcastAction action = new BroadcastAction(intent);
|
||||
String actionName = action.getType().getAction();
|
||||
|
||||
ReactInstanceManagerHolder.emitEvent(actionName, action.getDataAsWritableNativeMap());
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,6 @@ import android.text.TextUtils;
|
||||
|
||||
import com.dropbox.core.DbxException;
|
||||
import com.dropbox.core.DbxRequestConfig;
|
||||
import com.dropbox.core.android.Auth;
|
||||
import com.dropbox.core.oauth.DbxCredential;
|
||||
import com.dropbox.core.v2.DbxClientV2;
|
||||
import com.dropbox.core.v2.users.FullAccount;
|
||||
import com.dropbox.core.v2.users.SpaceAllocation;
|
||||
@@ -19,6 +17,7 @@ import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.dropbox.core.android.Auth;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
@@ -67,7 +66,7 @@ class DropboxModule
|
||||
@ReactMethod
|
||||
public void authorize(final Promise promise) {
|
||||
if (isEnabled) {
|
||||
Auth.startOAuth2PKCE(this.getCurrentActivity(), appKey, DbxRequestConfig.newBuilder(clientId).build());
|
||||
Auth.startOAuth2Authentication(this.getCurrentActivity(), appKey);
|
||||
this.promise = promise;
|
||||
} else {
|
||||
promise.reject(
|
||||
@@ -182,23 +181,11 @@ class DropboxModule
|
||||
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
DbxCredential credential = Auth.getDbxCredential();
|
||||
|
||||
if (this.promise != null ) {
|
||||
if (credential != null) {
|
||||
WritableMap result = Arguments.createMap();
|
||||
result.putString("token", credential.getAccessToken());
|
||||
result.putString("rToken", credential.getRefreshToken());
|
||||
result.putDouble("expireDate", credential.getExpiresAt());
|
||||
|
||||
this.promise.resolve(result);
|
||||
this.promise = null;
|
||||
} else {
|
||||
this.promise.reject("Invalid dropbox credentials");
|
||||
}
|
||||
String token = Auth.getOAuth2Token();
|
||||
|
||||
if (token != null && this.promise != null) {
|
||||
this.promise.resolve(token);
|
||||
this.promise = null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright @ 2017-present 8x8, Inc.
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,22 +24,17 @@ import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.HashMap;
|
||||
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";
|
||||
|
||||
private static final String TAG = NAME;
|
||||
|
||||
private final BroadcastEmitter broadcastEmitter;
|
||||
private final BroadcastReceiver broadcastReceiver;
|
||||
|
||||
/**
|
||||
* Initializes a new module instance. There shall be a single instance of
|
||||
* this module throughout the lifetime of the app.
|
||||
@@ -49,21 +44,6 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
public ExternalAPIModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
broadcastEmitter = new BroadcastEmitter(reactContext);
|
||||
broadcastReceiver = new BroadcastReceiver(reactContext);
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,44 +56,32 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a mapping with the constants this module is exporting.
|
||||
*
|
||||
* @return a {@link Map} mapping the constants to be exported with their
|
||||
* values.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
Map<String, Object> constants = new HashMap<>();
|
||||
|
||||
constants.put("SET_AUDIO_MUTED", BroadcastAction.Type.SET_AUDIO_MUTED.getAction());
|
||||
constants.put("HANG_UP", BroadcastAction.Type.HANG_UP.getAction());
|
||||
constants.put("SEND_ENDPOINT_TEXT_MESSAGE", BroadcastAction.Type.SEND_ENDPOINT_TEXT_MESSAGE.getAction());
|
||||
constants.put("TOGGLE_SCREEN_SHARE", BroadcastAction.Type.TOGGLE_SCREEN_SHARE.getAction());
|
||||
constants.put("RETRIEVE_PARTICIPANTS_INFO", BroadcastAction.Type.RETRIEVE_PARTICIPANTS_INFO.getAction());
|
||||
constants.put("OPEN_CHAT", BroadcastAction.Type.OPEN_CHAT.getAction());
|
||||
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);
|
||||
} catch(Exception e) {
|
||||
JitsiMeetLogger.e(e, TAG + " onExternalAPIEvent: error sending event");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import org.webrtc.VideoCodecInfo;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
/** Container for static helper functions related to dealing with H264 codecs. */
|
||||
class H264Utils {
|
||||
public static final String H264_FMTP_PROFILE_LEVEL_ID = "profile-level-id";
|
||||
public static final String H264_FMTP_LEVEL_ASYMMETRY_ALLOWED = "level-asymmetry-allowed";
|
||||
public static final String H264_FMTP_PACKETIZATION_MODE = "packetization-mode";
|
||||
|
||||
public static final String H264_PROFILE_CONSTRAINED_BASELINE = "42e0";
|
||||
public static final String H264_PROFILE_CONSTRAINED_HIGH = "640c";
|
||||
public static final String H264_LEVEL_3_1 = "1f"; // 31 in hex.
|
||||
public static final String H264_CONSTRAINED_HIGH_3_1 =
|
||||
H264_PROFILE_CONSTRAINED_HIGH + H264_LEVEL_3_1;
|
||||
public static final String H264_CONSTRAINED_BASELINE_3_1 =
|
||||
H264_PROFILE_CONSTRAINED_BASELINE + H264_LEVEL_3_1;
|
||||
|
||||
public static Map<String, String> getDefaultH264Params(boolean isHighProfile) {
|
||||
final Map<String, String> params = new HashMap<>();
|
||||
params.put(VideoCodecInfo.H264_FMTP_LEVEL_ASYMMETRY_ALLOWED, "1");
|
||||
params.put(VideoCodecInfo.H264_FMTP_PACKETIZATION_MODE, "1");
|
||||
params.put(VideoCodecInfo.H264_FMTP_PROFILE_LEVEL_ID,
|
||||
isHighProfile ? VideoCodecInfo.H264_CONSTRAINED_HIGH_3_1
|
||||
: VideoCodecInfo.H264_CONSTRAINED_BASELINE_3_1);
|
||||
return params;
|
||||
}
|
||||
|
||||
public static VideoCodecInfo DEFAULT_H264_BASELINE_PROFILE_CODEC =
|
||||
new VideoCodecInfo("H264", getDefaultH264Params(/* isHighProfile= */ false));
|
||||
public static VideoCodecInfo DEFAULT_H264_HIGH_PROFILE_CODEC =
|
||||
new VideoCodecInfo("H264", getDefaultH264Params(/* isHighProfile= */ true));
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2021-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.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.List;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -16,55 +16,33 @@
|
||||
|
||||
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;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
public class JitsiMeetActivity extends FragmentActivity
|
||||
implements JitsiMeetActivityInterface, JitsiMeetViewListener {
|
||||
|
||||
protected static final String TAG = JitsiMeetActivity.class.getSimpleName();
|
||||
|
||||
private static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE";
|
||||
private static final String JITSI_MEET_CONFERENCE_OPTIONS = "JitsiMeetConferenceOptions";
|
||||
|
||||
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
onBroadcastReceived(intent);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Instance of the {@link JitsiMeetView} which this activity will display.
|
||||
*/
|
||||
private JitsiMeetView jitsiView;
|
||||
|
||||
// Helpers for starting the activity
|
||||
//
|
||||
|
||||
@@ -72,9 +50,6 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
Intent intent = new Intent(context, JitsiMeetActivity.class);
|
||||
intent.setAction(ACTION_JITSI_MEET_CONFERENCE);
|
||||
intent.putExtra(JITSI_MEET_CONFERENCE_OPTIONS, options);
|
||||
if (!(context instanceof Activity)) {
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
}
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -87,40 +62,20 @@ 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();
|
||||
// Listen for conference events.
|
||||
getJitsiView().setListener(this);
|
||||
|
||||
if (!extraInitialize()) {
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@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,18 +86,11 @@ 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();
|
||||
}
|
||||
JitsiMeetOngoingConferenceService.abort(this);
|
||||
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
|
||||
|
||||
JitsiMeetActivityDelegate.onHostDestroy(this);
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -157,32 +105,28 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
//
|
||||
|
||||
protected JitsiMeetView getJitsiView() {
|
||||
return jitsiView;
|
||||
JitsiMeetFragment fragment
|
||||
= (JitsiMeetFragment) getSupportFragmentManager().findFragmentById(R.id.jitsiFragment);
|
||||
return fragment.getJitsiView();
|
||||
}
|
||||
|
||||
public void join(@Nullable String url) {
|
||||
JitsiMeetConferenceOptions options
|
||||
= new JitsiMeetConferenceOptions.Builder()
|
||||
.setRoom(url)
|
||||
.build();
|
||||
.setRoom(url)
|
||||
.build();
|
||||
join(options);
|
||||
}
|
||||
|
||||
public void join(JitsiMeetConferenceOptions options) {
|
||||
if (this.jitsiView != null) {
|
||||
this.jitsiView .join(options);
|
||||
} else {
|
||||
JitsiMeetLogger.w("Cannot join, view is null");
|
||||
}
|
||||
getJitsiView().join(options);
|
||||
}
|
||||
|
||||
protected void leave() {
|
||||
Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
|
||||
public void leave() {
|
||||
getJitsiView().leave();
|
||||
}
|
||||
|
||||
private @Nullable
|
||||
JitsiMeetConferenceOptions getConferenceOptions(Intent intent) {
|
||||
private @Nullable JitsiMeetConferenceOptions getConferenceOptions(Intent intent) {
|
||||
String action = intent.getAction();
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(action)) {
|
||||
@@ -201,7 +145,7 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
* Helper function called during activity initialization. If {@code true} is returned, the
|
||||
* initialization is delayed and the {@link JitsiMeetActivity#initialize()} method is not
|
||||
* called. In this case, it's up to the subclass to call the initialize method when ready.
|
||||
* <p>
|
||||
*
|
||||
* This is mainly required so we do some extra initialization in the Jitsi Meet app.
|
||||
*
|
||||
* @return {@code true} if the initialization will be delayed, {@code false} otherwise.
|
||||
@@ -216,41 +160,6 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
join(getConferenceOptions(getIntent()));
|
||||
}
|
||||
|
||||
protected void onConferenceJoined(HashMap<String, Object> extraData) {
|
||||
JitsiMeetLogger.i("Conference joined: " + extraData);
|
||||
// Launch the service for the ongoing notification.
|
||||
JitsiMeetOngoingConferenceService.launch(this, extraData);
|
||||
}
|
||||
|
||||
protected void onConferenceTerminated(HashMap<String, Object> extraData) {
|
||||
JitsiMeetLogger.i("Conference terminated: " + extraData);
|
||||
}
|
||||
|
||||
protected void onConferenceWillJoin(HashMap<String, Object> extraData) {
|
||||
JitsiMeetLogger.i("Conference will join: " + extraData);
|
||||
}
|
||||
|
||||
protected void onParticipantJoined(HashMap<String, Object> extraData) {
|
||||
try {
|
||||
JitsiMeetLogger.i("Participant joined: ", extraData);
|
||||
} catch (Exception e) {
|
||||
JitsiMeetLogger.w("Invalid participant joined extraData", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onParticipantLeft(HashMap<String, Object> extraData) {
|
||||
try {
|
||||
JitsiMeetLogger.i("Participant left: ", extraData);
|
||||
} catch (Exception e) {
|
||||
JitsiMeetLogger.w("Invalid participant left extraData", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onReadyToClose() {
|
||||
JitsiMeetLogger.i("SDK is ready to close");
|
||||
finish();
|
||||
}
|
||||
|
||||
// Activity lifecycle methods
|
||||
//
|
||||
|
||||
@@ -282,9 +191,7 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
|
||||
@Override
|
||||
protected void onUserLeaveHint() {
|
||||
if (this.jitsiView != null) {
|
||||
this.jitsiView .enterPictureInPicture();
|
||||
}
|
||||
getJitsiView().enterPictureInPicture();
|
||||
}
|
||||
|
||||
// JitsiMeetActivityInterface
|
||||
@@ -300,40 +207,24 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
private void registerForBroadcastMessages() {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
// JitsiMeetViewListener
|
||||
//
|
||||
|
||||
for (BroadcastEvent.Type type : BroadcastEvent.Type.values()) {
|
||||
intentFilter.addAction(type.getAction());
|
||||
}
|
||||
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, intentFilter);
|
||||
@Override
|
||||
public void onConferenceJoined(Map<String, Object> data) {
|
||||
JitsiMeetLogger.i("Conference joined: " + data);
|
||||
// Launch the service for the ongoing notification.
|
||||
JitsiMeetOngoingConferenceService.launch(this);
|
||||
}
|
||||
|
||||
private void onBroadcastReceived(Intent intent) {
|
||||
if (intent != null) {
|
||||
BroadcastEvent event = new BroadcastEvent(intent);
|
||||
@Override
|
||||
public void onConferenceTerminated(Map<String, Object> data) {
|
||||
JitsiMeetLogger.i("Conference terminated: " + data);
|
||||
finish();
|
||||
}
|
||||
|
||||
switch (event.getType()) {
|
||||
case CONFERENCE_JOINED:
|
||||
onConferenceJoined(event.getData());
|
||||
break;
|
||||
case CONFERENCE_WILL_JOIN:
|
||||
onConferenceWillJoin(event.getData());
|
||||
break;
|
||||
case CONFERENCE_TERMINATED:
|
||||
onConferenceTerminated(event.getData());
|
||||
break;
|
||||
case PARTICIPANT_JOINED:
|
||||
onParticipantJoined(event.getData());
|
||||
break;
|
||||
case PARTICIPANT_LEFT:
|
||||
onParticipantLeft(event.getData());
|
||||
break;
|
||||
case READY_TO_CLOSE:
|
||||
onReadyToClose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onConferenceWillJoin(Map<String, Object> data) {
|
||||
JitsiMeetLogger.i("Conference will join: " + data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,15 +116,12 @@ public class JitsiMeetActivityDelegate {
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
try {
|
||||
// Try to avoid a crash because some devices trip on this assert:
|
||||
// https://github.com/facebook/react-native/blob/df4e67fe75d781d1eb264128cadf079989542755/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java#L512
|
||||
// Why this happens is a mystery wrapped in an enigma.
|
||||
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
|
||||
if (reactContext != null && activity == reactContext.getCurrentActivity()) {
|
||||
reactInstanceManager.onHostPause(activity);
|
||||
} catch (AssertionError e) {
|
||||
// There seems to be a problem in RN when resuming an Activity when
|
||||
// rotation is involved and the planets align. There doesn't seem to
|
||||
// be a proper solution, but since the activity is going away anyway,
|
||||
// we'll YOLO-ignore the exception and hope fo the best.
|
||||
// Ref: https://github.com/facebook/react-native/search?q=Pausing+an+activity+that+is+not+the+current+activity%2C+this+is+incorrect%21&type=issues
|
||||
JitsiMeetLogger.e(e, "Error running onHostPause, ignoring");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,21 +40,33 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
* Room name.
|
||||
*/
|
||||
private String room;
|
||||
/**
|
||||
* Conference subject.
|
||||
*/
|
||||
private String subject;
|
||||
/**
|
||||
* JWT token used for authentication.
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* Config. See: https://github.com/jitsi/jitsi-meet/blob/master/config.js
|
||||
* Color scheme override, see: https://github.com/jitsi/jitsi-meet/blob/dbedee5e22e5dcf9c92db96ef5bb3c9982fc526d/react/features/base/color-scheme/defaultScheme.js
|
||||
*/
|
||||
private Bundle config;
|
||||
private Bundle colorScheme;
|
||||
|
||||
/**
|
||||
* Feature flags. See: https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/flags/constants.js
|
||||
*/
|
||||
private Bundle featureFlags;
|
||||
|
||||
/**
|
||||
* Set to {@code true} to join the conference with audio / video muted or to start in audio
|
||||
* only mode respectively.
|
||||
*/
|
||||
private Boolean audioMuted;
|
||||
private Boolean audioOnly;
|
||||
private Boolean videoMuted;
|
||||
|
||||
/**
|
||||
* USer information, to be used when no token is specified.
|
||||
*/
|
||||
@@ -68,14 +80,34 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
return room;
|
||||
}
|
||||
|
||||
public String getSubject() {
|
||||
return subject;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public Bundle getColorScheme() {
|
||||
return colorScheme;
|
||||
}
|
||||
|
||||
public Bundle getFeatureFlags() {
|
||||
return featureFlags;
|
||||
}
|
||||
|
||||
public boolean getAudioMuted() {
|
||||
return audioMuted;
|
||||
}
|
||||
|
||||
public boolean getAudioOnly() {
|
||||
return audioOnly;
|
||||
}
|
||||
|
||||
public boolean getVideoMuted() {
|
||||
return videoMuted;
|
||||
}
|
||||
|
||||
public JitsiMeetUserInfo getUserInfo() {
|
||||
return userInfo;
|
||||
}
|
||||
@@ -86,15 +118,19 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
public static class Builder {
|
||||
private URL serverURL;
|
||||
private String room;
|
||||
private String subject;
|
||||
private String token;
|
||||
|
||||
private Bundle config;
|
||||
private Bundle colorScheme;
|
||||
private Bundle featureFlags;
|
||||
|
||||
private Boolean audioMuted;
|
||||
private Boolean audioOnly;
|
||||
private Boolean videoMuted;
|
||||
|
||||
private JitsiMeetUserInfo userInfo;
|
||||
|
||||
public Builder() {
|
||||
config = new Bundle();
|
||||
featureFlags = new Bundle();
|
||||
}
|
||||
|
||||
@@ -126,7 +162,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setSubject(String subject) {
|
||||
setConfigOverride("subject", subject);
|
||||
this.subject = subject;
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -143,12 +179,25 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the conference will be joined with the microphone muted.
|
||||
* @param audioMuted - Muted indication.
|
||||
* Sets the color scheme override so the app is themed. See:
|
||||
* https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/color-scheme/defaultScheme.js
|
||||
* for the structure.
|
||||
* @param colorScheme - A color scheme to be applied to the app.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setAudioMuted(boolean audioMuted) {
|
||||
setConfigOverride("startWithAudioMuted", audioMuted);
|
||||
public Builder setColorScheme(Bundle colorScheme) {
|
||||
this.colorScheme = colorScheme;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the conference will be joined with the microphone muted.
|
||||
* @param muted - Muted indication.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setAudioMuted(boolean muted) {
|
||||
this.audioMuted = muted;
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -160,7 +209,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setAudioOnly(boolean audioOnly) {
|
||||
setConfigOverride("startAudioOnly", audioOnly);
|
||||
this.audioOnly = audioOnly;
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -170,7 +219,20 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setVideoMuted(boolean videoMuted) {
|
||||
setConfigOverride("startWithVideoMuted", videoMuted);
|
||||
this.videoMuted = videoMuted;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the welcome page enabled / disabled. The welcome page lists recent meetings and
|
||||
* calendar appointments and it's meant to be used by standalone applications. Defaults to
|
||||
* false.
|
||||
* @param enabled - Whether the welcome page should be enabled or not.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setWelcomePageEnabled(boolean enabled) {
|
||||
this.featureFlags.putBoolean("welcomepage.enabled", enabled);
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -199,36 +261,6 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setConfigOverride(String config, String value) {
|
||||
this.config.putString(config, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setConfigOverride(String config, int value) {
|
||||
this.config.putInt(config, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setConfigOverride(String config, boolean value) {
|
||||
this.config.putBoolean(config, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setConfigOverride(String config, Bundle bundle) {
|
||||
this.config.putBundle(config, bundle);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setConfigOverride(String config, String[] list) {
|
||||
this.config.putStringArray(config, list);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the immutable {@link JitsiMeetConferenceOptions} object with the configuration
|
||||
* that this {@link Builder} instance specified.
|
||||
@@ -239,9 +271,13 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
|
||||
options.serverURL = this.serverURL;
|
||||
options.room = this.room;
|
||||
options.subject = this.subject;
|
||||
options.token = this.token;
|
||||
options.config = this.config;
|
||||
options.colorScheme = this.colorScheme;
|
||||
options.featureFlags = this.featureFlags;
|
||||
options.audioMuted = this.audioMuted;
|
||||
options.audioOnly = this.audioOnly;
|
||||
options.videoMuted = this.videoMuted;
|
||||
options.userInfo = this.userInfo;
|
||||
|
||||
return options;
|
||||
@@ -254,10 +290,17 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
private JitsiMeetConferenceOptions(Parcel in) {
|
||||
serverURL = (URL) in.readSerializable();
|
||||
room = in.readString();
|
||||
subject = in.readString();
|
||||
token = in.readString();
|
||||
config = in.readBundle();
|
||||
colorScheme = in.readBundle();
|
||||
featureFlags = in.readBundle();
|
||||
userInfo = new JitsiMeetUserInfo(in.readBundle());
|
||||
byte tmpAudioMuted = in.readByte();
|
||||
audioMuted = tmpAudioMuted == 0 ? null : tmpAudioMuted == 1;
|
||||
byte tmpAudioOnly = in.readByte();
|
||||
audioOnly = tmpAudioOnly == 0 ? null : tmpAudioOnly == 1;
|
||||
byte tmpVideoMuted = in.readByte();
|
||||
videoMuted = tmpVideoMuted == 0 ? null : tmpVideoMuted == 1;
|
||||
}
|
||||
|
||||
Bundle asProps() {
|
||||
@@ -270,6 +313,25 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
|
||||
props.putBundle("flags", featureFlags);
|
||||
|
||||
if (colorScheme != null) {
|
||||
props.putBundle("colorScheme", colorScheme);
|
||||
}
|
||||
|
||||
Bundle config = new Bundle();
|
||||
|
||||
if (audioMuted != null) {
|
||||
config.putBoolean("startWithAudioMuted", audioMuted);
|
||||
}
|
||||
if (audioOnly != null) {
|
||||
config.putBoolean("startAudioOnly", audioOnly);
|
||||
}
|
||||
if (videoMuted != null) {
|
||||
config.putBoolean("startWithVideoMuted", videoMuted);
|
||||
}
|
||||
if (subject != null) {
|
||||
config.putString("subject", subject);
|
||||
}
|
||||
|
||||
Bundle urlProps = new Bundle();
|
||||
|
||||
// The room is fully qualified
|
||||
@@ -317,10 +379,14 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeSerializable(serverURL);
|
||||
dest.writeString(room);
|
||||
dest.writeString(subject);
|
||||
dest.writeString(token);
|
||||
dest.writeBundle(config);
|
||||
dest.writeBundle(colorScheme);
|
||||
dest.writeBundle(featureFlags);
|
||||
dest.writeBundle(userInfo != null ? userInfo.asBundle() : new Bundle());
|
||||
dest.writeByte((byte) (audioMuted == null ? 0 : audioMuted ? 1 : 2));
|
||||
dest.writeByte((byte) (audioOnly == null ? 0 : audioOnly ? 1 : 2));
|
||||
dest.writeByte((byte) (videoMuted == null ? 0 : videoMuted ? 1 : 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -17,21 +17,15 @@
|
||||
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
|
||||
@@ -41,46 +35,32 @@ import java.util.HashMap;
|
||||
* See: https://developer.android.com/guide/components/services
|
||||
*/
|
||||
public class JitsiMeetOngoingConferenceService extends Service
|
||||
implements OngoingConferenceTracker.OngoingConferenceListener {
|
||||
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();
|
||||
static final class Actions {
|
||||
static final String START = TAG + ":START";
|
||||
static final String HANGUP = TAG + ":HANGUP";
|
||||
}
|
||||
|
||||
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(Actions.START);
|
||||
|
||||
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,26 +69,12 @@ 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();
|
||||
intentFilter.addAction(BroadcastEvent.Type.AUDIO_MUTED_CHANGED.getAction());
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(broadcastReceiver, intentFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
OngoingConferenceTracker.getInstance().removeListener(this);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(broadcastReceiver);
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
@@ -120,45 +86,26 @@ 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);
|
||||
final String action = intent.getAction();
|
||||
if (Actions.START.equals(action)) {
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification();
|
||||
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);
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
JitsiMeetLogger.i(TAG + " Service started");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
stopSelf();
|
||||
break;
|
||||
default:
|
||||
JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
|
||||
break;
|
||||
} else if (Actions.HANGUP.equals(action)) {
|
||||
JitsiMeetLogger.i(TAG + " Hangup requested");
|
||||
// Abort all ongoing calls
|
||||
if (AudioModeModule.useConnectionService()) {
|
||||
ConnectionService.abortConnections();
|
||||
}
|
||||
stopSelf();
|
||||
} else {
|
||||
JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
@@ -168,60 +115,7 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
public void onCurrentConferenceChanged(String conferenceUrl) {
|
||||
if (conferenceUrl == null) {
|
||||
stopSelf();
|
||||
OngoingNotification.resetStartingtime();
|
||||
JitsiMeetLogger.i(TAG + "Service stopped");
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
HANGUP(TAG + ":HANGUP"),
|
||||
MUTE(TAG + ":MUTE"),
|
||||
UNMUTE(TAG + ":UNMUTE");
|
||||
|
||||
private final String name;
|
||||
|
||||
Action(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static Action fromName(String name) {
|
||||
for (Action action : Action.values()) {
|
||||
if (action.name.equalsIgnoreCase(name)) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't update service, notification is null");
|
||||
} else {
|
||||
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
|
||||
JitsiMeetLogger.i(TAG + " audio muted changed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -81,8 +83,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
result.putBoolean(key, (Boolean)bValue);
|
||||
} else if (valueType.contentEquals("String")) {
|
||||
result.putString(key, (String)bValue);
|
||||
} else if (valueType.contentEquals("Integer")) {
|
||||
result.putInt(key, (int)bValue);
|
||||
} else if (valueType.contentEquals("Bundle")) {
|
||||
result.putBundle(key, mergeProps((Bundle)aValue, (Bundle)bValue));
|
||||
} else {
|
||||
@@ -95,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,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) {
|
||||
@@ -158,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());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,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());
|
||||
@@ -216,27 +175,35 @@ 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
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
166
android/sdk/src/main/java/org/jitsi/meet/sdk/ListenerUtils.java
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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}.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class LocaleDetector extends ReactContextBaseJavaModule {
|
||||
public Map<String, Object> getConstants() {
|
||||
Context context = getReactApplicationContext();
|
||||
HashMap<String,Object> constants = new HashMap<>();
|
||||
constants.put("locale", context.getResources().getConfiguration().locale.toLanguageTag());
|
||||
constants.put("locale", context.getResources().getConfiguration().locale.toString());
|
||||
return constants;
|
||||
}
|
||||
|
||||
@@ -55,4 +55,4 @@ class LocaleDetector extends ReactContextBaseJavaModule {
|
||||
public String getName() {
|
||||
return "LocaleDetector";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class NotificationChannels {
|
||||
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
|
||||
|
||||
public static List<String> allIds = new ArrayList<String>() {{ add(ONGOING_CONFERENCE_CHANNEL_ID); }};
|
||||
}
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import static org.jitsi.meet.sdk.NotificationChannels.ONGOING_CONFERENCE_CHANNEL_ID;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
@@ -25,14 +23,13 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
/**
|
||||
* Helper class for creating the ongoing notification which is used with
|
||||
* {@link JitsiMeetOngoingConferenceService}. It allows the user to easily get back to the app
|
||||
@@ -41,8 +38,11 @@ import java.util.Random;
|
||||
class OngoingNotification {
|
||||
private static final String TAG = OngoingNotification.class.getSimpleName();
|
||||
|
||||
private static final String CHANNEL_ID = "JitsiNotificationChannel";
|
||||
private static final String CHANNEL_NAME = "Ongoing Conference Notifications";
|
||||
|
||||
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
|
||||
private static long startingTime = 0;
|
||||
|
||||
|
||||
static void createOngoingConferenceNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
@@ -56,16 +56,16 @@ class OngoingNotification {
|
||||
}
|
||||
|
||||
NotificationManager notificationManager
|
||||
= (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
= (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
NotificationChannel channel
|
||||
= notificationManager.getNotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID);
|
||||
= notificationManager.getNotificationChannel(CHANNEL_ID);
|
||||
if (channel != null) {
|
||||
// The channel was already created, no need to do it again.
|
||||
return;
|
||||
}
|
||||
|
||||
channel = new NotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID, context.getString(R.string.ongoing_notification_action_unmute), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
channel.enableLights(false);
|
||||
channel.enableVibration(false);
|
||||
channel.setShowBadge(false);
|
||||
@@ -73,7 +73,7 @@ class OngoingNotification {
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
static Notification buildOngoingConferenceNotification(boolean isMuted) {
|
||||
static Notification buildOngoingConferenceNotification() {
|
||||
Context context = ReactInstanceManagerHolder.getCurrentActivity();
|
||||
if (context == null) {
|
||||
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
|
||||
@@ -81,12 +81,13 @@ class OngoingNotification {
|
||||
}
|
||||
|
||||
Intent notificationIntent = new Intent(context, context.getClass());
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, ONGOING_CONFERENCE_CHANNEL_ID);
|
||||
|
||||
if (startingTime == 0) {
|
||||
startingTime = System.currentTimeMillis();
|
||||
NotificationCompat.Builder builder;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder = new NotificationCompat.Builder(context, CHANNEL_ID);
|
||||
} else {
|
||||
builder = new NotificationCompat.Builder(context);
|
||||
}
|
||||
|
||||
builder
|
||||
@@ -96,36 +97,23 @@ class OngoingNotification {
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(true)
|
||||
.setWhen(startingTime)
|
||||
.setUsesChronometer(true)
|
||||
.setAutoCancel(false)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setUsesChronometer(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
|
||||
|
||||
NotificationCompat.Action hangupAction = createAction(context, JitsiMeetOngoingConferenceService.Action.HANGUP, R.string.ongoing_notification_action_hang_up);
|
||||
// Add a "hang-up" action only if we are using ConnectionService.
|
||||
if (AudioModeModule.useConnectionService()) {
|
||||
Intent hangupIntent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
hangupIntent.setAction(JitsiMeetOngoingConferenceService.Actions.HANGUP);
|
||||
PendingIntent hangupPendingIntent
|
||||
= PendingIntent.getService(context, 0, hangupIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action hangupAction = new NotificationCompat.Action(0, "Hang up", hangupPendingIntent);
|
||||
|
||||
JitsiMeetOngoingConferenceService.Action toggleAudioAction = isMuted
|
||||
? JitsiMeetOngoingConferenceService.Action.UNMUTE : JitsiMeetOngoingConferenceService.Action.MUTE;
|
||||
int toggleAudioTitle = isMuted ? R.string.ongoing_notification_action_unmute : R.string.ongoing_notification_action_mute;
|
||||
NotificationCompat.Action audioAction = createAction(context, toggleAudioAction, toggleAudioTitle);
|
||||
|
||||
builder.addAction(hangupAction);
|
||||
builder.addAction(audioAction);
|
||||
builder.addAction(hangupAction);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
static void resetStartingtime() {
|
||||
startingTime = 0;
|
||||
}
|
||||
|
||||
private static NotificationCompat.Action createAction(Context context, JitsiMeetOngoingConferenceService.Action action, @StringRes int titleId) {
|
||||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
intent.setAction(action.getName());
|
||||
PendingIntent pendingIntent
|
||||
= PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
|
||||
String title = context.getString(titleId);
|
||||
return new NotificationCompat.Action(0, title, pendingIntent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class ParticipantInfo {
|
||||
|
||||
@SerializedName("participantId")
|
||||
public String id;
|
||||
|
||||
@SerializedName("displayName")
|
||||
public String displayName;
|
||||
|
||||
@SerializedName("avatarUrl")
|
||||
public String avatarUrl;
|
||||
|
||||
@SerializedName("email")
|
||||
public String email;
|
||||
|
||||
@SerializedName("name")
|
||||
public String name;
|
||||
|
||||
@SerializedName("isLocal")
|
||||
public boolean isLocal;
|
||||
|
||||
@SerializedName("role")
|
||||
public String role;
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ParticipantsService extends android.content.BroadcastReceiver {
|
||||
|
||||
private static final String TAG = ParticipantsService.class.getSimpleName();
|
||||
private static final String REQUEST_ID = "requestId";
|
||||
|
||||
private final Map<String, WeakReference<ParticipantsInfoCallback>> participantsInfoCallbackMap = new HashMap<>();
|
||||
|
||||
private static ParticipantsService instance;
|
||||
|
||||
@Nullable
|
||||
public static ParticipantsService getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private ParticipantsService(Context context) {
|
||||
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(BroadcastEvent.Type.PARTICIPANTS_INFO_RETRIEVED.getAction());
|
||||
localBroadcastManager.registerReceiver(this, intentFilter);
|
||||
}
|
||||
|
||||
static void init(Context context) {
|
||||
instance = new ParticipantsService(context);
|
||||
}
|
||||
|
||||
public void retrieveParticipantsInfo(ParticipantsInfoCallback participantsInfoCallback) {
|
||||
String callbackKey = UUID.randomUUID().toString();
|
||||
this.participantsInfoCallbackMap.put(callbackKey, new WeakReference<>(participantsInfoCallback));
|
||||
|
||||
String actionName = BroadcastAction.Type.RETRIEVE_PARTICIPANTS_INFO.getAction();
|
||||
WritableMap data = Arguments.createMap();
|
||||
data.putString(REQUEST_ID, callbackKey);
|
||||
ReactInstanceManagerHolder.emitEvent(actionName, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
BroadcastEvent event = new BroadcastEvent(intent);
|
||||
|
||||
switch (event.getType()) {
|
||||
case PARTICIPANTS_INFO_RETRIEVED:
|
||||
try {
|
||||
List<ParticipantInfo> participantInfoList = new Gson().fromJson(
|
||||
event.getData().get("participantsInfo").toString(),
|
||||
new TypeToken<ArrayList<ParticipantInfo>>() {
|
||||
}.getType());
|
||||
|
||||
ParticipantsInfoCallback participantsInfoCallback = this.participantsInfoCallbackMap.get(event.getData().get(REQUEST_ID).toString()).get();
|
||||
|
||||
if (participantsInfoCallback != null) {
|
||||
participantsInfoCallback.onReceived(participantInfoList);
|
||||
this.participantsInfoCallbackMap.remove(participantsInfoCallback);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
JitsiMeetLogger.w(TAG + "error parsing participantsList", e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ParticipantsInfoCallback {
|
||||
void onReceived(List<ParticipantInfo> participantInfoList);
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,15 +26,17 @@ 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;
|
||||
import com.oney.WebRTCModule.EglUtils;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.oney.WebRTCModule.RTCVideoViewManager;
|
||||
import com.oney.WebRTCModule.WebRTCModule;
|
||||
|
||||
import org.devio.rn.splashscreen.SplashScreenModule;
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.SoftwareVideoDecoderFactory;
|
||||
import org.webrtc.SoftwareVideoEncoderFactory;
|
||||
import org.webrtc.audio.AudioDeviceModule;
|
||||
import org.webrtc.audio.JavaAudioDeviceModule;
|
||||
|
||||
@@ -46,8 +46,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
|
||||
@@ -73,6 +71,7 @@ class ReactInstanceManagerHolder {
|
||||
new SplashScreenModule(reactContext),
|
||||
new PictureInPictureModule(reactContext),
|
||||
new ProximityModule(reactContext),
|
||||
new WiFiStatsModule(reactContext),
|
||||
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)));
|
||||
|
||||
if (AudioModeModule.useConnectionService()) {
|
||||
@@ -84,17 +83,22 @@ class ReactInstanceManagerHolder {
|
||||
WebRTCModule.Options options = new WebRTCModule.Options();
|
||||
|
||||
AudioDeviceModule adm = JavaAudioDeviceModule.builder(reactContext)
|
||||
.setEnableVolumeLogger(false)
|
||||
.createAudioDeviceModule();
|
||||
options.setAudioDeviceModule(adm);
|
||||
|
||||
EglBase.Context eglContext = EglUtils.getRootEglBaseContext();
|
||||
|
||||
options.setVideoDecoderFactory(new WebRTCVideoDecoderFactory(eglContext));
|
||||
options.setVideoEncoderFactory(new WebRTCVideoEncoderFactory(eglContext));
|
||||
options.setVideoDecoderFactory(new SoftwareVideoDecoderFactory());
|
||||
options.setVideoEncoderFactory(new SoftwareVideoEncoderFactory());
|
||||
|
||||
nativeModules.add(new WebRTCModule(reactContext, options));
|
||||
|
||||
try {
|
||||
Class<?> amplitudeModuleClass = Class.forName("org.jitsi.meet.sdk.AmplitudeModule");
|
||||
Constructor constructor = amplitudeModuleClass.getConstructor(ReactApplicationContext.class);
|
||||
nativeModules.add((NativeModule)constructor.newInstance(reactContext));
|
||||
} catch (Exception e) {
|
||||
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
||||
}
|
||||
|
||||
return nativeModules;
|
||||
}
|
||||
|
||||
@@ -105,81 +109,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.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.
|
||||
*
|
||||
@@ -240,35 +169,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
|
||||
@@ -282,7 +182,44 @@ class ReactInstanceManagerHolder {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(ReactInstanceManagerHolder.class.getCanonicalName(), "initializing RN with Activity");
|
||||
SoLoader.init(activity, /* native exopackage */ false);
|
||||
|
||||
List<ReactPackage> packages
|
||||
= new ArrayList<>(Arrays.asList(
|
||||
new com.BV.LinearGradient.LinearGradientPackage(),
|
||||
new com.calendarevents.CalendarEventsPackage(),
|
||||
new com.corbt.keepawake.KCKeepAwakePackage(),
|
||||
new com.facebook.react.shell.MainReactPackage(),
|
||||
new com.horcrux.svg.SvgPackage(),
|
||||
new com.kevinresol.react_native_default_preference.RNDefaultPreferencePackage(),
|
||||
new com.ocetnik.timer.BackgroundTimerPackage(),
|
||||
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
|
||||
new com.reactnativecommunity.netinfo.NetInfoPackage(),
|
||||
new com.reactnativecommunity.webview.RNCWebViewPackage(),
|
||||
new com.rnimmersive.RNImmersivePackage(),
|
||||
new com.zmxv.RNSound.RNSoundPackage(),
|
||||
new ReactPackageAdapter() {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
return ReactInstanceManagerHolder.createNativeModules(reactContext);
|
||||
}
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return ReactInstanceManagerHolder.createViewManagers(reactContext);
|
||||
}
|
||||
}));
|
||||
|
||||
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()
|
||||
@@ -290,10 +227,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
/** Enumeration of supported video codec types. */
|
||||
public enum VideoCodecMimeType {
|
||||
VP8("video/x-vnd.on2.vp8"),
|
||||
VP9("video/x-vnd.on2.vp9"),
|
||||
H264("video/avc"),
|
||||
AV1("video/av01");
|
||||
|
||||
private final String mimeType;
|
||||
|
||||
private VideoCodecMimeType(String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
|
||||
String mimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.HardwareVideoDecoderFactory;
|
||||
import org.webrtc.SoftwareVideoDecoderFactory;
|
||||
import org.webrtc.VideoCodecInfo;
|
||||
import org.webrtc.VideoDecoder;
|
||||
import org.webrtc.VideoDecoderFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is a custom video decoder factory for WebRTC which behaves similarly
|
||||
* to the default one in iOS. It supports the following codecs:
|
||||
*
|
||||
* - In hardware: H.264 (baseline)
|
||||
* - In software: VP8, VP9, AV1
|
||||
*/
|
||||
public class WebRTCVideoDecoderFactory implements VideoDecoderFactory {
|
||||
private final VideoDecoderFactory hardwareVideoDecoderFactory;
|
||||
private final VideoDecoderFactory softwareVideoDecoderFactory = new SoftwareVideoDecoderFactory();
|
||||
|
||||
public WebRTCVideoDecoderFactory(@Nullable EglBase.Context eglContext) {
|
||||
this.hardwareVideoDecoderFactory = new HardwareVideoDecoderFactory(eglContext);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public VideoDecoder createDecoder(VideoCodecInfo codecInfo) {
|
||||
if (codecInfo.name.equalsIgnoreCase(VideoCodecMimeType.H264.name())) {
|
||||
return this.hardwareVideoDecoderFactory.createDecoder(codecInfo);
|
||||
}
|
||||
|
||||
return this.softwareVideoDecoderFactory.createDecoder(codecInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoCodecInfo[] getSupportedCodecs() {
|
||||
List<VideoCodecInfo> codecs = new ArrayList<>();
|
||||
|
||||
codecs.add(H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC);
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP8.name(), new HashMap<>()));
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP9.name(), new HashMap<>()));
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.AV1.name(), new HashMap<>()));
|
||||
|
||||
return codecs.toArray(new VideoCodecInfo[codecs.size()]);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.HardwareVideoEncoderFactory;
|
||||
import org.webrtc.SoftwareVideoEncoderFactory;
|
||||
import org.webrtc.VideoCodecInfo;
|
||||
import org.webrtc.VideoEncoder;
|
||||
import org.webrtc.VideoEncoderFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is a custom video encoder factory for WebRTC which behaves similarly
|
||||
* to the default one in iOS. It supports the following codecs:
|
||||
*
|
||||
* - In hardware: H.264 (baseline)
|
||||
* - In software: VP8, VP9, AV1
|
||||
*/
|
||||
public class WebRTCVideoEncoderFactory implements VideoEncoderFactory {
|
||||
private final VideoEncoderFactory hardwareVideoEncoderFactory;
|
||||
private final VideoEncoderFactory softwareVideoEncoderFactory = new SoftwareVideoEncoderFactory();
|
||||
|
||||
public WebRTCVideoEncoderFactory(@Nullable EglBase.Context eglContext) {
|
||||
this.hardwareVideoEncoderFactory =
|
||||
new HardwareVideoEncoderFactory(eglContext, false, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public VideoEncoder createEncoder(VideoCodecInfo codecInfo) {
|
||||
if (codecInfo.name.equalsIgnoreCase(VideoCodecMimeType.H264.name())) {
|
||||
return this.hardwareVideoEncoderFactory.createEncoder(codecInfo);
|
||||
}
|
||||
|
||||
return this.softwareVideoEncoderFactory.createEncoder(codecInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoCodecInfo[] getSupportedCodecs() {
|
||||
List<VideoCodecInfo> codecs = new ArrayList<>();
|
||||
|
||||
codecs.add(H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC);
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP8.name(), new HashMap<>()));
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP9.name(), new HashMap<>()));
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.AV1.name(), new HashMap<>()));
|
||||
|
||||
return codecs.toArray(new VideoCodecInfo[codecs.size()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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 android.content.Context;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Module exposing WiFi statistics.
|
||||
*
|
||||
* Gathers rssi, signal in percentage, timestamp and the addresses of the wifi
|
||||
* device.
|
||||
*/
|
||||
@ReactModule(name = WiFiStatsModule.NAME)
|
||||
class WiFiStatsModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public static final String NAME = "WiFiStats";
|
||||
|
||||
/**
|
||||
* The {@code Log} tag {@code WiFiStatsModule} is to log messages with.
|
||||
*/
|
||||
static final String TAG = NAME;
|
||||
|
||||
/**
|
||||
* The scale used for the signal value. A level of the signal, given in the
|
||||
* range of 0 to SIGNAL_LEVEL_SCALE-1 (both inclusive).
|
||||
*/
|
||||
public final static int SIGNAL_LEVEL_SCALE = 101;
|
||||
|
||||
/**
|
||||
* {@link ExecutorService} for running all operations on a dedicated thread.
|
||||
*/
|
||||
private static final ExecutorService executor
|
||||
= Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
* Initializes a new module instance. There shall be a single instance of
|
||||
* this module throughout the lifetime of the application.
|
||||
*
|
||||
* @param reactContext the {@link ReactApplicationContext} where this module
|
||||
* is created.
|
||||
*/
|
||||
public WiFiStatsModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name for this module to be used in the React Native bridge.
|
||||
*
|
||||
* @return a string with the module name.
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link InetAddress} represented by this int.
|
||||
*
|
||||
* @param value the int representation of the ip address.
|
||||
* @return the {@link InetAddress}.
|
||||
* @throws UnknownHostException - if IP address is of illegal length.
|
||||
*/
|
||||
public static InetAddress toInetAddress(int value)
|
||||
throws UnknownHostException {
|
||||
return InetAddress.getByAddress(
|
||||
new byte[] {
|
||||
(byte) value,
|
||||
(byte) (value >> 8),
|
||||
(byte) (value >> 16),
|
||||
(byte) (value >> 24)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method to retrieve WiFi stats.
|
||||
*
|
||||
* @param promise a {@link Promise} which will be resolved if WiFi stats are
|
||||
* retrieved successfully, and it will be rejected otherwise.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getWiFiStats(final Promise promise) {
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Context context
|
||||
= getReactApplicationContext().getApplicationContext();
|
||||
WifiManager wifiManager
|
||||
= (WifiManager) context
|
||||
.getSystemService(Context.WIFI_SERVICE);
|
||||
|
||||
if (!wifiManager.isWifiEnabled()) {
|
||||
promise.reject(new Exception("Wifi not enabled"));
|
||||
return;
|
||||
}
|
||||
|
||||
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
|
||||
|
||||
if (wifiInfo.getNetworkId() == -1) {
|
||||
promise.reject(new Exception("Wifi not connected"));
|
||||
return;
|
||||
}
|
||||
|
||||
int rssi = wifiInfo.getRssi();
|
||||
int signalLevel
|
||||
= WifiManager.calculateSignalLevel(
|
||||
rssi, SIGNAL_LEVEL_SCALE);
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("rssi", rssi)
|
||||
.put("signal", signalLevel)
|
||||
.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
JSONArray addresses = new JSONArray();
|
||||
|
||||
InetAddress wifiAddress
|
||||
= toInetAddress(wifiInfo.getIpAddress());
|
||||
|
||||
try {
|
||||
Enumeration<NetworkInterface> e
|
||||
= NetworkInterface.getNetworkInterfaces();
|
||||
while (e.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = e.nextElement();
|
||||
boolean found = false;
|
||||
|
||||
// first check whether this is the desired interface
|
||||
Enumeration<InetAddress> as
|
||||
= networkInterface.getInetAddresses();
|
||||
while (as.hasMoreElements()) {
|
||||
InetAddress a = as.nextElement();
|
||||
if(a.equals(wifiAddress)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
// interface found let's put addresses
|
||||
// to the result object
|
||||
as = networkInterface.getInetAddresses();
|
||||
while (as.hasMoreElements()) {
|
||||
InetAddress a = as.nextElement();
|
||||
if (a.isLinkLocalAddress())
|
||||
continue;
|
||||
|
||||
addresses.put(a.getHostAddress());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
JitsiMeetLogger.e(e, TAG + " Unable to NetworkInterface.getNetworkInterfaces()");
|
||||
}
|
||||
|
||||
result.put("addresses", addresses);
|
||||
promise.resolve(result.toString());
|
||||
|
||||
JitsiMeetLogger.d(TAG + " WiFi stats: " + result.toString());
|
||||
} catch (Throwable e) {
|
||||
JitsiMeetLogger.e(e, TAG + " Failed to obtain wifi stats");
|
||||
promise.reject(
|
||||
new Exception("Failed to obtain wifi stats"));
|
||||
}
|
||||
}
|
||||
};
|
||||
executor.execute(r);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 699 B |
|
Before Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -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>
|
||||
@@ -3,8 +3,4 @@
|
||||
<string name="dropbox_app_key"></string>
|
||||
<string name="ongoing_notification_title">Ongoing meeting</string>
|
||||
<string name="ongoing_notification_text">You are currently in a meeting. Tap to return to it.</string>
|
||||
<string name="ongoing_notification_action_hang_up">Hang up</string>
|
||||
<string name="ongoing_notification_action_mute">Mute</string>
|
||||
<string name="ongoing_notification_action_unmute">Unmute</string>
|
||||
<string name="ongoing_notification_channel_name">Ongoing Conference Notifications</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
<style name="JitsiMeetActivityStyle" parent="Theme.AppCompat.Light.NoActionBar"/>
|
||||
</resources>
|
||||
@@ -1,59 +1,31 @@
|
||||
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'
|
||||
project(':react-native-async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-async-storage/async-storage/android')
|
||||
include ':react-native-background-timer'
|
||||
project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/android')
|
||||
include ':react-native-calendar-events'
|
||||
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')
|
||||
include ':react-native-community_clipboard'
|
||||
project(':react-native-community_clipboard').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/clipboard/android')
|
||||
include ':react-native-community-async-storage'
|
||||
project(':react-native-community-async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/async-storage/android')
|
||||
include ':react-native-community_netinfo'
|
||||
project(':react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android')
|
||||
include ':react-native-default-preference'
|
||||
project(':react-native-default-preference').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-default-preference/android')
|
||||
include ':react-native-device-info'
|
||||
project(':react-native-device-info').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-device-info/android')
|
||||
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-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'
|
||||
project(':react-native-screens').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-screens/android')
|
||||
include ':react-native-slider'
|
||||
project(':react-native-slider').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/slider/android')
|
||||
include ':react-native-linear-gradient'
|
||||
project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android')
|
||||
include ':react-native-sound'
|
||||
project(':react-native-sound').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sound/android')
|
||||
include ':react-native-splash-screen'
|
||||
project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android')
|
||||
include ':react-native-svg'
|
||||
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
|
||||
include ':react-native-video'
|
||||
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android')
|
||||
include ':react-native-webrtc'
|
||||
project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android')
|
||||
include ':react-native-webview'
|
||||
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
|
||||
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
|
||||
10
app.js
@@ -1,12 +1,10 @@
|
||||
/* application specific logic */
|
||||
|
||||
// Re-export jQuery
|
||||
// FIXME: Remove this requirement from torture tests.
|
||||
import $ from 'jquery';
|
||||
import 'jquery';
|
||||
import 'jquery-contextmenu';
|
||||
import 'jQuery-Impromptu';
|
||||
|
||||
window.$ = window.jQuery = $;
|
||||
|
||||
import '@matrix-org/olm';
|
||||
import 'olm';
|
||||
|
||||
import 'focus-visible';
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [ 'module:metro-react-native-babel-preset' ],
|
||||
env: {
|
||||
production: {
|
||||
plugins: [ 'react-native-paper/babel' ]
|
||||
}
|
||||
},
|
||||
plugins: [ 'optional-require' ]
|
||||
};
|
||||
1740
conference.js
@@ -1,28 +1,20 @@
|
||||
/* global APP, JitsiMeetJS, config */
|
||||
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import Logger from '@jitsi/logger';
|
||||
import Logger from 'jitsi-meet-logger';
|
||||
|
||||
import { redirectToTokenAuthService } from './modules/UI/authentication/AuthHandler';
|
||||
import { LoginDialog } from './react/features/authentication/components';
|
||||
import { isTokenAuthEnabled } from './react/features/authentication/functions';
|
||||
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
||||
import {
|
||||
connectionEstablished,
|
||||
connectionFailed,
|
||||
constructOptions
|
||||
} from './react/features/base/connection/actions.web';
|
||||
import { openDialog } from './react/features/base/dialog/actions';
|
||||
import { setJWT } from './react/features/base/jwt';
|
||||
connectionFailed
|
||||
} from './react/features/base/connection/actions';
|
||||
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 { setPrejoinDisplayNameRequired } from './react/features/prejoin/actions';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
@@ -84,24 +76,25 @@ 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) {
|
||||
const state = APP.store.getState();
|
||||
let { jwt } = state['features/base/jwt'];
|
||||
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
|
||||
function connect(id, password, roomName) {
|
||||
const connectionConfig = Object.assign({}, config);
|
||||
const { jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
if (!iAmRecorder && !iAmSipGateway && isVpaasMeeting(state)) {
|
||||
await APP.store.dispatch(getCustomerDetails());
|
||||
// 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;
|
||||
|
||||
if (!jwt) {
|
||||
jwt = await getJaasJWT(state);
|
||||
APP.store.dispatch(setJWT(jwt));
|
||||
}
|
||||
}
|
||||
serviceUrl += `?room=${roomName}`;
|
||||
|
||||
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, constructOptions(state));
|
||||
// FIXME Remove deprecated 'bosh' option assignment at some point(LJM will be accepting only 'serviceUrl' option
|
||||
// in future). It's included for the time being for Jitsi Meet and lib-jitsi-meet versions interoperability.
|
||||
connectionConfig.serviceUrl = connectionConfig.bosh = serviceUrl;
|
||||
|
||||
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, connectionConfig);
|
||||
|
||||
if (config.iAmRecorder) {
|
||||
connection.addFeature(DISCO_JIBRI_FEATURE);
|
||||
@@ -217,38 +210,10 @@ export function openConnection({ id, password, retry, roomName }) {
|
||||
const { jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED && !jwt) {
|
||||
return requestAuth(roomName);
|
||||
return AuthHandler.requestAuth(roomName, connect);
|
||||
}
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Authentication Dialog and try to connect with new credentials.
|
||||
* If failed to connect because of PASSWORD_REQUIRED error
|
||||
* then ask for password again.
|
||||
* @param {string} [roomName] name of the conference room
|
||||
*
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
function requestAuth(roomName) {
|
||||
const config = APP.store.getState()['features/base/config'];
|
||||
|
||||
if (isTokenAuthEnabled(config)) {
|
||||
// This Promise never resolves as user gets redirected to another URL
|
||||
return new Promise(() => redirectToTokenAuthService(roomName));
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
const onSuccess = connection => {
|
||||
resolve(connection);
|
||||
};
|
||||
|
||||
APP.store.dispatch(
|
||||
openDialog(LoginDialog, { onSuccess,
|
||||
roomName })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
font-size: 48px;
|
||||
color : #f2f2f2;
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
&__message {
|
||||
font-size: 24px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +1,50 @@
|
||||
/**
|
||||
* Mixins that mimic the way Atlaskit fills the screen with modals at low screen widths.
|
||||
*/
|
||||
@mixin full-size-modal-positioner() {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@mixin full-size-modal-dialog() {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the @atlaskit/flag container up a little bit so it does not cover the
|
||||
* toolbar with the first notification.
|
||||
*/
|
||||
.atlaskit-portal > #notifications-container {
|
||||
.jIMojv{
|
||||
bottom: calc(#{$newToolbarSizeWithPadding}) !important;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Keep overflow menu within screen vertical bounds and make it scrollable.
|
||||
* Disable the slide-in animation for @atlaskit/flag due to the animation
|
||||
* repeating for each queued flag once it becomes the top flag.
|
||||
*/
|
||||
.toolbox-button-wth-dialog > div:nth-child(2) {
|
||||
background: $menuBG;
|
||||
max-height: calc(100vh - #{$newToolbarSizeWithPadding} - 46px);
|
||||
margin-bottom: 4px;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
.mIBKA:first-child {
|
||||
animation: cbfRuT 0s !important;
|
||||
-webkit-animation: cbfRuT 0s !important;
|
||||
}
|
||||
|
||||
.modal-dialog-form {
|
||||
/**
|
||||
* Update the @atlaskit/dropdown-menu trigger wrapper to make sure it looks
|
||||
* click-able.
|
||||
*/
|
||||
.cjJUnw {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override @atlaskit/dropdown-menu styling when in a modal because the
|
||||
* dropdown backgrounds clash with the modal backgrounds.
|
||||
*/
|
||||
.cksvax[data-role=droplistContent] {
|
||||
border: 1px solid #455166;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove background color and box-shadow for the context menu container.
|
||||
* Override @atlaskit/theme styling for the top toolbar so it displays over
|
||||
* the video thumbnail while obscuring as little as possible.
|
||||
*/
|
||||
.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) {
|
||||
margin-bottom: 4px;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
.videocontainer .tOoji {
|
||||
background: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following selectors keep the chat modal full-size anywhere between 100px
|
||||
* and 580px for desktop or 680px for mobile.
|
||||
* Override @atlaskit/InlineDialog styling for the overflowmenu so it displays
|
||||
* with the correct height.
|
||||
*/
|
||||
@media (min-width: 100px) and (max-width: 320px) {
|
||||
.smiley-input {
|
||||
display: none;
|
||||
}
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 480px) and (max-width: 580px) {
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 580px) {
|
||||
// Override Atlaskit inline style for the modal background.
|
||||
// Important is unfortunately needed for that.
|
||||
.shift-right .focus-lock [role="dialog"][style] {
|
||||
background-color: $chatBackgroundColor !important;
|
||||
}
|
||||
|
||||
// Remove Atlaskit padding from the chat dialog.
|
||||
.shift-right .focus-lock [role="dialog"] > div:first-child > div:nth-child(2) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
div.Tooltip {
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
// make modal full screen on landscape orientation
|
||||
@media (max-height: 420px) {
|
||||
.atlaskit-portal {
|
||||
.css-1oc7v0j {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
max-width: 100%;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
&> div {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.toolbox-button-wth-dialog .eYJELv {
|
||||
max-height: initial;
|
||||
}
|
||||
|
||||
@@ -1,42 +1,26 @@
|
||||
.audio-preview {
|
||||
display: inline-block;
|
||||
|
||||
&-content {
|
||||
background: $menuBG;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
background: #2A3A4B;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
max-height: 456px;
|
||||
overflow: auto;
|
||||
width: 300px;
|
||||
&-ul {
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style-type: none;
|
||||
}
|
||||
width: 328px;
|
||||
}
|
||||
|
||||
&-header {
|
||||
color: #fff;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
padding: 8px 16px;
|
||||
padding: 16px;
|
||||
|
||||
&-icon {
|
||||
color: #A4B8D1;
|
||||
display: inline-block;
|
||||
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&--bordered {
|
||||
border-bottom: 1px solid #4C4D50;
|
||||
}
|
||||
|
||||
&-text {
|
||||
margin-left: 12px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,18 +29,19 @@
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 8px 0;
|
||||
padding: 12px 0;
|
||||
margin-left: 48px;
|
||||
|
||||
&--selected {
|
||||
background: #131519;
|
||||
background: #1C2025;
|
||||
cursor: initial;
|
||||
margin-left: 0;
|
||||
padding-left: 18px;
|
||||
padding-left: 21px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
display: inline-block;
|
||||
line-height: 24px;
|
||||
text-overflow: ellipsis;
|
||||
@@ -69,21 +54,14 @@
|
||||
&-speaker {
|
||||
position: relative;
|
||||
|
||||
&-ul {
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
&:hover, &:focus-within, &:focus {
|
||||
&:hover {
|
||||
.audio-preview-entry {
|
||||
background: #36383C;
|
||||
background: #3F4E5E;
|
||||
margin-left: 0;
|
||||
padding-left: 48px;
|
||||
|
||||
&--selected {
|
||||
padding-left: 18px;
|
||||
background: $newToolbarBackgroundColor;
|
||||
padding-left: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,16 +70,12 @@
|
||||
}
|
||||
|
||||
.audio-preview-entry-text {
|
||||
max-width: 178px;
|
||||
max-width: 196px;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.audio-preview-entry-text {
|
||||
max-width: 238px;
|
||||
max-width: 256px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,29 +84,19 @@
|
||||
|
||||
&:hover {
|
||||
.audio-preview-entry {
|
||||
background: #36383C;
|
||||
background: #3F4E5E;
|
||||
margin-left: 0;
|
||||
padding-left: 48px;
|
||||
|
||||
&--selected {
|
||||
background: $newToolbarBackgroundColor;
|
||||
padding-left: 18px;
|
||||
padding-left: 21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--nometer {
|
||||
.audio-preview-entry-text {
|
||||
max-width: 238px;
|
||||
}
|
||||
.audio-preview-entry-text {
|
||||
max-width: 196px;
|
||||
}
|
||||
|
||||
&--withmeter {
|
||||
.audio-preview-entry-text {
|
||||
max-width: 178px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&-icon {
|
||||
@@ -146,7 +110,7 @@
|
||||
|
||||
&--check {
|
||||
background: #31B76A;
|
||||
margin-right: 16px;
|
||||
margin-right: 13px;
|
||||
}
|
||||
|
||||
&--exclamation {
|
||||
@@ -157,11 +121,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-hr {
|
||||
border-top: 1px solid #4C4D50;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&-test-button {
|
||||
display: none;
|
||||
background: #FFF;
|
||||
@@ -170,22 +129,22 @@
|
||||
color: #1C2025;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
padding: 2px 8px;
|
||||
padding: 4px 16px;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 5px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
&-meter-mic {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 14px;
|
||||
top: 18px;
|
||||
}
|
||||
|
||||
// Override @atlaskit/InlineDialog container which is made with styled components
|
||||
& > div:nth-child(2) {
|
||||
// Override @atlaskit/InlineDialog container which is made with styled components
|
||||
& > div > div:nth-child(2) > div > div {
|
||||
outline: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
229
css/_aui_reset.scss
Normal file
@@ -0,0 +1,229 @@
|
||||
/* Fonts and line heights */
|
||||
/**
|
||||
* RESET
|
||||
*/
|
||||
html,
|
||||
body,
|
||||
p,
|
||||
div,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
img,
|
||||
pre,
|
||||
form,
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul,
|
||||
ol,
|
||||
dl {
|
||||
margin: 0;
|
||||
}
|
||||
img,
|
||||
fieldset {
|
||||
border: 0;
|
||||
}
|
||||
@-moz-document url-prefix() {
|
||||
img {
|
||||
font-size: 0;
|
||||
}
|
||||
img:-moz-broken {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
/* https://github.com/necolas/normalize.css */
|
||||
/* Customised to remove styles for unsupported browsers */
|
||||
details,
|
||||
main,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
input[type="button"],
|
||||
input[type="submit"],
|
||||
input[type="reset"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
/**
|
||||
* TYPOGRAPHY - 14px base font size, agnostic font stack
|
||||
*/
|
||||
body {
|
||||
color: #333;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.42857142857143;
|
||||
}
|
||||
/* International Font Stacks*/
|
||||
[lang|=en] {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
[lang|=ja] {
|
||||
font-family: "Hiragino Kaku Gothic Pro", "ヒラギノ角ゴ Pro W3", "メイリオ", Meiryo, "MS Pゴシック", Verdana, Arial, sans-serif;
|
||||
}
|
||||
/* Default margins */
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
dl,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
blockquote,
|
||||
pre {
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
/* No top margin to interfere with box padding */
|
||||
p:first-child,
|
||||
ul:first-child,
|
||||
ol:first-child,
|
||||
dl:first-child,
|
||||
h1:first-child,
|
||||
h2:first-child,
|
||||
h3:first-child,
|
||||
h4:first-child,
|
||||
h5:first-child,
|
||||
h6:first-child,
|
||||
blockquote:first-child,
|
||||
pre:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
/* Headings: desired line height in px / font size = unitless line height */
|
||||
h1 {
|
||||
color: #333;
|
||||
font-size: 32px;
|
||||
font-weight: normal;
|
||||
line-height: 1.25;
|
||||
text-transform: none;
|
||||
margin: 30px 0 0 0;
|
||||
}
|
||||
h2 {
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
font-weight: normal;
|
||||
line-height: 1.25;
|
||||
text-transform: none;
|
||||
margin: 30px 0 0 0;
|
||||
}
|
||||
h3 {
|
||||
color: #333;
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
text-transform: none;
|
||||
margin: 30px 0 0 0;
|
||||
}
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 1.25;
|
||||
text-transform: none;
|
||||
margin: 20px 0 0 0;
|
||||
}
|
||||
h5 {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 1.42857143;
|
||||
text-transform: none;
|
||||
margin: 20px 0 0 0;
|
||||
}
|
||||
h6 {
|
||||
color: #707070;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 1.66666667;
|
||||
text-transform: uppercase;
|
||||
margin: 20px 0 0 0;
|
||||
}
|
||||
h1:first-child,
|
||||
h2:first-child,
|
||||
h3:first-child,
|
||||
h4:first-child,
|
||||
h5:first-child,
|
||||
h6:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
/* Nice styles for using subheadings */
|
||||
h1 + h2,
|
||||
h2 + h3,
|
||||
h3 + h4,
|
||||
h4 + h5,
|
||||
h5 + h6 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* Other typographical elements */
|
||||
small {
|
||||
color: #707070;
|
||||
font-size: 12px;
|
||||
line-height: 1.33333333333333;
|
||||
}
|
||||
code,
|
||||
kbd {
|
||||
font-family: monospace;
|
||||
}
|
||||
var,
|
||||
address,
|
||||
dfn,
|
||||
cite {
|
||||
font-style: italic;
|
||||
}
|
||||
cite:before {
|
||||
content: "\2014 \2009";
|
||||
}
|
||||
blockquote {
|
||||
border-left: 1px solid #ccc;
|
||||
color: #707070;
|
||||
margin-left: 19px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
blockquote > cite {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
q {
|
||||
color: #707070;
|
||||
}
|
||||
q:before {
|
||||
content: open-quote;
|
||||
}
|
||||
q:after {
|
||||
content: close-quote;
|
||||
}
|
||||
abbr {
|
||||
border-bottom: 1px #707070 dotted;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3572b0;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:focus,
|
||||
a:hover,
|
||||
a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
54
css/_avatar.scss
Normal file
@@ -0,0 +1,54 @@
|
||||
.avatar {
|
||||
background-color: #AAA;
|
||||
border-radius: 50%;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
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%;
|
||||
}
|
||||
|
||||
.defaultAvatar {
|
||||
opacity: 0.6
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
117
css/_base.scss
@@ -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,
|
||||
@@ -21,7 +17,6 @@ textarea {
|
||||
html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -43,14 +38,28 @@ body {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.jitsi-icon {
|
||||
&-default svg {
|
||||
fill: white;
|
||||
/**
|
||||
* AtlasKit sets a default margin on the rendered modals, so
|
||||
* when the shift-right class is set when the chat opens, we
|
||||
* pad the modal container in order for the modals to be centered
|
||||
* while also taking the chat size into consideration.
|
||||
*/
|
||||
@media (min-width: 480px + $sidebarWidth) {
|
||||
.shift-right [class^="Modal__FillScreen"] {
|
||||
padding-left: $sidebarWidth;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled .jitsi-icon svg {
|
||||
fill: #929292;
|
||||
/**
|
||||
* Similarly, we offset the notifications when the chat is open by
|
||||
* padding the container.
|
||||
*/
|
||||
.shift-right [class^="styledFlagGroup-"] {
|
||||
padding-left: $sidebarWidth;
|
||||
}
|
||||
|
||||
.jitsi-icon svg {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.jitsi-icon.gray svg {
|
||||
@@ -126,12 +135,6 @@ form {
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.leftwatermarknomargin {
|
||||
background-position: center left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.rightwatermark {
|
||||
right: 32px;
|
||||
top: 32px;
|
||||
@@ -158,6 +161,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;
|
||||
@@ -189,7 +202,73 @@ form {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Necessary for the new icons to work properly. */
|
||||
.jitsi-icon svg path {
|
||||
fill: inherit !important;
|
||||
.desktop-browser {
|
||||
@media only screen and (max-width: $smallScreen) {
|
||||
.watermark {
|
||||
width: 20%;
|
||||
height: 20%;
|
||||
}
|
||||
|
||||
.new-toolbox {
|
||||
.toolbox-content {
|
||||
.button-group-center, .button-group-left, .button-group-right {
|
||||
.toolbox-button {
|
||||
.toolbox-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.toolbox-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $verySmallScreen) {
|
||||
#videoResolutionLabel {
|
||||
display: none;
|
||||
}
|
||||
.vertical-filmstrip .filmstrip {
|
||||
display: none;
|
||||
}
|
||||
.new-toolbox {
|
||||
.toolbox-content {
|
||||
.button-group-center, .button-group-left, .button-group-right {
|
||||
.settings-button-small-icon {
|
||||
display: none;
|
||||
}
|
||||
.toolbox-button {
|
||||
.toolbox-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.toolbox-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.chrome-extension-banner {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
374
css/_chat.scss
@@ -1,60 +1,37 @@
|
||||
#sideToolbarContainer {
|
||||
background-color: $chatBackgroundColor;
|
||||
flex-shrink: 0;
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
box-sizing: border-box;
|
||||
color: #FFF;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
left: -$sidebarWidth;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: width .16s ease-in-out;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: $sidebarWidth;
|
||||
z-index: $sideToolbarContainerZ;
|
||||
|
||||
@media (max-width: 580px) {
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
/**
|
||||
* The sidebar (chat) is off-screen when hidden. Move it flush to the left
|
||||
* side of the window when it should be visible.
|
||||
*/
|
||||
&.slideInExt {
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// extract header + tabs height
|
||||
height: calc(100% - 119px);
|
||||
}
|
||||
|
||||
.chat-panel-no-tabs {
|
||||
// extract header height
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
|
||||
#chat-conversation-container {
|
||||
// extract message input height
|
||||
height: calc(100% - 64px);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#chatconversation {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
font-size: 10pt;
|
||||
height: 100%;
|
||||
line-height: 20px;
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
text-align: left;
|
||||
width: $sidebarWidth;
|
||||
word-wrap: break-word;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > :first-child {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
@@ -74,59 +51,87 @@
|
||||
a:active {
|
||||
color: black;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
background: #06a5df;
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: black;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background: black;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #06a5df;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
#chat-recipient {
|
||||
align-items: center;
|
||||
background-color: $chatPrivateMessageBackgroundColor;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-weight: 100;
|
||||
padding: 10px;
|
||||
|
||||
span {
|
||||
color: white;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
div {
|
||||
svg {
|
||||
cursor: pointer;
|
||||
fill: white
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
background-color: $chatHeaderBackgroundColor;
|
||||
height: 70px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
|
||||
.jitsi-icon {
|
||||
.chat-close {
|
||||
align-items: center;
|
||||
bottom: 8px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 18px;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
line-height: 15px;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
width: 40px;
|
||||
|
||||
&:hover {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input-container {
|
||||
padding: 0 16px 24px;
|
||||
}
|
||||
|
||||
#chat-input {
|
||||
border-top: 1px solid $chatInputSeparatorColor;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
position: relative;
|
||||
}
|
||||
padding: 5px 10px;
|
||||
|
||||
.chat-input {
|
||||
flex: 1;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.smiley-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
margin: 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#chat-input .smiley-button {
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background-color: #484A4F;
|
||||
}
|
||||
* {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,96 +144,30 @@
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
-ms-overflow-style: none;
|
||||
border: 0px none;
|
||||
border-radius:0;
|
||||
box-shadow: none;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
resize: none;
|
||||
scrollbar-width: none;
|
||||
width: 100%;
|
||||
word-break: break-word;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#usermsg:hover {
|
||||
border: 0px none;
|
||||
box-shadow: none;
|
||||
}
|
||||
#usermsg:focus,
|
||||
#usermsg:active {
|
||||
border-bottom: 1px solid white;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
#nickname {
|
||||
text-align: center;
|
||||
color: #9d9d9d;
|
||||
font-size: 16px;
|
||||
margin: auto 0;
|
||||
padding: 0 16px;
|
||||
|
||||
#nickname-title {
|
||||
margin-bottom: 5px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
label[for="nickinput"] {
|
||||
> div > span {
|
||||
color: #B8C7E0;
|
||||
}
|
||||
}
|
||||
input {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
label {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.enter-chat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
height: 40px;
|
||||
background: #1B67EC;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
color: #AFB6BC;
|
||||
background: #11336E;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-browser {
|
||||
#nickname {
|
||||
input {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.enter-chat {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.chatmessage .usermessage {
|
||||
font-size: 16px;
|
||||
}
|
||||
font-size: 18px;
|
||||
margin-top: 30px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.sideToolbarContainer {
|
||||
@@ -236,27 +175,35 @@
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
border: 0 !important;
|
||||
clip: rect(1px, 1px, 1px, 1px) !important;
|
||||
clip-path: inset(50%) !important;
|
||||
height: 1px !important;
|
||||
margin: -1px !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
white-space: nowrap !important;
|
||||
.display-name {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
background-color: $chatRemoteMessageBackgroundColor;
|
||||
border-radius: 0px 6px 6px 6px;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
margin-top: 3px;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
|
||||
&.localuser {
|
||||
background-color: $chatLocalMessageBackgroundColor;
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
.usermessage {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-radius: 0px;
|
||||
|
||||
@@ -271,12 +218,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
.privatemessagenotice {
|
||||
font-size: 11px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.messagecontent {
|
||||
margin: 5px 10px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.smiley {
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
#smileys {
|
||||
font-size: 20pt;
|
||||
margin: auto;
|
||||
@@ -297,9 +254,7 @@
|
||||
|
||||
.smiley-input {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.smileys-panel {
|
||||
@@ -307,12 +262,10 @@
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(0, 0, 0, .6) !important;
|
||||
height: auto;
|
||||
display: flex;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
width: calc(#{$sidebarWidth} - 32px);
|
||||
margin-bottom: 5px;
|
||||
margin-left: -5px;
|
||||
width: $sidebarWidth;
|
||||
|
||||
/**
|
||||
* CSS transitions do not apply for auto dimensions. So to produce the css
|
||||
@@ -322,9 +275,14 @@
|
||||
*/
|
||||
transition: max-height 0.3s;
|
||||
|
||||
&.show-smileys {
|
||||
max-height: 500%;
|
||||
}
|
||||
|
||||
#smileysContainer {
|
||||
background-color: $chatBackgroundColor;
|
||||
border-top: 1px solid $chatInputSeparatorColor;
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border-bottom: 1px solid;
|
||||
border-top: 1px solid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +292,7 @@
|
||||
|
||||
.smileyContainer {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
height: 36px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -350,9 +308,21 @@
|
||||
}
|
||||
|
||||
.chat-message-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.local {
|
||||
align-items: flex-end;
|
||||
|
||||
.chatmessage {
|
||||
background-color: $chatLocalMessageBackgroundColor;
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
|
||||
&.privatemessage {
|
||||
background-color: $chatPrivateMessageBackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
.display-name {
|
||||
display: none;
|
||||
}
|
||||
@@ -363,49 +333,49 @@
|
||||
}
|
||||
|
||||
&.error {
|
||||
.chatmessage {
|
||||
background-color: $defaultWarningColor;
|
||||
border-radius: 0px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
margin-top: -5px; // Margin set by atlaskit.
|
||||
.chatmessage-wrapper {
|
||||
max-width: 100%;
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 16px;
|
||||
width: calc(100% - 32px);
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
.replywrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.jitsi-icon {
|
||||
cursor: pointer;
|
||||
.messageactions {
|
||||
align-self: stretch;
|
||||
border-left: 1px solid $chatActionsSeparatorColor;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
|
||||
.toolbox-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#chatconversation {
|
||||
width: 100%;
|
||||
.chatmessage {
|
||||
background-color: $chatRemoteMessageBackgroundColor;
|
||||
border-radius: 0px 6px 6px 6px;
|
||||
display: inline-block;
|
||||
margin-top: 3px;
|
||||
color: white;
|
||||
|
||||
&.privatemessage {
|
||||
background-color: $chatPrivateMessageBackgroundColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make header close button more easily tappable on mobile.
|
||||
*/
|
||||
.mobile-browser .chat-dialog-header .jitsi-icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
background: #36383C;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
52
css/_connection-info.scss
Normal file
@@ -0,0 +1,52 @@
|
||||
%connection-info {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
|
||||
td {
|
||||
padding: 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.connection-info
|
||||
{
|
||||
@extend %connection-info;
|
||||
|
||||
/**
|
||||
* Apply negative margin to reduce the appearance of padding in AtlasKit
|
||||
* InlineDialog.
|
||||
*/
|
||||
margin: -15px;
|
||||
|
||||
> 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;
|
||||
}
|
||||
|
||||
.connection-actions {
|
||||
margin: 10px auto;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
84
css/_connection-status.scss
Normal file
@@ -0,0 +1,84 @@
|
||||
.con-status {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
width: 100%;
|
||||
z-index: $toolbarZ + 3;
|
||||
|
||||
&-container {
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
margin: 0 auto;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
&-header {
|
||||
background: rgba(28, 32, 37, .5);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-circle {
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
&--good {
|
||||
background: #31B76A;
|
||||
}
|
||||
|
||||
&--poor {
|
||||
background: #E12D2D;
|
||||
}
|
||||
|
||||
&--non-optimal {
|
||||
background: #E39623;
|
||||
}
|
||||
|
||||
&-arrow {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
border-radius: 3px;
|
||||
margin-left: 8px;
|
||||
margin-right: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
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: rgba(28, 32, 37, .5);
|
||||
border-top: 1px solid #5E6D7A;
|
||||
padding: 16px;
|
||||
transition: opacity 0.16s ease-out;
|
||||
|
||||
&-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@
|
||||
}
|
||||
|
||||
// Override @Atlaskit/inline-dialog styles
|
||||
.cpick-container > div:nth-child(2) {
|
||||
.cpick-container > div > div:nth-child(2) > div > div {
|
||||
outline: none;
|
||||
padding: 8px 0 0 0;
|
||||
}
|
||||
|
||||