Compare commits

..

1 Commits

Author SHA1 Message Date
Hristo Terezov
9235000a7f ref(remote-control): Use React/Redux. 2020-12-11 14:24:33 -06:00
4158 changed files with 177773 additions and 317730 deletions

6
.buckconfig Normal file
View File

@@ -0,0 +1,6 @@
[android]
target = Google Inc.:Google APIs:23
[maven_repositories]
central = https://repo1.maven.org/maven2

View File

@@ -1,15 +0,0 @@
{
"name": "Jitsi Meet Dev Container",
"image": "mcr.microsoft.com/devcontainers/universal:2",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "20"
}
},
"hostRequirements": {
"cpus": 4,
"memory": "8gb",
"storage": "32gb"
},
"postCreateCommand": "bash -i -c 'nvm use && npm install && cp tsconfig.web.json tsconfig.json'"
}

View File

@@ -6,11 +6,8 @@ charset = utf-8
end_of_line = lf end_of_line = lf
indent_size = 4 indent_size = 4
indent_style = space indent_style = space
max_line_length = 120 max_line_length = 80
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[Makefile] [Makefile]
indent_style = tab indent_style = tab

View File

@@ -1,24 +1,12 @@
# The build artifacts of the jitsi-meet project. # The build artifacts of the jitsi-meet project.
build/* build/*
doc/*
# Third-party source code which we (1) do not want to modify or (2) try to # Third-party source code which we (1) do not want to modify or (2) try to
# modify as little as possible. # modify as little as possible.
flow-typed/*
libs/* 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 # 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 # not seem to be a reason why we will want to risk being inconsistent with our
# remaining JavaScript source code. # remaining JavaScript source code.
!.eslintrc.js !.eslintrc.js
# Not worth it.
actionTypes.ts
# It's not complete until all files are copied at build time.
react-native-sdk/
*.d.ts

View File

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

118
.flowconfig Normal file
View File

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

48
.github/ISSUE_TEMPLATE/1-bug-report.md vendored Normal file
View File

@@ -0,0 +1,48 @@
---
name: Bug report
about: Create a report to help us improve
---
<!--
This issue tracker is only for reporting bugs and tracking issues related to the source code.
Before posting, please make sure to check if the same or similar bugs have already been discussed: https://github.com/jitsi/jitsi-meet/issues
General questions regarding usage, installation, etc. should be posted at https://community.jitsi.org. They will be closed if posted here.
-->
### Description:
<!-- Please describe the bug clearly and concisely. -->
### Steps to reproduce:
1. <!-- Open '...' -->
2. <!-- Click on '...' -->
3. <!-- and so on... -->
### Expected behavior:
<!-- Please describe what should happen. -->
### Actual behavior:
<!-- Please describe what actually happens. -->
<!-- Please attach screenshot if possible. -->
### Server information:
- Jitsi Meet version:
- Operating System:
### Client information:
- Browser / app version:
- Operating System:
### Additional information:
<!-- Please provide additional information about the bug, if any. -->

View File

@@ -1,53 +0,0 @@
name: Bug report
description: File a bug report and help us improve
body:
- type: markdown
attributes:
value: |
This issue tracker is only for reporting bugs and tracking issues related to the source code.
**Before posting, please make sure to check if the same or similar bugs have already been reported.**
⚠️ General questions regarding usage, installation, etc. should be posted in our [community forum](https://community.jitsi.org).
- type: textarea
attributes:
label: What happened?
description: Please describe the problem. Be as detailed as possible.
validations:
required: true
- type: checkboxes
attributes:
label: Platform
description: On what platforms can you reproduce the problem?
options:
- label: Chrome (or Chromium based)
- label: Firefox
- label: Safari
- label: Other desktop browser
- label: Android browser
- label: iOS browser
- label: Electron app
- label: Android mobile app
- label: iOS mobile app
- label: Custom app using a mobile SDK
- type: input
attributes:
label: Browser / app / sdk version
description: Please provide the version of the browser / app / sdk where the problem manifests.
validations:
required: true
- type: textarea
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. The browser console JS logs (if applicable) is a good start. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: checkboxes
attributes:
label: Reproducibility
description: Does the problem reproduce on meet.jit.si using Chrome, Firefox or the official mobile apps?
options:
- label: The problem is reproducible on meet.jit.si
- type: textarea
attributes:
label: More details?
description: Please provide more details in case they apply (such as the Jitsi Meet version you are running, if you are hosting your own server).

View File

@@ -0,0 +1,25 @@
---
name: "Feature request"
about: Suggest an idea for this project
title: ''
labels: 'feature-request'
assignees: ''
---
<!--
Thank you for suggesting an idea to make Jitsi Meet better.
Please fill in as much of the template below as you're able.
Note that the ultimate decision for implementing features lies on the Jitsi team, not all feature requests shall be accepted.
-->
**Is your feature request related to a problem you are facing?**
Please describe the problem you are trying to solve.
**Describe the solution you'd like**
Please describe the desired behavior.
**Describe alternatives you've considered**
Please describe alternative solutions or features you have considered.

View File

@@ -1,22 +0,0 @@
name: Feature request
description: Suggest an idea for Jitsi Meet
labels: ["feature-request"]
body:
- type: markdown
attributes:
value: |
Thank you for suggesting an idea to make Jitsi Meet better.
**Note**: the ultimate decision for implementing features lies on the Jitsi team, not all feature requests shall be accepted.
- type: textarea
attributes:
label: What problem are you trying to solve?
description: Tell us what problem your feature request would solve.
- type: textarea
attributes:
label: What solution would you like to see?
description: Please describe the desired behavior or feature.
- type: textarea
attributes:
label: Is there an alternative?
description: Please describe alternative solutions or features you have considered.

View File

@@ -1,5 +1,5 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Need help with your Jitsi Meet installation? - name: Need help with Jitsi Meet?
url: https://community.jitsi.org url: https://community.jitsi.org
about: Please ask it in our community forum. about: Please ask it in our community.

16
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 90
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- confirmed
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@@ -1,26 +0,0 @@
name: Lua CI
on: [pull_request]
jobs:
luacheck:
name: Luacheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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 . \
--exclude-files=resources/prosody-plugins/mod_firewall/mod_firewall.lua | awk -F: '
{
print $0
printf "::warning file=%s,line=%s,col=%s::%s\n", $1, $2, $3, $4
}
'

View File

@@ -3,183 +3,14 @@ name: Simple CI
on: [pull_request] on: [pull_request]
jobs: jobs:
lint: run-ci:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Check Node / npm versions
run: |
node -v
npm -v
- name: Get changed files
id: changed-files
uses: jitsi/changed-files@main
- name: Get changed lang files
id: lang-files
run: echo "all=$(echo "${{ steps.changed-files.outputs.all_changed_files }}" | grep -oE 'lang\/\S+' | tr '\n' ' ')" >> "$GITHUB_OUTPUT"
- run: npm install
- name: Check git status
run: git status
- name: Normalize lang files to ensure sorted
if: steps.lang-files.outputs.all
run: npm run lang-sort
- name: Check lang files are formatted correctly
if: steps.lang-files.outputs.all
run: npm run lint:lang
- 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 && npm run tsc:ci
frontend:
name: Build Frontend name: Build Frontend
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Check Node / npm versions
run: |
node -v
npm -v
- run: npm install
- run: make
- name: Check config.js syntax
run: node config.js
android-rn-bundle-build:
name: Build mobile bundle (Android)
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Check Node / npm versions
run: |
node -v
npm -v
- run: npm install
- run: npx react-native bundle --entry-file react/index.native.js --platform android --bundle-output /tmp/android.bundle --reset-cache
ios-rn-bundle-build:
name: Build mobile bundle (iOS)
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Check Node / npm versions
run: |
node -v
npm -v
- run: npm install
- name: setup Xcode
run: |
uname -a
xcode-select -p
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
xcodebuild -version
- name: setup-cocoapods
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
- run: npx react-native info
- name: Install Pods
working-directory: ./ios
run: bundle exec pod install --repo-update --deployment
- run: npx react-native bundle --entry-file react/index.native.js --platform ios --bundle-output /tmp/ios.bundle --reset-cache
android-sdk-build:
name: Build mobile SDK (Android)
runs-on: ubuntu-latest
container: reactnativecommunity/react-native-android:v13.0
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Check Node / npm versions
run: |
node -v
npm -v
- run: npm install
- run: |
cd android
./gradlew :sdk:clean
./gradlew :sdk:assembleRelease
ios-sdk-build:
name: Build mobile SDK (iOS)
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Check Node / npm versions
run: |
node -v
npm -v
- run: npm install
- name: setup Xcode
run: |
uname -a
xcode-select -p
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
xcodebuild -version
- name: setup-cocoapods
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
- run: npx react-native info
- name: Install Pods
working-directory: ./ios
run: bundle exec pod install --repo-update --deployment
- run: |
xcodebuild clean \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeetSDK
xcodebuild archive \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeetSDK \
-configuration Release \
-sdk iphoneos \
-destination='generic/platform=iOS' \
-archivePath ios/sdk/out/ios-device \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
xcodebuild -create-xcframework \
-framework ios/sdk/out/ios-device.xcarchive/Products/Library/Frameworks/JitsiMeetSDK.framework \
-output ios/sdk/out/JitsiMeetSDK.xcframework
- run: ls -lR ios/sdk/out
debian-build:
name: Test Debian packages build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- uses: actions/setup-node@v4 - uses: actions/setup-node@v1
with: with:
node-version-file: '.nvmrc' node-version: '12.x'
cache: 'npm'
- name: Check Node / npm versions
run: |
node -v
npm -v
- run: npm install - run: npm install
- run: npm run lint
- run: make - run: make
- run: sudo apt-get install -y debhelper
- run: dpkg-buildpackage -A -rfakeroot -us -uc -d
- run: make source-package

View File

@@ -1,21 +0,0 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
with:
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
stale-pr-message: 'This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
stale-issue-label: 'stale'
stale-pr-label: 'stale'
exempt-issue-labels: 'confirmed,help-needed'
exempt-pr-labels: 'confirmed'
days-before-issue-stale: 60
days-before-pr-stale: 90
days-before-issue-close: 10
days-before-pr-close: 10

31
.gitignore vendored
View File

@@ -38,7 +38,6 @@ DerivedData
*.dSYM.zip *.dSYM.zip
*.xcuserstate *.xcuserstate
project.xcworkspace project.xcworkspace
**/.xcode.env.local
# Android/IntelliJ # Android/IntelliJ
# #
@@ -62,17 +61,14 @@ buck-out/
# fastlane # fastlane
# #
.bundle/ */fastlane/report.xml
**/fastlane/report.xml */fastlane/Preview.html
**/fastlane/Preview.html
**/fastlane/test_output
# Build artifacts # Build artifacts
*.jsbundle *.jsbundle
*.framework *.framework
android/app/debug android/app/debug
android/app/release android/app/release
ios/sdk/out
# precommit-hook # precommit-hook
.jshintignore .jshintignore
@@ -95,26 +91,3 @@ twa/*.apk
twa/*.aab twa/*.aab
twa/assetlinks.json twa/assetlinks.json
tsconfig.json
# React Native SDK
#
react-native-sdk/*.tgz
react-native-sdk/android/src
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetReactNativePackage.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetOngoingConferenceService.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JMOngoingConferenceModule.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/RNOngoingNotification.java
react-native-sdk/images
react-native-sdk/ios
react-native-sdk/lang
react-native-sdk/modules
react-native-sdk/node_modules
react-native-sdk/react
react-native-sdk/service
react-native-sdk/sounds
# tests
tests/.env
test-results

View File

@@ -1,8 +0,0 @@
global = false
unused = false
redefined = false
ignore = { "581" }
max_line_length = false
color = false
formatter = "plain"
quiet = 1

2
.npmrc
View File

@@ -1,3 +1 @@
package-lock=true package-lock=true
; FIXME Set legacy-peer-deps=false when we upgrade RN.
legacy-peer-deps=true

1
.nvmrc
View File

@@ -1 +0,0 @@
22

6
.travis.yml Normal file
View File

@@ -0,0 +1,6 @@
osx_image: xcode11.1
language: objective-c
script:
- "./ios/travis-ci/build-ipa.sh"
after_script:
- sleep 10

View File

@@ -1,20 +1,154 @@
# Follow Our Updated Guide to See How You Can Contribute # How to contribute
We would love to have your help. Before you start working however, please read
and follow this short guide.
**Hello there! 👋** # 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
problem can be reproduced.
We're thrilled that you're eager to contribute to **Jitsi Meet! ❤️** # Code contributions
Found a bug and know how to fix it? Great! Please read on.
Your interest in improving our platform means a lot to us. To ensure your contributions align seamlessly with our goals and processes, we've recently updated our guide. This guide will provide you with clear instructions on how to get involved effectively. ## Contributor License Agreement
While the Jitsi projects are released under the
[Apache License 2.0](https://github.com/jitsi/jitsi-meet/blob/master/LICENSE), the copyright
holder and principal creator is [8x8](https://www.8x8.com/). To
ensure that we can continue making these projects available under an Open Source license,
we need you to sign our Apache-based contributor
license agreement as either a [corporation](https://jitsi.org/ccla) or an
[individual](https://jitsi.org/icla). If you cannot accept the terms laid out
in the agreement, unfortunately, we cannot accept your contribution.
### 📖 Get Started ## Creating Pull Requests
- Make sure your code passes the linter rules beforehand. The linter is executed
automatically when committing code.
- Perform **one** logical change per pull request.
- Maintain a clean list of commits, squash them if necessary.
- Rebase your topic branch on top of the master branch before creating the pull
request.
Ready to get started? Head over to our [Jitsi Meet Handbook](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-contributing/) and let's make **Jitsi Meet** even better together! ## Coding style
### 💬 Join the Discussion ### Comments
Have questions or need help? Join our community discussions on the [Jitsi Forum](https://community.jitsi.org/) where contributors and maintainers can assist you. * Comments documenting the source code are required.
### ❗Additional Note * Comments from which documentation is automatically generated are **not**
Before sending us your code, double-check that it meets our coding standards. You can do this by running a command: `npm run lint`. If there are any issues, don't worry! You can fix them by running: `npm run lint-fix`. Once your code passes these checks, feel free to submit your pull request. subject to case-by-case decisions. Such comments are used, for example, on
types and their members. Examples of tools which automatically generate
documentation from such comments include JSDoc, Javadoc, Doxygen.
**Happy coding!** * Comments which are not automatically processed are strongly encouraged. They
are subject to case-by-case decisions. Such comments are often observed in
function bodies.
* Comments should be formatted as proper English sentences. Such formatting pays
attention to, for example, capitalization and punctuation.
### Duplication
* Don't copy-paste source code. Reuse it.
### Formatting
* Line length is limited to 120 characters.
* Sort by alphabetical order in order to make the addition of new entities as
easy as looking a word up in a dictionary. Otherwise, one risks duplicate
entries (with conflicting values in the cases of key-value pairs). For
example:
* Within an `import` of multiple names from a module, sort the names in
alphabetical order. (Of course, the default name stays first as required by
the `import` syntax.)
````javascript
import {
DOMINANT_SPEAKER_CHANGED,
JITSI_CLIENT_CONNECTED,
JITSI_CLIENT_CREATED,
JITSI_CLIENT_DISCONNECTED,
JITSI_CLIENT_ERROR,
JITSI_CONFERENCE_JOINED,
MODERATOR_CHANGED,
PEER_JOINED,
PEER_LEFT,
RTC_ERROR
} from './actionTypes';
````
* Within a group of imports (e.g. groups of imports delimited by an empty line
may be: third-party modules, then project modules, and eventually the
private files of a module), sort the module names in alphabetical order.
````javascript
import React, { Component } from 'react';
import { connect } from 'react-redux';
````
### Indentation
* Align `switch` and `case`/`default`. Don't indent the `case`/`default` more
than its `switch`.
````javascript
switch (i) {
case 0:
...
break;
default:
...
}
````
### Naming
* An abstraction should have one name within the project and across multiple
projects. For example:
* The instance of lib-jitsi-meet's `JitsiConnection` type should be named
`connection` or `jitsiConnection` in jitsi-meet, not `client`.
* The class `ReducerRegistry` should be defined in ReducerRegistry.js and its
imports in other files should use the same name. Don't define the class
`Registry` in ReducerRegistry.js and then import it as `Reducers` in other
files.
* The names of global constants (including ES6 module-global constants) should
be written in uppercase with underscores to separate words. For example,
`BACKGROUND_COLOR`.
* The underscore character at the beginning of a name signals that the
respective variable, function, property is non-public i.e. private, protected,
or internal. In contrast, the lack of an underscore at the beginning of a name
signals public API.
### Feature layout
When adding a new feature, this would be the usual layout.
```
react/features/sample/
├── actionTypes.js
├── actions.js
├── components
│   ├── AnotherComponent.js
│   ├── OneComponent.js
│   └── index.js
├── middleware.js
└── reducer.js
```
The middleware must be imported in `react/features/app/` specifically
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
component. Features / files requiring those must import them explicitly.
This has not always been the case and the entire codebase hasn't been migrated to
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.

16
Gemfile
View File

@@ -1,16 +0,0 @@
source "https://rubygems.org"
ruby ">= 3.4.0"
gem "cocoapods", "~> 1.16"
# (Optional) Fastlane for automation
gem "fastlane"
gem "abbrev"
gem "logger"
gem "mutex_m"
gem "csv"
gem "bigdecimal"
# (Optional) Bundler itself to ensure consistency
gem "bundler"

View File

@@ -1,331 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.7)
base64
nkf
rexml
abbrev (0.1.2)
activesupport (7.2.2.1)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.1)
aws-partitions (1.1050.0)
aws-sdk-core (3.218.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.98.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.181.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.11.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.9)
claide (1.1.0)
cocoapods (1.16.2)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.16.2)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 2.1, < 3.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.6.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
fourflusher (>= 2.3.0, < 3.0)
gh_inspector (~> 1.0)
molinillo (~> 0.8.0)
nap (~> 1.0)
ruby-macho (>= 2.3.0, < 3.0)
xcodeproj (>= 1.27.0, < 2.0)
cocoapods-core (1.16.2)
activesupport (>= 5.0, < 8)
addressable (~> 2.8)
algoliasearch (~> 1.0)
concurrent-ruby (~> 1.1)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
netrc (~> 0.11)
public_suffix (~> 4.0)
typhoeus (~> 1.0)
cocoapods-deintegrate (1.0.5)
cocoapods-downloader (2.1)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.1)
cocoapods-trunk (1.6.0)
nap (>= 0.8, < 2.0)
netrc (~> 0.11)
cocoapods-try (1.2.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
concurrent-ruby (1.3.5)
connection_pool (2.5.0)
csv (3.3.2)
declarative (0.0.20)
digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.6.20240107)
dotenv (2.8.1)
drb (2.2.1)
emoji_regex (3.2.3)
escape (0.0.4)
ethon (0.16.0)
ffi (>= 1.15.0)
excon (0.112.0)
faraday (1.10.4)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.1.0)
multipart-post (~> 2.0)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.0)
fastlane (2.226.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored (~> 1.2)
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
fastlane-sirp (>= 1.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-env (>= 1.6.0, < 2.0.0)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (>= 0.1.1, < 1.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.5)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (~> 3)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.0)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
ffi (1.17.1)
ffi (1.17.1-aarch64-linux-gnu)
ffi (1.17.1-aarch64-linux-musl)
ffi (1.17.1-arm-linux-gnu)
ffi (1.17.1-arm-linux-musl)
ffi (1.17.1-arm64-darwin)
ffi (1.17.1-x86-linux-gnu)
ffi (1.17.1-x86-linux-musl)
ffi (1.17.1-x86_64-darwin)
ffi (1.17.1-x86_64-linux-gnu)
ffi (1.17.1-x86_64-linux-musl)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.3)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.31.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.7.1)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.4.0)
google-cloud-storage (1.47.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.31.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.8.1)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.8)
domain_name (~> 0.5)
httpclient (2.8.3)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
jmespath (1.6.2)
json (2.10.1)
jwt (2.10.1)
base64
logger (1.6.6)
mini_magick (4.13.2)
mini_mime (1.1.5)
minitest (5.25.4)
molinillo (0.8.0)
multi_json (1.15.0)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
nap (1.1.0)
naturally (2.2.1)
netrc (0.11.0)
nkf (0.2.0)
optparse (0.6.0)
os (1.1.4)
plist (3.7.2)
public_suffix (4.0.7)
rake (13.2.1)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.4.1)
rouge (3.28.0)
ruby-macho (2.5.1)
ruby2_keywords (0.0.5)
rubyzip (2.4.1)
securerandom (0.4.1)
security (0.1.5)
signet (0.19.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.10)
CFPropertyList
naturally
sysrandom (1.0.5)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.2)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
typhoeus (1.4.1)
ethon (>= 0.9.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
uber (0.1.0)
unicode-display_width (2.6.0)
word_wrap (1.0.0)
xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)
xcpretty (0.4.0)
rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
aarch64-linux-gnu
aarch64-linux-musl
arm-linux-gnu
arm-linux-musl
arm64-darwin
ruby
x86-linux-gnu
x86-linux-musl
x86_64-darwin
x86_64-linux-gnu
x86_64-linux-musl
DEPENDENCIES
abbrev
bigdecimal
bundler
cocoapods (~> 1.16)
csv
fastlane
logger
mutex_m
RUBY VERSION
ruby 3.4.2p28
BUNDLED WITH
2.6.3

119
Makefile
View File

@@ -1,40 +1,29 @@
BUILD_DIR = build BUILD_DIR = build
CLEANCSS = ./node_modules/.bin/cleancss CLEANCSS = ./node_modules/.bin/cleancss
DEPLOY_DIR = libs DEPLOY_DIR = libs
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
OLM_DIR = node_modules/@matrix-org/olm LIBFLAC_DIR = node_modules/libflacjs/dist/min/
TF_WASM_DIR = node_modules/@tensorflow/tfjs-backend-wasm/dist/ OLM_DIR = node_modules/olm
RNNOISE_WASM_DIR = node_modules/@jitsi/rnnoise-wasm/dist RNNOISE_WASM_DIR = node_modules/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
NODE_SASS = ./node_modules/.bin/sass NODE_SASS = ./node_modules/.bin/sass
NPM = npm NPM = npm
OUTPUT_DIR = . OUTPUT_DIR = .
STYLES_BUNDLE = css/all.bundle.css STYLES_BUNDLE = css/all.bundle.css
STYLES_DESTINATION = css/all.css STYLES_DESTINATION = css/all.css
STYLES_MAIN = css/main.scss STYLES_MAIN = css/main.scss
ifeq ($(OS),Windows_NT) WEBPACK = ./node_modules/.bin/webpack
WEBPACK = .\node_modules\.bin\webpack --progress WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack-dev-server
WEBPACK_DEV_SERVER = .\node_modules\.bin\webpack serve --mode development --progress
else
WEBPACK = ./node_modules/.bin/webpack --progress
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack serve --mode development --progress
endif
all: compile deploy all: compile deploy clean
compile: clean compile:
NODE_OPTIONS=--max-old-space-size=8192 \ $(WEBPACK) -p
$(WEBPACK)
clean: clean:
rm -fr $(BUILD_DIR) rm -fr $(BUILD_DIR)
.NOTPARALLEL: .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: deploy-init:
rm -fr $(DEPLOY_DIR) rm -fr $(DEPLOY_DIR)
@@ -43,26 +32,43 @@ deploy-init:
deploy-appbundle: deploy-appbundle:
cp \ cp \
$(BUILD_DIR)/app.bundle.min.js \ $(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.map \
$(BUILD_DIR)/external_api.min.js \ $(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.map \
$(BUILD_DIR)/alwaysontop.min.js \ $(BUILD_DIR)/alwaysontop.min.js \
$(BUILD_DIR)/alwaysontop.min.js.map \ $(BUILD_DIR)/alwaysontop.min.map \
$(BUILD_DIR)/face-landmarks-worker.min.js \ $(OUTPUT_DIR)/analytics-ga.js \
$(BUILD_DIR)/face-landmarks-worker.min.js.map \ $(BUILD_DIR)/analytics-ga.min.js \
$(BUILD_DIR)/noise-suppressor-worklet.min.js \ $(BUILD_DIR)/analytics-ga.min.map \
$(BUILD_DIR)/noise-suppressor-worklet.min.js.map \ $(BUILD_DIR)/video-blur-effect.min.js \
$(BUILD_DIR)/screenshot-capture-worker.min.js \ $(BUILD_DIR)/video-blur-effect.min.map \
$(BUILD_DIR)/screenshot-capture-worker.min.js.map \ $(BUILD_DIR)/rnnoise-processor.min.js \
$(DEPLOY_DIR) $(BUILD_DIR)/rnnoise-processor.min.map \
cp \
$(BUILD_DIR)/close3.min.js \ $(BUILD_DIR)/close3.min.js \
$(BUILD_DIR)/close3.min.js.map \ $(BUILD_DIR)/close3.min.map \
$(DEPLOY_DIR) || true $(DEPLOY_DIR)
deploy-lib-jitsi-meet: deploy-lib-jitsi-meet:
cp \ cp \
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.* \ $(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_DIR)
deploy-olm: deploy-olm:
@@ -70,59 +76,26 @@ deploy-olm:
$(OLM_DIR)/olm.wasm \ $(OLM_DIR)/olm.wasm \
$(DEPLOY_DIR) $(DEPLOY_DIR)
deploy-tf-wasm:
cp \
$(TF_WASM_DIR)/*.wasm \
$(DEPLOY_DIR)
deploy-rnnoise-binary: deploy-rnnoise-binary:
cp \ cp \
$(RNNOISE_WASM_DIR)/rnnoise.wasm \ $(RNNOISE_WASM_DIR)/rnnoise.wasm \
$(DEPLOY_DIR) $(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: deploy-css:
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \ $(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
$(CLEANCSS) --skip-rebase $(STYLES_BUNDLE) > $(STYLES_DESTINATION) && \ $(CLEANCSS) --skip-rebase $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
rm $(STYLES_BUNDLE) rm $(STYLES_BUNDLE)
deploy-local: deploy-local:
([ ! -x deploy-local.sh ] || ./deploy-local.sh) ([ ! -x deploy-local.sh ] || ./deploy-local.sh)
.NOTPARALLEL: .NOTPARALLEL:
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-olm deploy-tf-wasm deploy-excalidraw-dev deploy-face-landmarks dev: deploy-init deploy-css deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-olm
$(WEBPACK_DEV_SERVER) $(WEBPACK_DEV_SERVER) --detect-circular-deps
source-package: compile deploy source-package:
mkdir -p source_package/jitsi-meet/css && \ mkdir -p source_package/jitsi-meet/css && \
cp -r *.js *.html resources/*.txt fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \ cp -r *.js *.html resources/*.txt connection_optimization favicon.ico fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
cp css/all.css source_package/jitsi-meet/css && \ cp css/all.css source_package/jitsi-meet/css && \
(cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \ (cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \
rm -rf source_package rm -rf source_package

115
README.md
View File

@@ -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 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).
video conferencing platforms with state-of-the-art video quality and features.
<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"> Jitsi Meet allows very efficient collaboration. Users can stream their desktop or only some windows. It also supports shared document editing with Etherpad.
<img src="https://raw.githubusercontent.com/jitsi/jitsi-meet/master/readme-img1.png" width="900" />
</p>
<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 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).
* 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
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 scalable and free to use. All you need is a Google, Facebook or GitHub account in order to start a meeting. All browsers are supported! ## Download
Using mobile? No problem, you can either use your mobile web browser or our fully-featured | Latest stable release | [![release](https://img.shields.io/badge/release-latest-green.svg)](https://github.com/jitsi/jitsi-meet/releases/latest) |
mobile apps: |---|---|
| Android | Android (F-Droid) | iOS | You can download Debian/Ubuntu binaries:
|:-:|:-:|:-:| * [stable](https://download.jitsi.org/stable/) ([instructions](https://jitsi.org/downloads/ubuntu-debian-installations-instructions/))
| [<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/packages/org.jitsi.meet/) | [<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905) | * [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 You can download source archives (produced by ```make source-package```):
developed you can also sign up for our open beta testing here: * [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) * [Android](https://play.google.com/apps/testing/org.jitsi.meet)
* [iOS](https://testflight.apple.com/join/isy6ja7S) * [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. ## Development
Advanced users also have the possibility of building all the components from source.
You can check the latest releases [here](https://jitsi.github.io/handbook/docs/releases). 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).
## 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).
## Contributing ## Contributing
If you are looking to contribute to Jitsi Meet, first of all, thank you! Please If you are looking to contribute to Jitsi Meet, first of all, thank you! Please
see our [guidelines for contributing](CONTRIBUTING.md). see our [guidelines for contributing](CONTRIBUTING.md).
<br /> ## Embedding in external applications
<br />
<footer> Jitsi Meet provides a very flexible way of embedding in external applications by using the [Jitsi Meet API](doc/api.md).
<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. ## Security
</p>
</footer> 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!

View File

@@ -4,6 +4,6 @@
We take security very seriously and develop all Jitsi projects to be secure and safe. We take security very seriously and develop all Jitsi projects to be secure and safe.
If you find (or simply suspect) a security issue in any of the Jitsi projects, please report it to us via [HackerOne](https://hackerone.com/8x8-bounty) or send us an email to security@jitsi.org. If you find (or simply suspect) a security issue in any of the Jitsi projects, please report it to us via [HackerOne](https://hackerone.com/8x8) or send us an email to security@jitsi.org.
**We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.** **We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.**

View File

@@ -1 +0,0 @@
OK

163
analytics-ga.js Normal file
View File

@@ -0,0 +1,163 @@
/* global ga */
(function(ctx) {
/**
*
*/
function Analytics(options) {
/* eslint-disable */
if (!options.googleAnalyticsTrackingId) {
console.log(
'Failed to initialize Google Analytics handler, no tracking ID');
return;
}
/**
* Google Analytics
* TODO: Keep this local, there's no need to add it to window.
*/
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', options.googleAnalyticsTrackingId, 'auto');
ga('send', 'pageview');
/* eslint-enable */
}
/**
* Extracts the integer to use for a Google Analytics event's value field
* from a lib-jitsi-meet analytics event.
* @param {Object} event - The lib-jitsi-meet analytics event.
* @returns {Object} - The integer to use for the 'value' of a Google
* Analytics event.
* @private
*/
Analytics.prototype._extractAction = function(event) {
// Page events have a single 'name' field.
if (event.type === 'page') {
return event.name;
}
// All other events have action, actionSubject, and source fields. All
// three fields are required, and the often jitsi-meet and
// lib-jitsi-meet use the same value when separate values are not
// necessary (i.e. event.action == event.actionSubject).
// Here we concatenate these three fields, but avoid adding the same
// value twice, because it would only make the GA event's action harder
// to read.
let action = event.action;
if (event.actionSubject && event.actionSubject !== event.action) {
// Intentionally use string concatenation as analytics needs to
// work on IE but this file does not go through babel. For some
// reason disabling this globally for the file does not have an
// effect.
// eslint-disable-next-line prefer-template
action = event.actionSubject + '.' + action;
}
if (event.source && event.source !== event.action
&& event.source !== event.action) {
// eslint-disable-next-line prefer-template
action = event.source + '.' + action;
}
return action;
};
/**
* Extracts the integer to use for a Google Analytics event's value field
* from a lib-jitsi-meet analytics event.
* @param {Object} event - The lib-jitsi-meet analytics event.
* @returns {Object} - The integer to use for the 'value' of a Google
* Analytics event, or NaN if the lib-jitsi-meet event doesn't contain a
* suitable value.
* @private
*/
Analytics.prototype._extractValue = function(event) {
let value = event && event.attributes && event.attributes.value;
// Try to extract an integer from the "value" attribute.
value = Math.round(parseFloat(value));
return value;
};
/**
* Extracts the string to use for a Google Analytics event's label field
* from a lib-jitsi-meet analytics event.
* @param {Object} event - The lib-jitsi-meet analytics event.
* @returns {string} - The string to use for the 'label' of a Google
* Analytics event.
* @private
*/
Analytics.prototype._extractLabel = function(event) {
let label = '';
// The label field is limited to 500B. We will concatenate all
// attributes of the event, except the user agent because it may be
// lengthy and is probably included from elsewhere.
for (const property in event.attributes) {
if (property !== 'permanent_user_agent'
&& property !== 'permanent_callstats_name'
&& event.attributes.hasOwnProperty(property)) {
// eslint-disable-next-line prefer-template
label += property + '=' + event.attributes[property] + '&';
}
}
if (label.length > 0) {
label = label.slice(0, -1);
}
return label;
};
/**
* This is the entry point of the API. The function sends an event to
* google analytics. The format of the event is described in
* AnalyticsAdapter in lib-jitsi-meet.
* @param {Object} event - the event in the format specified by
* lib-jitsi-meet.
*/
Analytics.prototype.sendEvent = function(event) {
if (!event || !ga) {
return;
}
const ignoredEvents
= [ 'e2e_rtt', 'rtp.stats', 'rtt.by.region', 'available.device',
'stream.switch.delay', 'ice.state.changed', 'ice.duration' ];
// Temporary removing some of the events that are too noisy.
if (ignoredEvents.indexOf(event.action) !== -1) {
return;
}
const gaEvent = {
'eventCategory': 'jitsi-meet',
'eventAction': this._extractAction(event),
'eventLabel': this._extractLabel(event)
};
const value = this._extractValue(event);
if (!isNaN(value)) {
gaEvent.eventValue = value;
}
ga('send', 'event', gaEvent);
};
if (typeof ctx.JitsiMeetJS === 'undefined') {
ctx.JitsiMeetJS = {};
}
if (typeof ctx.JitsiMeetJS.app === 'undefined') {
ctx.JitsiMeetJS.app = {};
}
if (typeof ctx.JitsiMeetJS.app.analyticsHandlers === 'undefined') {
ctx.JitsiMeetJS.app.analyticsHandlers = [];
}
ctx.JitsiMeetJS.app.analyticsHandlers.push(Analytics);
})(window);
/* eslint-enable prefer-template */

View File

@@ -16,6 +16,10 @@ android {
compileSdkVersion rootProject.ext.compileSdkVersion compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion buildToolsVersion rootProject.ext.buildToolsVersion
packagingOptions {
exclude 'lib/*/libhermes*.so'
}
defaultConfig { defaultConfig {
applicationId 'org.jitsi.meet' applicationId 'org.jitsi.meet'
versionCode vcode versionCode vcode
@@ -42,7 +46,6 @@ android {
debug { debug {
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}" buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}" buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
applicationIdSuffix ".debug"
} }
release { release {
// Uncomment the following line for singing a test release build. // Uncomment the following line for singing a test release build.
@@ -69,24 +72,23 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
namespace 'org.jitsi.meet'
} }
dependencies { dependencies {
implementation 'androidx.appcompat:appcompat:1.5.1' implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.2.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.13' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
if (!rootProject.ext.libreBuild) { if (!rootProject.ext.libreBuild) {
// Sync with react-native-google-signin implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation 'com.google.android.gms:play-services-auth:20.5.0'
// Firebase // Firebase
// - Crashlytics // - Crashlytics
// - Dynamic Links // - Dynamic Links
implementation 'com.google.firebase:firebase-analytics:21.3.0' implementation 'com.google.firebase:firebase-analytics:17.5.0'
implementation 'com.google.firebase:firebase-crashlytics:18.4.3' implementation 'com.google.firebase:firebase-crashlytics:17.2.1'
implementation 'com.google.firebase:firebase-dynamic-links:21.1.0' implementation 'com.google.firebase:firebase-dynamic-links:19.1.0'
} }
implementation project(':sdk') implementation project(':sdk')
@@ -107,7 +109,6 @@ gradle.projectsEvaluated {
def dropboxActivity = """ def dropboxActivity = """
<activity <activity
android:configChanges="keyboard|orientation" android:configChanges="keyboard|orientation"
android:exported="true"
android:launchMode="singleTask" android:launchMode="singleTask"
android:name="com.dropbox.core.android.AuthActivity"> android:name="com.dropbox.core.android.AuthActivity">
<intent-filter> <intent-filter>
@@ -121,7 +122,7 @@ gradle.projectsEvaluated {
android.applicationVariants.all { variant -> android.applicationVariants.all { variant ->
variant.outputs.each { output -> variant.outputs.each { output ->
output.getProcessManifestProvider().get().doLast { output.getProcessManifestProvider().get().doLast {
def outputDir = multiApkManifestOutputDirectory.get().asFile def outputDir = manifestOutputDirectory.get().asFile
def manifestPath = new File(outputDir, 'AndroidManifest.xml') def manifestPath = new File(outputDir, 'AndroidManifest.xml')
def charset = 'UTF-8' def charset = 'UTF-8'
def text def text

View File

@@ -1,8 +1,3 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
# Add project specific ProGuard rules here. # Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified # By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
@@ -14,25 +9,20 @@
# Add any project specific keep options here: # Add any project specific keep options here:
# Disabling obfuscation is useful if you collect stack traces from production crashes
# (unless you are using a system that supports de-obfuscate the stack traces).
# -dontobfuscate
# React Native # React Native
# Keep our interfaces so they can be used by other ProGuard rules. # Keep our interfaces so they can be used by other ProGuard rules.
# See http://sourceforge.net/p/proguard/bugs/466/ # See http://sourceforge.net/p/proguard/bugs/466/
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
# Do not strip any method/class that is annotated with @DoNotStrip # Do not strip any method/class that is annotated with @DoNotStrip
-keep @com.facebook.proguard.annotations.DoNotStrip class * -keep @com.facebook.proguard.annotations.DoNotStrip class *
-keep @com.facebook.common.internal.DoNotStrip class *
-keepclassmembers class * { -keepclassmembers class * {
@com.facebook.proguard.annotations.DoNotStrip *; @com.facebook.proguard.annotations.DoNotStrip *;
} @com.facebook.common.internal.DoNotStrip *;
-keep @com.facebook.proguard.annotations.DoNotStripAny class * {
*;
} }
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
@@ -40,32 +30,32 @@
*** get*(); *** get*();
} }
-keep class * implements com.facebook.react.bridge.JavaScriptModule { *; } -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
-keep class * implements com.facebook.react.bridge.NativeModule { *; } -keep class * extends com.facebook.react.bridge.NativeModule { *; }
-keepclassmembers,includedescriptorclasses class * { native <methods>; } -keepclassmembers,includedescriptorclasses class * { native <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.UIProp <fields>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; } -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; } -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
-dontwarn com.facebook.react.** -dontwarn com.facebook.react.**
-keep,includedescriptorclasses class com.facebook.react.bridge.** { *; } -keep,includedescriptorclasses class com.facebook.react.bridge.** { *; }
-keep,includedescriptorclasses class com.facebook.react.turbomodule.core.** { *; }
# hermes # okhttp
-keep class com.facebook.jni.** { *; }
-keepattributes Signature
-keepattributes *Annotation*
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn okhttp3.**
# okio # okio
-keep class sun.misc.Unsafe { *; } -keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.* -dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-keep class okio.** { *; }
-dontwarn okio.** -dontwarn okio.**
# yoga
-keep,allowobfuscation @interface com.facebook.yoga.annotations.DoNotStrip
-keep @com.facebook.yoga.annotations.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.yoga.annotations.DoNotStrip *;
}
# WebRTC # WebRTC
-keep class org.webrtc.** { *; } -keep class org.webrtc.** { *; }
@@ -95,9 +85,4 @@
# ^^^ We added the above when we switched minifyEnabled on. # ^^^ We added the above when we switched minifyEnabled on.
# Rule to avoid build errors related to SVGs. # Rule to avoid build errors related to SVGs.
-keep public class com.horcrux.svg.** {*;} -keep public class com.horcrux.svg.** {*;}
# https://github.com/facebook/fresco/issues/2638
-keep public class com.facebook.imageutils.** {
public *;
}

View File

@@ -1,9 +1,9 @@
<manifest <manifest
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jitsi.meet"
android:installLocation="auto"> android:installLocation="auto">
<application <application
android:allowBackup="true" android:allowBackup="true"
android:extractNativeLibs="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
@@ -12,11 +12,9 @@
android:name="android.content.APP_RESTRICTIONS" android:name="android.content.APP_RESTRICTIONS"
android:resource="@xml/app_restrictions" /> android:resource="@xml/app_restrictions" />
<activity <activity
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleInstance" android:launchMode="singleTask"
android:taskAffinity=""
android:name=".MainActivity" android:name=".MainActivity"
android:resizeableActivity="true" android:resizeableActivity="true"
android:supportsPictureInPicture="true" android:supportsPictureInPicture="true"

View File

@@ -30,16 +30,15 @@ import android.view.KeyEvent;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.oney.WebRTCModule.WebRTCModuleOptions;
import org.jitsi.meet.sdk.JitsiMeet; import org.jitsi.meet.sdk.JitsiMeet;
import org.jitsi.meet.sdk.JitsiMeetActivity; import org.jitsi.meet.sdk.JitsiMeetActivity;
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions; import org.jitsi.meet.sdk.JitsiMeetConferenceOptions;
import org.webrtc.Logging;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Collection; import java.util.Collection;
import java.util.Map;
/** /**
* The one and only Activity that the Jitsi Meet app needs. The * The one and only Activity that the Jitsi Meet app needs. The
@@ -76,17 +75,14 @@ public class MainActivity extends JitsiMeetActivity {
*/ */
private String defaultURL; private String defaultURL;
// JitsiMeetActivity overrides // JitsiMeetActivity overrides
// //
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
JitsiMeet.showSplashScreen(this); JitsiMeet.showSplashScreen(this);
super.onCreate(savedInstanceState);
WebRTCModuleOptions options = WebRTCModuleOptions.getInstance();
options.loggingSeverity = Logging.Severity.LS_ERROR;
super.onCreate(null);
} }
@Override @Override
@@ -151,12 +147,13 @@ public class MainActivity extends JitsiMeetActivity {
} }
private void setJitsiMeetConferenceDefaultOptions() { private void setJitsiMeetConferenceDefaultOptions() {
// Set default options // Set default options
JitsiMeetConferenceOptions defaultOptions JitsiMeetConferenceOptions defaultOptions
= new JitsiMeetConferenceOptions.Builder() = new JitsiMeetConferenceOptions.Builder()
.setWelcomePageEnabled(true)
.setServerURL(buildURL(defaultURL)) .setServerURL(buildURL(defaultURL))
.setFeatureFlag("welcomepage.enabled", true) .setFeatureFlag("call-integration.enabled", false)
.setFeatureFlag("resolution", 360)
.setFeatureFlag("server-url-change.enabled", !configurationByRestrictions) .setFeatureFlag("server-url-change.enabled", !configurationByRestrictions)
.build(); .build();
JitsiMeet.setDefaultConferenceOptions(defaultOptions); JitsiMeet.setDefaultConferenceOptions(defaultOptions);
@@ -185,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 // Activity lifecycle method overrides
// //
@@ -218,6 +220,11 @@ public class MainActivity extends JitsiMeetActivity {
super.onPictureInPictureModeChanged(isInPictureInPictureMode); super.onPictureInPictureModeChanged(isInPictureInPictureMode);
Log.d(TAG, "Is in picture-in-picture mode: " + isInPictureInPictureMode); Log.d(TAG, "Is in picture-in-picture mode: " + isInPictureInPictureMode);
if (!isInPictureInPictureMode) {
this.startActivity(new Intent(this, getClass())
.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT));
}
} }
// Helper methods // Helper methods
@@ -226,7 +233,7 @@ public class MainActivity extends JitsiMeetActivity {
private @Nullable URL buildURL(String urlStr) { private @Nullable URL buildURL(String urlStr) {
try { try {
return new URL(urlStr); return new URL(urlStr);
} catch (Exception e) { } catch (MalformedURLException e) {
return null; return null;
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 960 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -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>

View File

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

View File

@@ -2,5 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon> </adaptive-icon>

View File

@@ -2,5 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon> </adaptive-icon>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="colorPrimary">#17A0DB</color> <color name="colorPrimary">#17A0DB</color>
<color name="navigationBarColor">#161618</color> <color name="colorPrimaryDark">#1081B2</color>
</resources> </resources>

View File

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

View File

@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<restrictions xmlns:android="http://schemas.android.com/apk/res/android"> <restrictions xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Server URL configuration --> <!-- Server URL configuration -->
<restriction <restriction
android:description="@string/restriction_server_url_description" android:defaultValue="https://meet.jit.si"
android:key="SERVER_URL" android:description="@string/restriction_server_url_description"
android:restrictionType="string" android:key="SERVER_URL"
android:title="@string/restriction_server_url_title"/> android:restrictionType="string"
</restrictions> android:title="@string/restriction_server_url_title"/>
</restrictions>

View File

@@ -7,25 +7,23 @@ import org.gradle.util.VersionNumber
buildscript { buildscript {
repositories { repositories {
google() google()
mavenCentral() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.4.2' classpath 'com.android.tools.build:gradle:4.0.2'
classpath 'com.google.gms:google-services:4.4.0' classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
} }
} }
ext { ext {
kotlinVersion = "1.9.24" buildToolsVersion = "29.0.3"
buildToolsVersion = "34.0.0" compileSdkVersion = 29
compileSdkVersion = 34 minSdkVersion = 23
minSdkVersion = 26 targetSdkVersion = 29
targetSdkVersion = 34
supportLibVersion = "28.0.0" supportLibVersion = "28.0.0"
ndkVersion = "26.1.10909125"
// The Maven artifact groupId of the third-party react-native modules which // 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 // 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 // third-party Maven repositories so we have to deploy to a Maven repository
// of ours. // of ours.
@@ -40,16 +38,16 @@ ext {
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean() libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild
//React Native and Hermes Version
rnVersion = "0.75.5"
} }
allprojects { allprojects {
repositories { repositories {
mavenCentral()
google() google()
maven { url 'https://www.jitpack.io' } 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") }
} }
// Make sure we use the react-native version in node_modules and not the one // Make sure we use the react-native version in node_modules and not the one
@@ -57,41 +55,20 @@ allprojects {
configurations.all { configurations.all {
resolutionStrategy { resolutionStrategy {
eachDependency { DependencyResolveDetails details -> eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'com.facebook.react') { if (details.requested.group == 'com.facebook.react'
if (details.requested.name == 'react-native') { && details.requested.name == 'react-native') {
details.useTarget "com.facebook.react:react-android:$rnVersion" def file = new File("$rootDir/../node_modules/react-native/package.json")
} def version = new JsonSlurper().parseText(file.text).version
if (details.requested.name == 'react-android') { details.useVersion version
details.useVersion rootProject.ext.rnVersion
}
} }
} }
} }
} }
// Due to a dependency conflict between React Native and the Fresco library used by GiphySDK,
// GIFs appear as static images instead of animating
// https://github.com/Giphy/giphy-react-native-sdk/commit/7fe466ed6fddfaec95f9cbc959d33bd75ad8f900
configurations.configureEach {
resolutionStrategy {
forcedModules = [
'com.facebook.fresco:fresco:3.2.0',
'com.facebook.fresco:animated-gif:3.2.0',
'com.facebook.fresco:animated-base:3.2.0',
'com.facebook.fresco:animated-drawable:3.2.0',
'com.facebook.fresco:animated-webp:3.2.0',
'com.facebook.fresco:webpsupport:3.2.0',
'com.facebook.fresco:imagepipeline-okhttp3:3.2.0',
'com.facebook.fresco:middleware:3.2.0',
'com.facebook.fresco:nativeimagetranscoder:3.2.0'
]
}
}
// Third-party react-native modules which Jitsi Meet SDK for Android depends // Third-party react-native modules which Jitsi Meet SDK for Android depends
// on and which are not available in third-party Maven repositories need to // on and which are not available in third-party Maven repositories need to
// be deployed in a Maven repository of ours. // be deployed in a Maven repository of ours.
//
if (project.name.startsWith('react-native-')) { if (project.name.startsWith('react-native-')) {
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
@@ -133,7 +110,7 @@ allprojects {
project.version = "${json.version}-jitsi-${versionQualifierNumber}" project.version = "${json.version}-jitsi-${versionQualifierNumber}"
task jitsiAndroidSourcesJar(type: Jar) { task androidSourcesJar(type: Jar) {
classifier = 'sources' classifier = 'sources'
from android.sourceSets.main.java.source from android.sourceSets.main.java.source
} }
@@ -147,7 +124,7 @@ allprojects {
artifact("${project.buildDir}/outputs/aar/${project.name}-release.aar") { artifact("${project.buildDir}/outputs/aar/${project.name}-release.aar") {
extension "aar" extension "aar"
} }
artifact(jitsiAndroidSourcesJar) artifact(androidSourcesJar)
pom.withXml { pom.withXml {
def pomXml = asNode() def pomXml = asNode()
pomXml.appendNode('name', project.name) pomXml.appendNode('name', project.name)

View File

@@ -9,9 +9,9 @@
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # 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:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
@@ -24,7 +24,6 @@ android.enableDexingArtifactTransform.desugaring=false
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
appVersion=99.0.0 appVersion=20.5.0
sdkVersion=0.0.0 sdkVersion=2.11.0

View File

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

286
android/gradlew vendored
View File

@@ -1,129 +1,78 @@
#!/bin/sh #!/usr/bin/env 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.
#
############################################################################## ##############################################################################
# ##
# Gradle start up script for POSIX generated by Gradle. ## Gradle start up script for UN*X
# ##
# 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/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
app_path=$0 PRG="$0"
# Need this for relative symlinks.
# Need this for daisy-chained symlinks. while [ -h "$PRG" ] ; do
while ls=`ls -ld "$PRG"`
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path link=`expr "$ls" : '.*-> \(.*\)$'`
[ -h "$app_path" ] if expr "$link" : '/.*' > /dev/null; then
do PRG="$link"
ls=$( ls -ld "$app_path" ) else
link=${ls#*' -> '} PRG=`dirname "$PRG"`"/$link"
case $link in #( fi
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" 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. # 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. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD="maximum"
warn () { warn () {
echo "$*" echo "$*"
} >&2 }
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} >&2 }
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "$( uname )" in #( case "`uname`" in
CYGWIN* ) cygwin=true ;; #( CYGWIN* )
Darwin* ) darwin=true ;; #( cygwin=true
MSYS* | MINGW* ) msys=true ;; #( ;;
NONSTOP* ) nonstop=true ;; Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java JAVACMD="$JAVA_HOME/jre/sh/java"
else else
JAVACMD=$JAVA_HOME/bin/java JAVACMD="$JAVA_HOME/bin/java"
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 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." location of your Java installation."
fi fi
else 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. 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 Please set the JAVA_HOME variable in your environment to match the
@@ -140,95 +89,84 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
case $MAX_FD in #( MAX_FD_LIMIT=`ulimit -H -n`
max*) if [ $? -eq 0 ] ; then
MAX_FD=$( ulimit -H -n ) || if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
warn "Could not query maximum file descriptor limit" MAX_FD="$MAX_FD_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" )
fi fi
# Roll the args list around exactly as many times as the number of ulimit -n $MAX_FD
# args, so each arg winds up back in the position where it started, but if [ $? -ne 0 ] ; then
# possibly modified. warn "Could not set maximum file descriptor limit: $MAX_FD"
# fi
# NB: a `for` loop captures its iteration list before it begins, so else
# changing the positional parameters here affects neither the number of warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
# iterations, nor the values presented in `arg`. fi
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi fi
# Collect all arguments for the java command; # For Darwin, add options to specify how the application appears in the dock
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of if $darwin; then
# shell script including quotes and variable substitutions, so put them in GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
# double quotes to make sure that they get re-expanded; and fi
# * put everything else in single quotes, so that it's not re-expanded.
set -- \ # For Cygwin, switch paths to Windows format before running java
"-Dorg.gradle.appname=$APP_BASE_NAME" \ if $cygwin ; then
-classpath "$CLASSPATH" \ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
org.gradle.wrapper.GradleWrapperMain \ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
"$@" JAVACMD=`cygpath --unix "$JAVACMD"`
# Use "xargs" to parse quoted args. # We build the pattern for arguments to be converted via cygpath
# ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. SEP=""
# for dir in $ROOTDIRSRAW ; do
# In Bash we could simply go: ROOTDIRS="$ROOTDIRS$SEP$dir"
# SEP="|"
# readarray ARGS < <( xargs -n1 <<<"$var" ) && done
# set -- "${ARGS[@]}" "$@" OURCYGPATTERN="(^($ROOTDIRS))"
# # Add a user-defined pattern to the cygpath arguments
# but POSIX shell has neither arrays nor command substitution, so instead we if [ "$GRADLE_CYGPATTERN" != "" ] ; then
# post-process each arg (as a line of input to sed) to backslash-escape any OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
# character that might be a shell metacharacter, then use eval to reverse fi
# that process (while maintaining the separation between arguments), and wrap # Now convert the arguments - kludge to limit ourselves to /bin/sh
# the whole thing up as a single "set" statement. i=0
# for arg in "$@" ; do
# This will of course break if any of these variables contains a newline or CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
# an unmatched quote. CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
#
eval "set -- $( if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
xargs -n1 | else
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | eval `echo args$i`="\"$arg\""
tr '\n' ' ' 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" "$@" exec "$JAVACMD" "$@"

173
android/gradlew.bat vendored
View File

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

View File

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

View File

@@ -9,6 +9,9 @@ THE_MVN_REPO=${MVN_REPO:-${1:-$DEFAULT_MVN_REPO}}
MVN_HTTP=0 MVN_HTTP=0
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2) DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}} SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
RN_VERSION=$(jq -r '.version' ${THIS_DIR}/../../node_modules/react-native/package.json)
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1 | cut -c 2-)
DO_GIT_TAG=${GIT_TAG:-0}
if [[ $THE_MVN_REPO == http* ]]; then if [[ $THE_MVN_REPO == http* ]]; then
MVN_HTTP=1 MVN_HTTP=1
@@ -22,7 +25,60 @@ export MVN_REPO=$THE_MVN_REPO
echo "Releasing Jitsi Meet SDK ${SDK_VERSION}" echo "Releasing Jitsi Meet SDK ${SDK_VERSION}"
echo "Using ${MVN_REPO} as the Maven repo" echo "Using ${MVN_REPO} as the Maven repo"
if [[ $MVN_HTTP == 0 ]]; then if [[ $MVN_HTTP == 1 ]]; then
# Push React Native
echo "Pushing React Native ${RN_VERSION} to the Maven repo"
pushd ${THIS_DIR}/../../node_modules/react-native/android/com/facebook/react/react-native/${RN_VERSION}
mvn \
deploy:deploy-file \
-Durl=${MVN_REPO} \
-DrepositoryId=${MVN_REPO_ID} \
-Dfile=react-native-${RN_VERSION}.aar \
-Dpackaging=aar \
-DgeneratePom=false \
-DpomFile=react-native-${RN_VERSION}.pom || true
popd
# Push JSC
echo "Pushing JSC ${JSC_VERSION} to the Maven repo"
pushd ${THIS_DIR}/../../node_modules/jsc-android/dist/org/webkit/android-jsc/${JSC_VERSION}
mvn \
deploy:deploy-file \
-Durl=${MVN_REPO} \
-DrepositoryId=${MVN_REPO_ID} \
-Dfile=android-jsc-${JSC_VERSION}.aar \
-Dpackaging=aar \
-DgeneratePom=false \
-DpomFile=android-jsc-${JSC_VERSION}.pom || true
popd
else
# Push React Native, if necessary
if [[ ! -d ${MVN_REPO}/com/facebook/react/react-native/${RN_VERSION} ]]; then
echo "Pushing React Native ${RN_VERSION} to the Maven repo"
pushd ${THIS_DIR}/../../node_modules/react-native/android/com/facebook/react/react-native/${RN_VERSION}
mvn \
deploy:deploy-file \
-Durl=${MVN_REPO} \
-Dfile=react-native-${RN_VERSION}.aar \
-Dpackaging=aar \
-DgeneratePom=false \
-DpomFile=react-native-${RN_VERSION}.pom
popd
fi
# Push JSC, if necessary
if [[ ! -d ${MVN_REPO}/org/webkit/android-jsc/${JSC_VERSION} ]]; then
echo "Pushing JSC ${JSC_VERSION} to the Maven repo"
pushd ${THIS_DIR}/../../node_modules/jsc-android/dist/org/webkit/android-jsc/${JSC_VERSION}
mvn \
deploy:deploy-file \
-Durl=${MVN_REPO} \
-Dfile=android-jsc-${JSC_VERSION}.aar \
-Dpackaging=aar \
-DgeneratePom=false \
-DpomFile=android-jsc-${JSC_VERSION}.pom
popd
fi
# Check if an SDK with that same version has already been released # Check if an SDK with that same version has already been released
if [[ -d ${MVN_REPO}/org/jitsi/react/jitsi-meet-sdk/${SDK_VERSION} ]]; then if [[ -d ${MVN_REPO}/org/jitsi/react/jitsi-meet-sdk/${SDK_VERSION} ]]; then
echo "There is already a release with that version in the Maven repo!" echo "There is already a release with that version in the Maven repo!"
@@ -33,17 +89,20 @@ fi
# Now build and publish the Jitsi Meet SDK and its dependencies # Now build and publish the Jitsi Meet SDK and its dependencies
echo "Building and publishing the Jitsi Meet SDK" echo "Building and publishing the Jitsi Meet SDK"
pushd ${THIS_DIR}/../ pushd ${THIS_DIR}/../
./gradlew clean ./gradlew clean
./gradlew assembleRelease ./gradlew assembleRelease
./gradlew publish ./gradlew publish
popd popd
# The artifacts are now on the Maven repo, commit them if [[ $DO_GIT_TAG == 1 ]]; then
if [[ $MVN_HTTP == 0 ]]; then # The artifacts are now on the Maven repo, commit them
pushd ${MVN_REPO_PATH} pushd ${MVN_REPO_PATH}
git add -A . git add -A .
git commit -m "Jitsi Meet SDK + dependencies: ${SDK_VERSION}" git commit -m "Jitsi Meet SDK + dependencies: ${SDK_VERSION}"
popd popd
# Tag the release
git tag android-sdk-${SDK_VERSION}
fi fi
# Done! # Done!

View File

@@ -2,4 +2,4 @@
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd) THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
exec ${THIS_DIR}/../../node_modules/react-native/scripts/packager.sh --reset-cache exec ${THIS_DIR}/../../node_modules/react-native/scripts/launchPackager.command --reset-cache

View File

@@ -3,12 +3,10 @@ apply plugin: 'maven-publish'
android { android {
compileSdkVersion rootProject.ext.compileSdkVersion compileSdkVersion rootProject.ext.compileSdkVersion
ndkVersion rootProject.ext.ndkVersion
defaultConfig { defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
buildConfigField "String", "SDK_VERSION", "\"$sdkVersion\""
} }
buildTypes { buildTypes {
@@ -27,72 +25,53 @@ android {
sourceSets { sourceSets {
main { main {
java { java {
if (rootProject.ext.libreBuild) {
srcDir "src"
exclude "**/AmplitudeModule.java"
}
exclude "test/" exclude "test/"
} }
} }
} }
namespace 'org.jitsi.meet.sdk'
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.fragment:fragment:1.4.1' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' implementation 'androidx.fragment:fragment:1.2.5'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
api "com.facebook.react:react-android:$rootProject.ext.rnVersion" //noinspection GradleDynamicVersion
api "com.facebook.react:hermes-android:$rootProject.ext.rnVersion" api 'com.facebook.react:react-native:+'
//noinspection GradleDynamicVersion //noinspection GradleDynamicVersion
implementation 'org.webkit:android-jsc:+' implementation 'org.webkit:android-jsc:+'
implementation 'com.facebook.fresco:animated-gif:2.5.0' implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1' implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation 'com.squareup.duktape:duktape-android:1.3.0' implementation 'com.squareup.duktape:duktape-android:1.3.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'androidx.startup:startup-runtime:1.1.0'
// Only add these packages if we are NOT doing a LIBRE_BUILD
if (!rootProject.ext.libreBuild) { if (!rootProject.ext.libreBuild) {
implementation project(':react-native-amplitude') implementation 'com.amplitude:android-sdk:2.14.1'
implementation project(':react-native-giphy') implementation(project(":react-native-google-signin")) {
implementation(project(':react-native-google-signin')) {
exclude group: 'com.google.android.gms' exclude group: 'com.google.android.gms'
exclude group: 'androidx' exclude group: 'androidx'
} }
} }
implementation project(':react-native-async-storage')
implementation project(':react-native-background-timer') implementation project(':react-native-background-timer')
implementation project(':react-native-calendar-events') 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-community_netinfo')
implementation project(':react-native-default-preference') implementation project(':react-native-default-preference')
implementation(project(':react-native-device-info')) { implementation project(':react-native-immersive')
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-mode')
implementation project(':react-native-keep-awake') implementation project(':react-native-keep-awake')
implementation project(':react-native-orientation-locker') implementation project(':react-native-linear-gradient')
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-sound') implementation project(':react-native-sound')
implementation project(':react-native-splash-screen')
implementation project(':react-native-svg') implementation project(':react-native-svg')
implementation project(':react-native-video') implementation project(':react-native-webrtc')
implementation project(':react-native-webview') implementation project(':react-native-webview')
implementation project(':react-native-splash-screen')
// Use `api` here so consumers can use WebRTCModuleOptions.
api project(':react-native-webrtc')
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
} }
@@ -142,7 +121,8 @@ android.libraryVariants.all { def variant ->
// Run the bundler // Run the bundler
commandLine( commandLine(
"node", "node",
"node_modules/react-native/scripts/bundle.js", "node_modules/react-native/local-cli/cli.js",
"bundle",
"--platform", "android", "--platform", "android",
"--dev", "${devEnabled}", "--dev", "${devEnabled}",
"--reset-cache", "--reset-cache",
@@ -170,9 +150,16 @@ android.libraryVariants.all { def variant ->
// Bundle sounds // Bundle sounds
// //
copy { copy {
from("${projectDir}/../../sounds") from("${projectDir}/../../sounds/incomingMessage.wav")
include("*.wav") from("${projectDir}/../../sounds/joined.wav")
include("*.mp3") 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") into("${assetsDir}/sounds")
} }
@@ -223,7 +210,7 @@ publishing {
def groupId = it.moduleGroup def groupId = it.moduleGroup
def artifactId = it.moduleName def artifactId = it.moduleName
if (artifactId.startsWith('react-native-')) { if (artifactId.startsWith('react-native-') && groupId.equals('jitsi-meet')) {
groupId = rootProject.ext.moduleGroupId groupId = rootProject.ext.moduleGroupId
} }

View File

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

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <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. --> <!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
@@ -9,13 +9,10 @@
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" /> <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <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.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature <uses-feature
android:glEsVersion="0x00020000" android:glEsVersion="0x00020000"
@@ -33,17 +30,16 @@
android:supportsRtl="true"> android:supportsRtl="true">
<activity <activity
android:name=".JitsiMeetActivity" android:name=".JitsiMeetActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/JitsiMeetActivityStyle"
android:resizeableActivity="true" android:resizeableActivity="true"
android:supportsPictureInPicture="true" android:supportsPictureInPicture="true"
android:windowSoftInputMode="adjustResize"/> android:windowSoftInputMode="adjustResize"></activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service <service
android:name=".ConnectionService" android:name=".ConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.telecom.ConnectionService" /> <action android:name="android.telecom.ConnectionService" />
</intent-filter> </intent-filter>
@@ -51,22 +47,7 @@
<service <service
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService" android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
android:foregroundServiceType="mediaPlayback|microphone" /> 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> </application>
</manifest> </manifest>

View File

@@ -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;
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright @ 2017-present 8x8, Inc. * Copyright @ 2017-present Atlassian Pty Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.module.annotations.ReactModule;
import java.lang.reflect.Field;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -33,11 +32,7 @@ import java.util.Map;
class AppInfoModule class AppInfoModule
extends ReactContextBaseJavaModule { extends ReactContextBaseJavaModule {
private static final String BUILD_CONFIG = "org.jitsi.meet.sdk.BuildConfig";
public static final String NAME = "AppInfo"; public static final String NAME = "AppInfo";
public static final boolean GOOGLE_SERVICES_ENABLED = getGoogleServicesEnabled();
public static final boolean LIBRE_BUILD = getLibreBuild();
public static final String SDK_VERSION = getSdkVersion();
public AppInfoModule(ReactApplicationContext reactContext) { public AppInfoModule(ReactApplicationContext reactContext) {
super(reactContext); super(reactContext);
@@ -80,9 +75,8 @@ class AppInfoModule
constants.put( constants.put(
"version", "version",
packageInfo == null ? "" : packageInfo.versionName); packageInfo == null ? "" : packageInfo.versionName);
constants.put("sdkVersion", SDK_VERSION); constants.put("LIBRE_BUILD", BuildConfig.LIBRE_BUILD);
constants.put("LIBRE_BUILD", LIBRE_BUILD); constants.put("GOOGLE_SERVICES_ENABLED", BuildConfig.GOOGLE_SERVICES_ENABLED);
constants.put("GOOGLE_SERVICES_ENABLED", GOOGLE_SERVICES_ENABLED);
return constants; return constants;
} }
@@ -91,60 +85,4 @@ class AppInfoModule
public String getName() { public String getName() {
return NAME; return NAME;
} }
/**
* Checks if libre google services object is null based on build configuration.
*/
private static boolean getGoogleServicesEnabled() {
Object googleServicesEnabled = getBuildConfigValue("GOOGLE_SERVICES_ENABLED");
if (googleServicesEnabled !=null) {
return (Boolean) googleServicesEnabled;
}
return false;
}
/**
* Checks if libre build field is null based on build configuration.
*/
private static boolean getLibreBuild() {
Object libreBuild = getBuildConfigValue("LIBRE_BUILD");
if (libreBuild !=null) {
return (Boolean) libreBuild;
}
return false;
}
/**
* Gets the SDK version.
*/
private static String getSdkVersion() {
Object sdkVersion = getBuildConfigValue("SDK_VERSION");
if (sdkVersion !=null) {
return (String) sdkVersion;
}
return "";
}
/**
* Gets build config value of a certain field.
*
* @param fieldName Field from build config.
*/
private static Object getBuildConfigValue(String fieldName) {
try {
Class<?> c = Class.forName(BUILD_CONFIG);
Field f = c.getDeclaredField(fieldName);
f.setAccessible(true);
return f.get(null);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
} }

View File

@@ -18,11 +18,10 @@ package org.jitsi.meet.sdk;
import android.content.Context; import android.content.Context;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Build;
import android.telecom.CallAudioState; import android.telecom.CallAudioState;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.facebook.react.bridge.ReactContext;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -33,6 +32,7 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for * {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
* Android versions >= O when ConnectionService is enabled. * Android versions >= O when ConnectionService is enabled.
*/ */
@RequiresApi(Build.VERSION_CODES.O)
class AudioDeviceHandlerConnectionService implements class AudioDeviceHandlerConnectionService implements
AudioModeModule.AudioDeviceHandlerInterface, AudioModeModule.AudioDeviceHandlerInterface,
RNConnectionService.CallAudioStateListener { RNConnectionService.CallAudioStateListener {
@@ -49,8 +49,6 @@ class AudioDeviceHandlerConnectionService implements
*/ */
private AudioModeModule module; private AudioModeModule module;
private RNConnectionService rcs;
/** /**
* Converts any of the "DEVICE_" constants into the corresponding * Converts any of the "DEVICE_" constants into the corresponding
* {@link android.telecom.CallAudioState} "ROUTE_" number. * {@link android.telecom.CallAudioState} "ROUTE_" number.
@@ -143,8 +141,8 @@ class AudioDeviceHandlerConnectionService implements
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler"); JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
module = audioModeModule; module = audioModeModule;
rcs = module.getContext().getNativeModule(RNConnectionService.class);
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
if (rcs != null) { if (rcs != null) {
rcs.setCallAudioStateListener(this); rcs.setCallAudioStateListener(this);
} else { } else {
@@ -154,9 +152,9 @@ class AudioDeviceHandlerConnectionService implements
@Override @Override
public void stop() { public void stop() {
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
if (rcs != null) { if (rcs != null) {
rcs.setCallAudioStateListener(null); rcs.setCallAudioStateListener(null);
rcs = null;
} else { } else {
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null"); JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
} }

View File

@@ -16,9 +16,7 @@
package org.jitsi.meet.sdk; package org.jitsi.meet.sdk;
import android.media.AudioAttributes;
import android.media.AudioDeviceInfo; import android.media.AudioDeviceInfo;
import android.media.AudioFocusRequest;
import android.media.AudioManager; import android.media.AudioManager;
import java.util.HashSet; import java.util.HashSet;
@@ -44,12 +42,6 @@ class AudioDeviceHandlerGeneric implements
*/ */
private AudioModeModule module; private AudioModeModule module;
/**
* Constant defining a Hearing Aid. Only available on API level >= 28.
* The value of: AudioDeviceInfo.TYPE_HEARING_AID
*/
private static final int TYPE_HEARING_AID = 23;
/** /**
* Constant defining a USB headset. Only available on API level >= 26. * Constant defining a USB headset. Only available on API level >= 26.
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET * The value of: AudioDeviceInfo.TYPE_USB_HEADSET
@@ -68,7 +60,7 @@ class AudioDeviceHandlerGeneric implements
private AudioManager audioManager; 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. * This is only used on Android >= M.
*/ */
private final Runnable onAudioDeviceChangeRunner = new Runnable() { private final Runnable onAudioDeviceChangeRunner = new Runnable() {
@@ -86,12 +78,10 @@ class AudioDeviceHandlerGeneric implements
devices.add(AudioModeModule.DEVICE_EARPIECE); devices.add(AudioModeModule.DEVICE_EARPIECE);
break; break;
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
case AudioDeviceInfo.TYPE_HDMI:
devices.add(AudioModeModule.DEVICE_SPEAKER); devices.add(AudioModeModule.DEVICE_SPEAKER);
break; break;
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
case AudioDeviceInfo.TYPE_WIRED_HEADSET: case AudioDeviceInfo.TYPE_WIRED_HEADSET:
case TYPE_HEARING_AID:
case TYPE_USB_HEADSET: case TYPE_USB_HEADSET:
devices.add(AudioModeModule.DEVICE_HEADPHONES); devices.add(AudioModeModule.DEVICE_HEADPHONES);
break; break;
@@ -152,7 +142,7 @@ class AudioDeviceHandlerGeneric implements
// Some other application potentially stole our audio focus // Some other application potentially stole our audio focus
// temporarily. Restore our mode. // temporarily. Restore our mode.
if (audioFocusLost) { if (audioFocusLost) {
module.resetAudioRoute(); module.updateAudioRoute();
} }
audioFocusLost = false; audioFocusLost = false;
break; break;
@@ -226,19 +216,8 @@ class AudioDeviceHandlerGeneric implements
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false); audioManager.setMicrophoneMute(false);
int gotFocus = audioManager.requestAudioFocus(new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes( == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(this)
.build()
);
if (gotFocus == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
JitsiMeetLogger.w(TAG + " Audio focus request failed"); JitsiMeetLogger.w(TAG + " Audio focus request failed");
return false; return false;
} }

View File

@@ -16,22 +16,18 @@
package org.jitsi.meet.sdk; package org.jitsi.meet.sdk;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Build;
import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger; import org.jitsi.meet.sdk.log.JitsiMeetLogger;
@@ -82,10 +78,12 @@ class AudioModeModule extends ReactContextBaseJavaModule {
/** /**
* Whether or not the ConnectionService is used for selecting audio devices. * Whether or not the ConnectionService is used for selecting audio devices.
*/ */
private static boolean useConnectionService_ = true;
private static final boolean supportsConnectionService = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
private static boolean useConnectionService_ = supportsConnectionService;
static boolean useConnectionService() { static boolean useConnectionService() {
return useConnectionService_; return supportsConnectionService && useConnectionService_;
} }
/** /**
@@ -136,11 +134,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/ */
private String userSelectedDevice; private String userSelectedDevice;
/**
* Whether or not audio is disabled.
*/
private boolean audioDisabled;
/** /**
* Initializes a new module instance. There shall be a single instance of * Initializes a new module instance. There shall be a single instance of
* this module throughout the lifetime of the application. * this module throughout the lifetime of the application.
@@ -154,16 +147,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
audioManager = (AudioManager)reactContext.getSystemService(Context.AUDIO_SERVICE); 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. * Gets a mapping with the constants this module is exporting.
* *
@@ -201,7 +184,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
deviceInfo.putBoolean("selected", device.equals(selectedDevice)); deviceInfo.putBoolean("selected", device.equals(selectedDevice));
data.pushMap(deviceInfo); 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"); JitsiMeetLogger.i(TAG + " Updating audio device list");
} }
}); });
@@ -217,10 +200,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
return NAME; return NAME;
} }
public ReactContext getContext(){
return this.getReactApplicationContext();
}
/** /**
* Initializes the audio device handler module. This function is called *after* all Catalyst * 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} * modules have been created, and that's why we use it, because {@link AudioDeviceHandlerConnectionService}
@@ -241,12 +220,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
audioDeviceHandler.stop(); audioDeviceHandler.stop();
} }
audioDeviceHandler = null;
if (audioDisabled) {
return;
}
if (useConnectionService()) { if (useConnectionService()) {
audioDeviceHandler = new AudioDeviceHandlerConnectionService(audioManager); audioDeviceHandler = new AudioDeviceHandlerConnectionService(audioManager);
} else { } else {
@@ -283,33 +256,12 @@ class AudioModeModule extends ReactContextBaseJavaModule {
if (mode != -1) { if (mode != -1) {
JitsiMeetLogger.i(TAG + " User selected device set to: " + device); JitsiMeetLogger.i(TAG + " User selected device set to: " + device);
userSelectedDevice = device; userSelectedDevice = device;
updateAudioRoute(mode, false); updateAudioRoute(mode);
} }
} }
}); });
} }
@ReactMethod
public void setDisabled(final boolean disabled, final Promise promise) {
if (audioDisabled == disabled) {
promise.resolve(null);
return;
}
JitsiMeetLogger.i(TAG + " audio disabled: " + disabled);
audioDisabled = disabled;
setAudioDeviceHandler();
if (disabled) {
mode = -1;
availableDevices.clear();
resetSelectedDevice();
}
promise.resolve(null);
}
/** /**
* Public method to set the current audio mode. * Public method to set the current audio mode.
* *
@@ -319,32 +271,18 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/ */
@ReactMethod @ReactMethod
public void setMode(final int mode, final Promise promise) { public void setMode(final int mode, final Promise promise) {
if (audioDisabled) { if (mode != DEFAULT && mode != AUDIO_CALL && mode != VIDEO_CALL) {
promise.resolve(null);
return;
}
if (mode < DEFAULT || mode > VIDEO_CALL) {
promise.reject("setMode", "Invalid audio mode " + mode); promise.reject("setMode", "Invalid audio mode " + mode);
return; 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() { runInAudioThread(new Runnable() {
@Override @Override
public void run() { public void run() {
boolean success; boolean success;
try { try {
success = updateAudioRoute(mode, false); success = updateAudioRoute(mode);
} catch (Throwable e) { } catch (Throwable e) {
success = false; success = false;
JitsiMeetLogger.e(e, TAG + " Failed to update audio route for mode: " + mode); JitsiMeetLogger.e(e, TAG + " Failed to update audio route for mode: " + mode);
@@ -383,7 +321,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
* @return {@code true} if the audio route was updated successfully; * @return {@code true} if the audio route was updated successfully;
* {@code false}, otherwise. * {@code false}, otherwise.
*/ */
private boolean updateAudioRoute(int mode, boolean force) { private boolean updateAudioRoute(int mode) {
JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode); JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode);
if (!audioDeviceHandler.setMode(mode)) { if (!audioDeviceHandler.setMode(mode)) {
@@ -418,7 +356,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
// If the previously selected device and the current default one // If the previously selected device and the current default one
// match, do nothing. // match, do nothing.
if (!force && selectedDevice != null && selectedDevice.equals(audioDevice)) { if (selectedDevice != null && selectedDevice.equals(audioDevice)) {
return true; return true;
} }
@@ -483,16 +421,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/ */
void updateAudioRoute() { void updateAudioRoute() {
if (mode != -1) { if (mode != -1) {
updateAudioRoute(mode, false); updateAudioRoute(mode);
}
}
/**
* Re-sets the current audio route. Needed when focus is lost and regained.
*/
void resetAudioRoute() {
if (mode != -1) {
updateAudioRoute(mode, true);
} }
} }

View 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;
}
}

View File

@@ -1,66 +0,0 @@
package org.jitsi.meet.sdk;
import android.content.Intent;
import android.os.Bundle;
/**
* 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 Bundle data;
public BroadcastAction(Intent intent) {
this.type = Type.buildTypeFromAction(intent.getAction());
this.data = intent.getExtras();
}
public Type getType() {
return this.type;
}
public Bundle getData() {
return this.data;
}
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"),
TOGGLE_CAMERA("org.jitsi.meet.TOGGLE_CAMERA"),
SHOW_NOTIFICATION("org.jitsi.meet.SHOW_NOTIFICATION"),
HIDE_NOTIFICATION("org.jitsi.meet.HIDE_NOTIFICATION"),
START_RECORDING("org.jitsi.meet.START_RECORDING"),
STOP_RECORDING("org.jitsi.meet.STOP_RECORDING"),
OVERWRITE_CONFIG("org.jitsi.meet.OVERWRITE_CONFIG"),
SEND_CAMERA_FACING_MODE_MESSAGE("org.jitsi.meet.SEND_CAMERA_FACING_MODE_MESSAGE");
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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -1,182 +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_BLURRED("org.jitsi.meet.CONFERENCE_BLURRED"),
CONFERENCE_FOCUSED("org.jitsi.meet.CONFERENCE_FOCUSED"),
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"),
TRANSCRIPTION_CHUNK_RECEIVED("org.jitsi.meet.TRANSCRIPTION_CHUNK_RECEIVED"),
CUSTOM_BUTTON_PRESSED("org.jitsi.meet.CUSTOM_BUTTON_PRESSED"),
CONFERENCE_UNIQUE_ID_SET("org.jitsi.meet.CONFERENCE_UNIQUE_ID_SET"),
RECORDING_STATUS_CHANGED("org.jitsi.meet.RECORDING_STATUS_CHANGED");
private static final String CONFERENCE_BLURRED_NAME = "CONFERENCE_BLURRED";
private static final String CONFERENCE_FOCUSED_NAME = "CONFERENCE_FOCUSED";
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 static final String TRANSCRIPTION_CHUNK_RECEIVED_NAME = "TRANSCRIPTION_CHUNK_RECEIVED";
private static final String CUSTOM_BUTTON_PRESSED_NAME = "CUSTOM_BUTTON_PRESSED";
private static final String CONFERENCE_UNIQUE_ID_SET_NAME = "CONFERENCE_UNIQUE_ID_SET";
private static final String RECORDING_STATUS_CHANGED_NAME = "RECORDING_STATUS_CHANGED";
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_BLURRED_NAME:
return CONFERENCE_BLURRED;
case CONFERENCE_FOCUSED_NAME:
return CONFERENCE_FOCUSED;
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;
case TRANSCRIPTION_CHUNK_RECEIVED_NAME:
return TRANSCRIPTION_CHUNK_RECEIVED;
case CUSTOM_BUTTON_PRESSED_NAME:
return CUSTOM_BUTTON_PRESSED;
case CONFERENCE_UNIQUE_ID_SET_NAME:
return CONFERENCE_UNIQUE_ID_SET;
case RECORDING_STATUS_CHANGED_NAME:
return RECORDING_STATUS_CHANGED;
}
return null;
}
}
}

View File

@@ -1,157 +0,0 @@
package org.jitsi.meet.sdk;
import android.content.Intent;
import android.os.Bundle;
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;
}
public static Intent buildRetrieveParticipantsInfo(String requestId) {
Intent intent = new Intent(BroadcastAction.Type.RETRIEVE_PARTICIPANTS_INFO.getAction());
intent.putExtra("requestId", requestId);
return intent;
}
public static Intent buildToggleCameraIntent() {
return new Intent(BroadcastAction.Type.TOGGLE_CAMERA.getAction());
}
public static Intent buildShowNotificationIntent(
String appearance, String description, String timeout, String title, String uid) {
Intent intent = new Intent(BroadcastAction.Type.SHOW_NOTIFICATION.getAction());
intent.putExtra("appearance", appearance);
intent.putExtra("description", description);
intent.putExtra("timeout", timeout);
intent.putExtra("title", title);
intent.putExtra("uid", uid);
return intent;
}
public static Intent buildHideNotificationIntent(String uid) {
Intent intent = new Intent(BroadcastAction.Type.HIDE_NOTIFICATION.getAction());
intent.putExtra("uid", uid);
return intent;
}
public enum RecordingMode {
FILE("file"),
STREAM("stream");
private final String mode;
RecordingMode(String mode) {
this.mode = mode;
}
public String getMode() {
return mode;
}
}
public static Intent buildStartRecordingIntent(
RecordingMode mode,
String dropboxToken,
boolean shouldShare,
String rtmpStreamKey,
String rtmpBroadcastID,
String youtubeStreamKey,
String youtubeBroadcastID,
Bundle extraMetadata,
boolean transcription) {
Intent intent = new Intent(BroadcastAction.Type.START_RECORDING.getAction());
intent.putExtra("mode", mode.getMode());
intent.putExtra("dropboxToken", dropboxToken);
intent.putExtra("shouldShare", shouldShare);
intent.putExtra("rtmpStreamKey", rtmpStreamKey);
intent.putExtra("rtmpBroadcastID", rtmpBroadcastID);
intent.putExtra("youtubeStreamKey", youtubeStreamKey);
intent.putExtra("youtubeBroadcastID", youtubeBroadcastID);
intent.putExtra("extraMetadata", extraMetadata);
intent.putExtra("transcription", transcription);
return intent;
}
public static Intent buildStopRecordingIntent(RecordingMode mode, boolean transcription) {
Intent intent = new Intent(BroadcastAction.Type.STOP_RECORDING.getAction());
intent.putExtra("mode", mode.getMode());
intent.putExtra("transcription", transcription);
return intent;
}
public static Intent buildOverwriteConfigIntent(Bundle config) {
Intent intent = new Intent(BroadcastAction.Type.OVERWRITE_CONFIG.getAction());
intent.putExtra("config", config);
return intent;
}
public static Intent buildSendCameraFacingModeMessageIntent(String to, String facingMode) {
Intent intent = new Intent(BroadcastAction.Type.SEND_CAMERA_FACING_MODE_MESSAGE.getAction());
intent.putExtra("to", to);
intent.putExtra("facingMode", facingMode);
return intent;
}
}

View File

@@ -1,44 +0,0 @@
package org.jitsi.meet.sdk;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import com.facebook.react.bridge.Arguments;
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();
Bundle data = action.getData();
// For actions without data bundle (like hangup), we create an empty map
// instead of attempting to convert a null bundle to avoid crashes.
if (data != null) {
ReactInstanceManagerHolder.emitEvent(actionName, Arguments.fromBundle(data));
} else {
ReactInstanceManagerHolder.emitEvent(actionName, Arguments.createMap());
}
}
}

View File

@@ -13,7 +13,6 @@ import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle; import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager; import android.telecom.TelecomManager;
import android.telecom.VideoProfile; import android.telecom.VideoProfile;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.Promise;
@@ -37,6 +36,7 @@ import java.util.Objects;
* *
* @author Pawel Domas * @author Pawel Domas
*/ */
@RequiresApi(api = Build.VERSION_CODES.O)
public class ConnectionService extends android.telecom.ConnectionService { public class ConnectionService extends android.telecom.ConnectionService {
/** /**
@@ -357,7 +357,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
JitsiMeetLogger.i(TAG + " onDisconnect " + getCallUUID()); JitsiMeetLogger.i(TAG + " onDisconnect " + getCallUUID());
WritableNativeMap data = new WritableNativeMap(); WritableNativeMap data = new WritableNativeMap();
data.putString("callUUID", getCallUUID()); data.putString("callUUID", getCallUUID());
RNConnectionService.getInstance().emitEvent( ReactInstanceManagerHolder.emitEvent(
"org.jitsi.meet:features/connection_service#disconnect", "org.jitsi.meet:features/connection_service#disconnect",
data); data);
// The JavaScript side will not go back to the native with // The JavaScript side will not go back to the native with
@@ -377,7 +377,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
JitsiMeetLogger.i(TAG + " onAbort " + getCallUUID()); JitsiMeetLogger.i(TAG + " onAbort " + getCallUUID());
WritableNativeMap data = new WritableNativeMap(); WritableNativeMap data = new WritableNativeMap();
data.putString("callUUID", getCallUUID()); data.putString("callUUID", getCallUUID());
RNConnectionService.getInstance().emitEvent( ReactInstanceManagerHolder.emitEvent(
"org.jitsi.meet:features/connection_service#abort", "org.jitsi.meet:features/connection_service#abort",
data); data);
// The JavaScript side will not go back to the native with // The JavaScript side will not go back to the native with
@@ -406,7 +406,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
@Override @Override
public void onCallAudioStateChanged(CallAudioState state) { public void onCallAudioStateChanged(CallAudioState state) {
JitsiMeetLogger.d(TAG + " onCallAudioStateChanged: " + state); JitsiMeetLogger.d(TAG + " onCallAudioStateChanged: " + state);
RNConnectionService module = RNConnectionService.getInstance(); RNConnectionService module = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
if (module != null) { if (module != null) {
module.onCallAudioStateChange(state); module.onCallAudioStateChange(state);
} }

View File

@@ -8,8 +8,6 @@ import android.text.TextUtils;
import com.dropbox.core.DbxException; import com.dropbox.core.DbxException;
import com.dropbox.core.DbxRequestConfig; 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.DbxClientV2;
import com.dropbox.core.v2.users.FullAccount; import com.dropbox.core.v2.users.FullAccount;
import com.dropbox.core.v2.users.SpaceAllocation; 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.Promise;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.dropbox.core.android.Auth;
import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.module.annotations.ReactModule;
@@ -67,7 +66,7 @@ class DropboxModule
@ReactMethod @ReactMethod
public void authorize(final Promise promise) { public void authorize(final Promise promise) {
if (isEnabled) { if (isEnabled) {
Auth.startOAuth2PKCE(this.getCurrentActivity(), appKey, DbxRequestConfig.newBuilder(clientId).build()); Auth.startOAuth2Authentication(this.getCurrentActivity(), appKey);
this.promise = promise; this.promise = promise;
} else { } else {
promise.reject( promise.reject(
@@ -182,23 +181,11 @@ class DropboxModule
@Override @Override
public void onHostResume() { public void onHostResume() {
DbxCredential credential = Auth.getDbxCredential(); String token = Auth.getOAuth2Token();
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");
}
if (token != null && this.promise != null) {
this.promise.resolve(token);
this.promise = null; this.promise = null;
} }
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright @ 2017-present 8x8, Inc. * Copyright @ 2017-present Atlassian Pty Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 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. * Module implementing an API for sending events from JavaScript to native code.
*/ */
@ReactModule(name = ExternalAPIModule.NAME) @ReactModule(name = ExternalAPIModule.NAME)
class ExternalAPIModule extends ReactContextBaseJavaModule { class ExternalAPIModule
extends ReactContextBaseJavaModule {
public static final String NAME = "ExternalAPI"; public static final String NAME = "ExternalAPI";
private static final String TAG = NAME; 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 * Initializes a new module instance. There shall be a single instance of
* this module throughout the lifetime of the app. * this module throughout the lifetime of the app.
@@ -49,21 +44,6 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
*/ */
public ExternalAPIModule(ReactApplicationContext reactContext) { public ExternalAPIModule(ReactApplicationContext reactContext) {
super(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,51 +56,32 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
return NAME; 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());
constants.put("TOGGLE_CAMERA", BroadcastAction.Type.TOGGLE_CAMERA.getAction());
constants.put("SHOW_NOTIFICATION", BroadcastAction.Type.SHOW_NOTIFICATION.getAction());
constants.put("HIDE_NOTIFICATION", BroadcastAction.Type.HIDE_NOTIFICATION.getAction());
constants.put("START_RECORDING", BroadcastAction.Type.START_RECORDING.getAction());
constants.put("STOP_RECORDING", BroadcastAction.Type.STOP_RECORDING.getAction());
constants.put("OVERWRITE_CONFIG", BroadcastAction.Type.OVERWRITE_CONFIG.getAction());
constants.put("SEND_CAMERA_FACING_MODE_MESSAGE", BroadcastAction.Type.SEND_CAMERA_FACING_MODE_MESSAGE.getAction());
return constants;
}
/** /**
* Dispatches an event that occurred on the JavaScript side of the SDK to * 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 name The name of the event.
* @param data The details/specifics of the event to send determined * @param data The details/specifics of the event to send determined
* by/associated with the specified {@code name}. * by/associated with the specified {@code name}.
* @param scope
*/ */
@ReactMethod @ReactMethod
public void sendEvent(String name, ReadableMap data) { public void sendEvent(String name, ReadableMap data, String scope) {
// Keep track of the current ongoing conference. // Keep track of the current ongoing conference.
OngoingConferenceTracker.getInstance().onExternalAPIEvent(name, data); OngoingConferenceTracker.getInstance().onExternalAPIEvent(name, data);
JitsiMeetLogger.d(TAG + " Sending event: " + name + " with data: " + data); // The JavaScript App needs to provide uniquely identifying information
broadcastEmitter.sendBroadcast(name, data); // 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");
}
}
} }
} }

View File

@@ -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();
}
}

View File

@@ -16,58 +16,33 @@
package org.jitsi.meet.sdk; package org.jitsi.meet.sdk;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.FragmentActivity;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.facebook.react.modules.core.PermissionListener; import com.facebook.react.modules.core.PermissionListener;
import org.jitsi.meet.sdk.log.JitsiMeetLogger; 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 * A base activity for SDK users to embed. It uses {@link JitsiMeetFragment} to do the heavy
* between the {@code JitsiMeetView} and the Activity lifecycle methods. * lifting and wires the remaining Activity lifecycle methods so it works out of the box.
*
* 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.
*/ */
public class JitsiMeetActivity extends AppCompatActivity public class JitsiMeetActivity extends FragmentActivity
implements JitsiMeetActivityInterface { implements JitsiMeetActivityInterface, JitsiMeetViewListener {
protected static final String TAG = JitsiMeetActivity.class.getSimpleName(); protected static final String TAG = JitsiMeetActivity.class.getSimpleName();
private static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE"; private static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE";
private static final String JITSI_MEET_CONFERENCE_OPTIONS = "JitsiMeetConferenceOptions"; private static final String JITSI_MEET_CONFERENCE_OPTIONS = "JitsiMeetConferenceOptions";
private boolean isReadyToClose;
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 // Helpers for starting the activity
// //
@@ -75,9 +50,6 @@ public class JitsiMeetActivity extends AppCompatActivity
Intent intent = new Intent(context, JitsiMeetActivity.class); Intent intent = new Intent(context, JitsiMeetActivity.class);
intent.setAction(ACTION_JITSI_MEET_CONFERENCE); intent.setAction(ACTION_JITSI_MEET_CONFERENCE);
intent.putExtra(JITSI_MEET_CONFERENCE_OPTIONS, options); intent.putExtra(JITSI_MEET_CONFERENCE_OPTIONS, options);
if (!(context instanceof Activity)) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent); context.startActivity(intent);
} }
@@ -90,44 +62,22 @@ public class JitsiMeetActivity extends AppCompatActivity
// Overrides // Overrides
// //
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
this.sendBroadcast(intent);
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jitsi_meet); setContentView(R.layout.activity_jitsi_meet);
this.jitsiView = findViewById(R.id.jitsiView);
registerForBroadcastMessages(); // Listen for conference events.
getJitsiView().setListener(this);
if (!extraInitialize()) { if (!extraInitialize()) {
initialize(); initialize();
} }
} }
@Override
public void onResume() {
super.onResume();
JitsiMeetActivityDelegate.onHostResume(this);
}
@Override
public void onStop() {
JitsiMeetActivityDelegate.onHostPause(this);
super.onStop();
}
@Override @Override
public void onDestroy() { public void onDestroy() {
JitsiMeetLogger.i("onDestroy()");
// Here we are trying to handle the following corner case: an application using the SDK // Here we are trying to handle the following corner case: an application using the SDK
// is using this Activity for displaying meetings, but there is another "main" Activity // is using this Activity for displaying meetings, but there is another "main" Activity
// with other content. If this Activity is "swiped out" from the recent list we will get // with other content. If this Activity is "swiped out" from the recent list we will get
@@ -135,33 +85,19 @@ public class JitsiMeetActivity extends AppCompatActivity
// current meeting, but when our view is detached from React the JS <-> Native bridge won't // current meeting, but when our view is detached from React the JS <-> Native bridge won't
// be operational so the external API won't be able to notify the native side that the // 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. // conference terminated. Thus, try our best to clean up.
if (!isReadyToClose) { leave();
JitsiMeetLogger.i("onDestroy(): leaving...");
leave();
}
this.jitsiView = null;
if (AudioModeModule.useConnectionService()) { if (AudioModeModule.useConnectionService()) {
ConnectionService.abortConnections(); ConnectionService.abortConnections();
} }
JitsiMeetOngoingConferenceService.abort(this); JitsiMeetOngoingConferenceService.abort(this);
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
JitsiMeetActivityDelegate.onHostDestroy(this);
super.onDestroy(); super.onDestroy();
} }
@Override @Override
public void finish() { public void finish() {
if (!isReadyToClose) { leave();
JitsiMeetLogger.i("finish(): leaving...");
leave();
}
JitsiMeetLogger.i("finish(): finishing...");
super.finish(); super.finish();
} }
@@ -169,35 +105,28 @@ public class JitsiMeetActivity extends AppCompatActivity
// //
protected JitsiMeetView getJitsiView() { protected JitsiMeetView getJitsiView() {
return jitsiView; JitsiMeetFragment fragment
= (JitsiMeetFragment) getSupportFragmentManager().findFragmentById(R.id.jitsiFragment);
return fragment.getJitsiView();
} }
public void join(@Nullable String url) { public void join(@Nullable String url) {
JitsiMeetConferenceOptions options JitsiMeetConferenceOptions options
= new JitsiMeetConferenceOptions.Builder() = new JitsiMeetConferenceOptions.Builder()
.setRoom(url) .setRoom(url)
.build(); .build();
join(options); join(options);
} }
public void join(JitsiMeetConferenceOptions options) { public void join(JitsiMeetConferenceOptions options) {
if (this.jitsiView != null) { getJitsiView().join(options);
this.jitsiView.join(options);
} else {
JitsiMeetLogger.w("Cannot join, view is null");
}
} }
protected void leave() { public void leave() {
if (this.jitsiView != null) { getJitsiView().leave();
this.jitsiView.abort();
} else {
JitsiMeetLogger.w("Cannot leave, view is null");
}
} }
private @Nullable private @Nullable JitsiMeetConferenceOptions getConferenceOptions(Intent intent) {
JitsiMeetConferenceOptions getConferenceOptions(Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action)) { if (Intent.ACTION_VIEW.equals(action)) {
@@ -216,7 +145,7 @@ public class JitsiMeetActivity extends AppCompatActivity
* Helper function called during activity initialization. If {@code true} is returned, the * Helper function called during activity initialization. If {@code true} is returned, the
* initialization is delayed and the {@link JitsiMeetActivity#initialize()} method is not * 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. * 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. * 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. * @return {@code true} if the initialization will be delayed, {@code false} otherwise.
@@ -231,58 +160,6 @@ public class JitsiMeetActivity extends AppCompatActivity
join(getConferenceOptions(getIntent())); 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");
isReadyToClose = true;
finish();
}
// protected void onTranscriptionChunkReceived(HashMap<String, Object> extraData) {
// JitsiMeetLogger.i("Transcription chunk received: " + extraData);
// }
// protected void onCustomButtonPressed(HashMap<String, Object> extraData) {
// JitsiMeetLogger.i("Custom button pressed: " + extraData);
// }
// protected void onConferenceUniqueIdSet(HashMap<String, Object> extraData) {
// JitsiMeetLogger.i("Conference unique id set: " + extraData);
// }
// protected void onRecordingStatusChanged(HashMap<String, Object> extraData) {
// JitsiMeetLogger.i("Recording status changed: " + extraData);
// }
// Activity lifecycle methods // Activity lifecycle methods
// //
@@ -314,9 +191,7 @@ public class JitsiMeetActivity extends AppCompatActivity
@Override @Override
protected void onUserLeaveHint() { protected void onUserLeaveHint() {
if (this.jitsiView != null) { getJitsiView().enterPictureInPicture();
this.jitsiView.enterPictureInPicture();
}
} }
// JitsiMeetActivityInterface // JitsiMeetActivityInterface
@@ -327,58 +202,29 @@ public class JitsiMeetActivity extends AppCompatActivity
JitsiMeetActivityDelegate.requestPermissions(this, permissions, requestCode, listener); JitsiMeetActivityDelegate.requestPermissions(this, permissions, requestCode, listener);
} }
@SuppressLint("MissingSuperCall")
@Override @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
} }
private void registerForBroadcastMessages() { // JitsiMeetViewListener
IntentFilter intentFilter = new IntentFilter(); //
for (BroadcastEvent.Type type : BroadcastEvent.Type.values()) { @Override
intentFilter.addAction(type.getAction()); public void onConferenceJoined(Map<String, Object> data) {
} JitsiMeetLogger.i("Conference joined: " + data);
// Launch the service for the ongoing notification.
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, intentFilter); JitsiMeetOngoingConferenceService.launch(this);
} }
private void onBroadcastReceived(Intent intent) { @Override
if (intent != null) { public void onConferenceTerminated(Map<String, Object> data) {
BroadcastEvent event = new BroadcastEvent(intent); JitsiMeetLogger.i("Conference terminated: " + data);
finish();
}
switch (event.getType()) { @Override
case CONFERENCE_JOINED: public void onConferenceWillJoin(Map<String, Object> data) {
onConferenceJoined(event.getData()); JitsiMeetLogger.i("Conference will join: " + data);
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;
// case TRANSCRIPTION_CHUNK_RECEIVED:
// onTranscriptionChunkReceived(event.getData());
// break;
// case CUSTOM_BUTTON_PRESSED:
// onCustomButtonPressed(event.getData());
// break;
// case CONFERENCE_UNIQUE_ID_SET:
// onConferenceUniqueIdSet(event.getData());
// break;
// case RECORDING_STATUS_CHANGED:
// onRecordingStatusChanged(event.getData());
// break;
}
}
} }
} }

View File

@@ -42,7 +42,7 @@ public class JitsiMeetActivityDelegate {
/** /**
* Tells whether or not the permissions request is currently in progress. * Tells whether or not the permissions request is currently in progress.
* *
* @return {@code true} if the permissions are being requested or {@code false} otherwise. * @return {@code true} if the permssions are being requested or {@code false} otherwise.
*/ */
static boolean arePermissionsBeingRequested() { static boolean arePermissionsBeingRequested() {
return permissionListener != null; return permissionListener != null;
@@ -116,15 +116,12 @@ public class JitsiMeetActivityDelegate {
= ReactInstanceManagerHolder.getReactInstanceManager(); = ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) { 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); 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");
} }
} }
} }

View File

@@ -21,7 +21,6 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
/** /**
@@ -41,21 +40,33 @@ public class JitsiMeetConferenceOptions implements Parcelable {
* Room name. * Room name.
*/ */
private String room; private String room;
/**
* Conference subject.
*/
private String subject;
/** /**
* JWT token used for authentication. * JWT token used for authentication.
*/ */
private String token; 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 * Feature flags. See: https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/flags/constants.js
*/ */
private Bundle featureFlags; 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. * USer information, to be used when no token is specified.
*/ */
@@ -69,14 +80,34 @@ public class JitsiMeetConferenceOptions implements Parcelable {
return room; return room;
} }
public String getSubject() {
return subject;
}
public String getToken() { public String getToken() {
return token; return token;
} }
public Bundle getColorScheme() {
return colorScheme;
}
public Bundle getFeatureFlags() { public Bundle getFeatureFlags() {
return featureFlags; return featureFlags;
} }
public boolean getAudioMuted() {
return audioMuted;
}
public boolean getAudioOnly() {
return audioOnly;
}
public boolean getVideoMuted() {
return videoMuted;
}
public JitsiMeetUserInfo getUserInfo() { public JitsiMeetUserInfo getUserInfo() {
return userInfo; return userInfo;
} }
@@ -87,15 +118,19 @@ public class JitsiMeetConferenceOptions implements Parcelable {
public static class Builder { public static class Builder {
private URL serverURL; private URL serverURL;
private String room; private String room;
private String subject;
private String token; private String token;
private Bundle config; private Bundle colorScheme;
private Bundle featureFlags; private Bundle featureFlags;
private Boolean audioMuted;
private Boolean audioOnly;
private Boolean videoMuted;
private JitsiMeetUserInfo userInfo; private JitsiMeetUserInfo userInfo;
public Builder() { public Builder() {
config = new Bundle();
featureFlags = new Bundle(); featureFlags = new Bundle();
} }
@@ -127,7 +162,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
* @return - The {@link Builder} object itself so the method calls can be chained. * @return - The {@link Builder} object itself so the method calls can be chained.
*/ */
public Builder setSubject(String subject) { public Builder setSubject(String subject) {
setConfigOverride("subject", subject); this.subject = subject;
return this; return this;
} }
@@ -144,12 +179,25 @@ public class JitsiMeetConferenceOptions implements Parcelable {
} }
/** /**
* Indicates the conference will be joined with the microphone muted. * Sets the color scheme override so the app is themed. See:
* @param audioMuted - Muted indication. * 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. * @return - The {@link Builder} object itself so the method calls can be chained.
*/ */
public Builder setAudioMuted(boolean audioMuted) { public Builder setColorScheme(Bundle colorScheme) {
setConfigOverride("startWithAudioMuted", audioMuted); 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; return this;
} }
@@ -161,7 +209,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
* @return - The {@link Builder} object itself so the method calls can be chained. * @return - The {@link Builder} object itself so the method calls can be chained.
*/ */
public Builder setAudioOnly(boolean audioOnly) { public Builder setAudioOnly(boolean audioOnly) {
setConfigOverride("startAudioOnly", audioOnly); this.audioOnly = audioOnly;
return this; return this;
} }
@@ -171,7 +219,20 @@ public class JitsiMeetConferenceOptions implements Parcelable {
* @return - The {@link Builder} object itself so the method calls can be chained. * @return - The {@link Builder} object itself so the method calls can be chained.
*/ */
public Builder setVideoMuted(boolean videoMuted) { 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; return this;
} }
@@ -200,42 +261,6 @@ public class JitsiMeetConferenceOptions implements Parcelable {
return this; 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;
}
public Builder setConfigOverride(String config, ArrayList<Bundle> arrayList) {
this.config.putParcelableArrayList(config, arrayList);
return this;
}
/** /**
* Builds the immutable {@link JitsiMeetConferenceOptions} object with the configuration * Builds the immutable {@link JitsiMeetConferenceOptions} object with the configuration
* that this {@link Builder} instance specified. * that this {@link Builder} instance specified.
@@ -246,9 +271,13 @@ public class JitsiMeetConferenceOptions implements Parcelable {
options.serverURL = this.serverURL; options.serverURL = this.serverURL;
options.room = this.room; options.room = this.room;
options.subject = this.subject;
options.token = this.token; options.token = this.token;
options.config = this.config; options.colorScheme = this.colorScheme;
options.featureFlags = this.featureFlags; options.featureFlags = this.featureFlags;
options.audioMuted = this.audioMuted;
options.audioOnly = this.audioOnly;
options.videoMuted = this.videoMuted;
options.userInfo = this.userInfo; options.userInfo = this.userInfo;
return options; return options;
@@ -261,17 +290,48 @@ public class JitsiMeetConferenceOptions implements Parcelable {
private JitsiMeetConferenceOptions(Parcel in) { private JitsiMeetConferenceOptions(Parcel in) {
serverURL = (URL) in.readSerializable(); serverURL = (URL) in.readSerializable();
room = in.readString(); room = in.readString();
subject = in.readString();
token = in.readString(); token = in.readString();
config = in.readBundle(); colorScheme = in.readBundle();
featureFlags = in.readBundle(); featureFlags = in.readBundle();
userInfo = new JitsiMeetUserInfo(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() { Bundle asProps() {
Bundle props = new Bundle(); Bundle props = new Bundle();
// Android always has the PiP flag set by default.
if (!featureFlags.containsKey("pip.enabled")) {
featureFlags.putBoolean("pip.enabled", true);
}
props.putBundle("flags", featureFlags); 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(); Bundle urlProps = new Bundle();
// The room is fully qualified // The room is fully qualified
@@ -319,10 +379,14 @@ public class JitsiMeetConferenceOptions implements Parcelable {
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(serverURL); dest.writeSerializable(serverURL);
dest.writeString(room); dest.writeString(room);
dest.writeString(subject);
dest.writeString(token); dest.writeString(token);
dest.writeBundle(config); dest.writeBundle(colorScheme);
dest.writeBundle(featureFlags); dest.writeBundle(featureFlags);
dest.writeBundle(userInfo != null ? userInfo.asBundle() : new Bundle()); 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 @Override

View File

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

View File

@@ -16,33 +16,16 @@
package org.jitsi.meet.sdk; package org.jitsi.meet.sdk;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.Manifest.permission.RECORD_AUDIO;
import android.app.Activity;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service; import android.app.Service;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.facebook.react.modules.core.PermissionListener;
import org.jitsi.meet.sdk.log.JitsiMeetLogger; import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
/** /**
* This class implements an Android {@link Service}, a foreground one specifically, and it's * This class implements an Android {@link Service}, a foreground one specifically, and it's
@@ -51,101 +34,33 @@ import java.util.Random;
* *
* See: https://developer.android.com/guide/components/services * See: https://developer.android.com/guide/components/services
*/ */
public class JitsiMeetOngoingConferenceService extends Service implements OngoingConferenceTracker.OngoingConferenceListener { public class JitsiMeetOngoingConferenceService extends Service
implements OngoingConferenceTracker.OngoingConferenceListener {
private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName(); private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
private static final String ACTIVITY_DATA_KEY = "activityDataKey";
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 static final int PERMISSIONS_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE); static final class Actions {
static final String START = TAG + ":START";
static final String HANGUP = TAG + ":HANGUP";
}
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver(); static void launch(Context context) {
OngoingNotification.createOngoingConferenceNotificationChannel();
private boolean isAudioMuted;
private Class tapBackActivity;
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
private static void doLaunch(Context context, HashMap<String, Object> extraData) {
Activity activity = (Activity) context;
OngoingNotification.createNotificationChannel(activity);
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class); Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
intent.setAction(Actions.START);
Bundle extraDataBundle = new Bundle();
extraDataBundle.putSerializable(EXTRA_DATA_KEY, extraData);
intent.putExtra(EXTRA_DATA_BUNDLE_KEY, extraDataBundle);
intent.putExtra(ACTIVITY_DATA_KEY, activity.getClass().getCanonicalName());
ComponentName componentName; ComponentName componentName;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
componentName = context.startForegroundService(intent); componentName = context.startForegroundService(intent);
} catch (RuntimeException e) { } else {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31). componentName = context.startService(intent);
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
return;
} }
if (componentName == null) { if (componentName == null) {
JitsiMeetLogger.w(TAG + " Ongoing conference service not started"); JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
} }
} }
static void abort(Context context) {
public static void launch(Context context, HashMap<String, Object> extraData) {
List<String> permissionsList = new ArrayList<>();
PermissionListener listener = new PermissionListener() {
@Override
public boolean onRequestPermissionsResult(int i, String[] strings, int[] results) {
int counter = 0;
if (results.length > 0) {
for (int result : results) {
if (result == PackageManager.PERMISSION_GRANTED) {
counter++;
}
}
if (counter == results.length){
doLaunch(context, extraData);
JitsiMeetLogger.w(TAG + " Service launched, permissions were granted");
} else {
JitsiMeetLogger.w(TAG + " Couldn't launch service, permissions were not granted");
}
}
return true;
}
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissionsList.add(POST_NOTIFICATIONS);
permissionsList.add(RECORD_AUDIO);
}
String[] permissionsArray = new String[ permissionsList.size() ];
permissionsArray = permissionsList.toArray( permissionsArray );
if (permissionsArray.length > 0) {
JitsiMeetActivityDelegate.requestPermissions(
(Activity) context,
permissionsArray,
PERMISSIONS_REQUEST_CODE,
listener
);
} else {
doLaunch(context, extraData);
JitsiMeetLogger.w(TAG + " Service launched");
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class); Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
context.stopService(intent); context.stopService(intent);
} }
@@ -154,31 +69,12 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this, tapBackActivity);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(NOTIFICATION_ID, notification);
}
}
OngoingConferenceTracker.getInstance().addListener(this); OngoingConferenceTracker.getInstance().addListener(this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BroadcastEvent.Type.AUDIO_MUTED_CHANGED.getAction());
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(broadcastReceiver, intentFilter);
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
OngoingConferenceTracker.getInstance().removeListener(this); OngoingConferenceTracker.getInstance().removeListener(this);
LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(broadcastReceiver);
super.onDestroy(); super.onDestroy();
} }
@@ -190,57 +86,26 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
final String actionName = intent.getAction(); final String action = intent.getAction();
final Action action = Action.fromName(actionName); if (Actions.START.equals(action)) {
Notification notification = OngoingNotification.buildOngoingConferenceNotification();
if (action != Action.HANGUP) {
Boolean isAudioMuted = tryParseIsAudioMuted(intent);
if (isAudioMuted != null) {
this.isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
}
if (tapBackActivity == null) {
String targetActivityName = intent.getExtras().getString(ACTIVITY_DATA_KEY);
Class<? extends Activity> targetActivity = null;
try {
targetActivity = Class.forName(targetActivityName).asSubclass(Activity.class);
tapBackActivity = targetActivity;
} catch (ClassNotFoundException e) {
JitsiMeetLogger.w(TAG + " Could not find target Activity: " + targetActivityName);
}
}
Notification notification = OngoingNotification.buildOngoingConferenceNotification(this.isAudioMuted, this, tapBackActivity);
if (notification == null) { if (notification == null) {
stopSelf(); stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null"); JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else { } else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); startForeground(OngoingNotification.NOTIFICATION_ID, notification);
notificationManager.notify(NOTIFICATION_ID, notification); JitsiMeetLogger.i(TAG + " Service started");
} }
} } else if (Actions.HANGUP.equals(action)) {
JitsiMeetLogger.i(TAG + " Hangup requested");
// When starting the service, there is no action passed in the intent // Abort all ongoing calls
if (action != null) { if (AudioModeModule.useConnectionService()) {
switch (action) { ConnectionService.abortConnections();
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;
} }
stopSelf();
} else {
JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
stopSelf();
} }
return START_NOT_STICKY; return START_NOT_STICKY;
@@ -250,61 +115,7 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
public void onCurrentConferenceChanged(String conferenceUrl) { public void onCurrentConferenceChanged(String conferenceUrl) {
if (conferenceUrl == null) { if (conferenceUrl == null) {
stopSelf(); stopSelf();
OngoingNotification.resetStartingtime();
JitsiMeetLogger.i(TAG + "Service stopped"); 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) {
Class tapBackActivity = JitsiMeetOngoingConferenceService.this.tapBackActivity;
isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, context, tapBackActivity);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't update service, notification is null");
} else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " audio muted changed");
}
}
}
} }

View File

@@ -16,31 +16,35 @@
package org.jitsi.meet.sdk; package org.jitsi.meet.sdk;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.facebook.react.ReactRootView; import com.facebook.react.bridge.ReadableMap;
import org.jitsi.meet.sdk.log.JitsiMeetLogger; 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. Should match the background color set in JS. * The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
* redux action types.
*/ */
private static final int BACKGROUND_COLOR = 0xFF040404; 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. * Helper method to recursively merge 2 {@link Bundle} objects representing React Native props.
@@ -79,8 +83,6 @@ public class JitsiMeetView extends FrameLayout {
result.putBoolean(key, (Boolean)bValue); result.putBoolean(key, (Boolean)bValue);
} else if (valueType.contentEquals("String")) { } else if (valueType.contentEquals("String")) {
result.putString(key, (String)bValue); result.putString(key, (String)bValue);
} else if (valueType.contentEquals("Integer")) {
result.putInt(key, (int)bValue);
} else if (valueType.contentEquals("Bundle")) { } else if (valueType.contentEquals("Bundle")) {
result.putBundle(key, mergeProps((Bundle)aValue, (Bundle)bValue)); result.putBundle(key, mergeProps((Bundle)aValue, (Bundle)bValue));
} else { } else {
@@ -93,32 +95,20 @@ public class JitsiMeetView extends FrameLayout {
public JitsiMeetView(@NonNull Context context) { public JitsiMeetView(@NonNull Context context) {
super(context); super(context);
initialize(context);
}
public JitsiMeetView(Context context, AttributeSet attrs) { // Check if the parent Activity implements JitsiMeetActivityInterface,
super(context, attrs); // otherwise things may go wrong.
initialize(context); if (!(context instanceof JitsiMeetActivityInterface)) {
} throw new RuntimeException("Enclosing Activity must implement JitsiMeetActivityInterface");
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;
} }
OngoingConferenceTracker.getInstance().addListener(this);
}
@Override
public void dispose() {
OngoingConferenceTracker.getInstance().removeListener(this);
super.dispose();
} }
/** /**
@@ -136,7 +126,8 @@ public class JitsiMeetView extends FrameLayout {
PictureInPictureModule.class); PictureInPictureModule.class);
if (pipModule != null if (pipModule != null
&& pipModule.isPictureInPictureSupported() && pipModule.isPictureInPictureSupported()
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()) { && !JitsiMeetActivityDelegate.arePermissionsBeingRequested()
&& this.url != null) {
try { try {
pipModule.enterPictureInPicture(); pipModule.enterPictureInPicture();
} catch (RuntimeException re) { } catch (RuntimeException re) {
@@ -156,50 +147,12 @@ public class JitsiMeetView extends FrameLayout {
} }
/** /**
* Internal method which aborts running RN by passing empty props. * Leaves the currently active conference.
* This is only meant to be used from the enclosing Activity's onDestroy.
*/ */
public void abort() { public void leave() {
setProps(new Bundle()); setProps(new Bundle());
} }
/**
* 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.
*/
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);
}
/** /**
* Helper method to set the React Native props. * Helper method to set the React Native props.
* @param newProps - New props to be set on the React Native view. * @param newProps - New props to be set on the React Native view.
@@ -214,7 +167,7 @@ public class JitsiMeetView extends FrameLayout {
// by leaving the conference. However, React and, respectively, // by leaving the conference. However, React and, respectively,
// appProperties/initialProperties are declarative expressions i.e. one // appProperties/initialProperties are declarative expressions i.e. one
// and the same URL will not trigger an automatic re-render in the // 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 // "imperativeness" in React Component props by defining a unique value
// per setProps() invocation. // per setProps() invocation.
props.putLong("timestamp", System.currentTimeMillis()); props.putLong("timestamp", System.currentTimeMillis());
@@ -222,6 +175,32 @@ public class JitsiMeetView extends FrameLayout {
createReactRootView("App", props); 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 @Override
protected void onDetachedFromWindow() { protected void onDetachedFromWindow() {
dispose(); dispose();

View File

@@ -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);
}

View File

@@ -1,80 +0,0 @@
package org.jitsi.meet.sdk;
/*
* 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.
*/
import androidx.annotation.Nullable;
import com.oney.WebRTCModule.webrtcutils.SoftwareVideoDecoderFactoryProxy;
import org.webrtc.EglBase;
import org.webrtc.HardwareVideoDecoderFactory;
import org.webrtc.PlatformSoftwareVideoDecoderFactory;
import org.webrtc.VideoCodecInfo;
import org.webrtc.VideoDecoder;
import org.webrtc.VideoDecoderFactory;
import org.webrtc.VideoDecoderFallback;
import java.util.Arrays;
import java.util.LinkedHashSet;
/**
* Custom decoder factory which uses HW decoders and falls back to SW.
*/
public class JitsiVideoDecoderFactory implements VideoDecoderFactory {
private final VideoDecoderFactory hardwareVideoDecoderFactory;
private final VideoDecoderFactory softwareVideoDecoderFactory = new SoftwareVideoDecoderFactoryProxy();
private final @Nullable VideoDecoderFactory platformSoftwareVideoDecoderFactory;
/**
* Create decoder factory using default hardware decoder factory.
*/
public JitsiVideoDecoderFactory(@Nullable EglBase.Context eglContext) {
this.hardwareVideoDecoderFactory = new HardwareVideoDecoderFactory(eglContext);
this.platformSoftwareVideoDecoderFactory = new PlatformSoftwareVideoDecoderFactory(eglContext);
}
/**
* Create decoder factory using explicit hardware decoder factory.
*/
JitsiVideoDecoderFactory(VideoDecoderFactory hardwareVideoDecoderFactory) {
this.hardwareVideoDecoderFactory = hardwareVideoDecoderFactory;
this.platformSoftwareVideoDecoderFactory = null;
}
@Override
public @Nullable VideoDecoder createDecoder(VideoCodecInfo codecType) {
VideoDecoder softwareDecoder = softwareVideoDecoderFactory.createDecoder(codecType);
final VideoDecoder hardwareDecoder = hardwareVideoDecoderFactory.createDecoder(codecType);
if (softwareDecoder == null && platformSoftwareVideoDecoderFactory != null) {
softwareDecoder = platformSoftwareVideoDecoderFactory.createDecoder(codecType);
}
if (hardwareDecoder != null && softwareDecoder != null) {
// Both hardware and software supported, wrap it in a software fallback
return new VideoDecoderFallback(
/* fallback= */ softwareDecoder, /* primary= */ hardwareDecoder);
}
return hardwareDecoder != null ? hardwareDecoder : softwareDecoder;
}
@Override
public VideoCodecInfo[] getSupportedCodecs() {
LinkedHashSet<VideoCodecInfo> supportedCodecInfos = new LinkedHashSet<>();
supportedCodecInfos.addAll(Arrays.asList(softwareVideoDecoderFactory.getSupportedCodecs()));
supportedCodecInfos.addAll(Arrays.asList(hardwareVideoDecoderFactory.getSupportedCodecs()));
if (platformSoftwareVideoDecoderFactory != null) {
supportedCodecInfos.addAll(
Arrays.asList(platformSoftwareVideoDecoderFactory.getSupportedCodecs()));
}
return supportedCodecInfos.toArray(new VideoCodecInfo[supportedCodecInfos.size()]);
}
}

View File

@@ -1,16 +0,0 @@
package org.jitsi.meet.sdk;
import androidx.annotation.Nullable;
import com.oney.WebRTCModule.webrtcutils.H264AndSoftwareVideoEncoderFactory;
import org.webrtc.EglBase;
/**
* Custom encoder factory which uses HW for H.264 and SW for everything else.
*/
public class JitsiVideoEncoderFactory extends H264AndSoftwareVideoEncoderFactory {
public JitsiVideoEncoderFactory(@Nullable EglBase.Context eglContext) {
super(eglContext);
}
}

View 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;
}
}

View File

@@ -47,7 +47,7 @@ class LocaleDetector extends ReactContextBaseJavaModule {
public Map<String, Object> getConstants() { public Map<String, Object> getConstants() {
Context context = getReactApplicationContext(); Context context = getReactApplicationContext();
HashMap<String,Object> constants = new HashMap<>(); HashMap<String,Object> constants = new HashMap<>();
constants.put("locale", context.getResources().getConfiguration().locale.toLanguageTag()); constants.put("locale", context.getResources().getConfiguration().locale.toString());
return constants; return constants;
} }
@@ -55,4 +55,4 @@ class LocaleDetector extends ReactContextBaseJavaModule {
public String getName() { public String getName() {
return "LocaleDetector"; return "LocaleDetector";
} }
} }

View File

@@ -16,19 +16,19 @@
package org.jitsi.meet.sdk; package org.jitsi.meet.sdk;
import org.jitsi.meet.sdk.log.JitsiMeetLogger; import android.app.Notification;
import android.app.Activity;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat; 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 * Helper class for creating the ongoing notification which is used with
@@ -38,28 +38,34 @@ import androidx.core.app.NotificationCompat;
class OngoingNotification { class OngoingNotification {
private static final String TAG = OngoingNotification.class.getSimpleName(); private static final String TAG = OngoingNotification.class.getSimpleName();
private static long startingTime = 0; private static final String CHANNEL_ID = "JitsiNotificationChannel";
private static final String CHANNEL_NAME = "Ongoing Conference Notifications";
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel"; static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
static void createNotificationChannel(Activity context) {
static void createOngoingConferenceNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
Context context = ReactInstanceManagerHolder.getCurrentActivity();
if (context == null) { if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context"); JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return; return;
} }
NotificationManager notificationManager NotificationManager notificationManager
= (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel NotificationChannel channel
= notificationManager.getNotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID); = notificationManager.getNotificationChannel(CHANNEL_ID);
if (channel != null) { if (channel != null) {
// The channel was already created, no need to do it again. // The channel was already created, no need to do it again.
return; return;
} }
channel = new NotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID, context.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT); channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(false); channel.enableLights(false);
channel.enableVibration(false); channel.enableVibration(false);
channel.setShowBadge(false); channel.setShowBadge(false);
@@ -67,19 +73,21 @@ class OngoingNotification {
notificationManager.createNotificationChannel(channel); notificationManager.createNotificationChannel(channel);
} }
static Notification buildOngoingConferenceNotification(Boolean isMuted, Context context, Class tapBackActivity) { static Notification buildOngoingConferenceNotification() {
Context context = ReactInstanceManagerHolder.getCurrentActivity();
if (context == null) { if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context"); JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null; return null;
} }
Intent notificationIntent = new Intent(context, tapBackActivity == null ? context.getClass() : tapBackActivity); 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); NotificationCompat.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (startingTime == 0) { builder = new NotificationCompat.Builder(context, CHANNEL_ID);
startingTime = System.currentTimeMillis(); } else {
builder = new NotificationCompat.Builder(context);
} }
builder builder
@@ -89,36 +97,23 @@ class OngoingNotification {
.setPriority(NotificationCompat.PRIORITY_DEFAULT) .setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.setOngoing(true) .setOngoing(true)
.setWhen(startingTime)
.setUsesChronometer(true)
.setAutoCancel(false) .setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setUsesChronometer(true)
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName())); .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 builder.addAction(hangupAction);
? 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);
return builder.build(); 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);
}
} }

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -20,6 +20,7 @@ import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.PictureInPictureParams; import android.app.PictureInPictureParams;
import android.os.Build;
import android.util.Rational; import android.util.Rational;
import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.Promise;
@@ -42,7 +43,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
private static final String TAG = NAME; private static final String TAG = NAME;
private static boolean isSupported; private static boolean isSupported;
private boolean isEnabled; private boolean isDisabled;
public PictureInPictureModule(ReactApplicationContext reactContext) { public PictureInPictureModule(ReactApplicationContext reactContext) {
super(reactContext); super(reactContext);
@@ -52,7 +53,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
// Android Go devices don't support PiP. There doesn't seem to be a better way to detect it than // Android Go devices don't support PiP. There doesn't seem to be a better way to detect it than
// to use ActivityManager.isLowRamDevice(). // to use ActivityManager.isLowRamDevice().
// https://stackoverflow.com/questions/58340558/how-to-detect-android-go // https://stackoverflow.com/questions/58340558/how-to-detect-android-go
isSupported = !am.isLowRamDevice(); isSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !am.isLowRamDevice();
} }
/** /**
@@ -81,8 +82,9 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
* including when the activity is not visible (paused or stopped), if the * including when the activity is not visible (paused or stopped), if the
* screen is locked or if the user has an activity pinned. * screen is locked or if the user has an activity pinned.
*/ */
@TargetApi(Build.VERSION_CODES.O)
public void enterPictureInPicture() { public void enterPictureInPicture() {
if (!isEnabled) { if (isDisabled) {
return; return;
} }
@@ -130,8 +132,8 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
} }
@ReactMethod @ReactMethod
public void setPictureInPictureEnabled(Boolean enabled) { public void setPictureInPictureDisabled(Boolean disabled) {
this.isEnabled = enabled; this.isDisabled = disabled;
} }
public boolean isPictureInPictureSupported() { public boolean isPictureInPictureSupported() {

View File

@@ -3,25 +3,21 @@ package org.jitsi.meet.sdk;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.telecom.DisconnectCause; import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount; import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle; import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager; import android.telecom.TelecomManager;
import android.telecom.VideoProfile; import android.telecom.VideoProfile;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger; import org.jitsi.meet.sdk.log.JitsiMeetLogger;
@@ -31,6 +27,7 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
* *
* @author Pawel Domas * @author Pawel Domas
*/ */
@RequiresApi(api = Build.VERSION_CODES.O)
@ReactModule(name = RNConnectionService.NAME) @ReactModule(name = RNConnectionService.NAME)
class RNConnectionService extends ReactContextBaseJavaModule { class RNConnectionService extends ReactContextBaseJavaModule {
@@ -38,7 +35,6 @@ class RNConnectionService extends ReactContextBaseJavaModule {
private static final String TAG = ConnectionService.TAG; private static final String TAG = ConnectionService.TAG;
private static RNConnectionService sRNConnectionServiceInstance;
/** /**
* Handler for dealing with call state changes. We are acting as a proxy between ConnectionService * Handler for dealing with call state changes. We are acting as a proxy between ConnectionService
* and other modules such as {@link AudioModeModule}. * and other modules such as {@link AudioModeModule}.
@@ -51,6 +47,7 @@ class RNConnectionService extends ReactContextBaseJavaModule {
* @param audioRoute the new audio route to be set. See * @param audioRoute the new audio route to be set. See
* {@link android.telecom.CallAudioState} constants prefixed with "ROUTE_". * {@link android.telecom.CallAudioState} constants prefixed with "ROUTE_".
*/ */
@RequiresApi(api = Build.VERSION_CODES.O)
static void setAudioRoute(int audioRoute) { static void setAudioRoute(int audioRoute) {
for (ConnectionService.ConnectionImpl c for (ConnectionService.ConnectionImpl c
: ConnectionService.getConnections()) { : ConnectionService.getConnections()) {
@@ -60,21 +57,6 @@ class RNConnectionService extends ReactContextBaseJavaModule {
RNConnectionService(ReactApplicationContext reactContext) { RNConnectionService(ReactApplicationContext reactContext) {
super(reactContext); super(reactContext);
sRNConnectionServiceInstance = this;
}
static RNConnectionService getInstance() {
return sRNConnectionServiceInstance;
}
@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.
} }
/** /**
@@ -201,7 +183,7 @@ class RNConnectionService extends ReactContextBaseJavaModule {
* Called by the JS side to update the call's state. * Called by the JS side to update the call's state.
* *
* @param callUUID - the call's UUID. * @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} * state. See static fields in {@link ConnectionService.ConnectionImpl}
* prefixed with "KEY_" for the values supported by the Android * prefixed with "KEY_" for the values supported by the Android
* implementation. * implementation.
@@ -234,22 +216,4 @@ class RNConnectionService extends ReactContextBaseJavaModule {
interface CallAudioStateListener { interface CallAudioStateListener {
void onCallAudioStateChange(android.telecom.CallAudioState callAudioState); void onCallAudioStateChange(android.telecom.CallAudioState callAudioState);
} }
/**
* Helper function to send an event to JavaScript.
*
* @param eventName {@code String} containing the event name.
* @param data {@code Object} optional ancillary data for the event.
*/
void emitEvent(
String eventName,
@Nullable Object data) {
ReactContext reactContext = getReactApplicationContext();
if (reactContext != null) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, data);
}
}
} }

View File

@@ -16,36 +16,36 @@
package org.jitsi.meet.sdk; package org.jitsi.meet.sdk;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContext;
import com.facebook.react.common.LifecycleState; 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.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.ViewManager; import com.facebook.react.uimanager.ViewManager;
import com.oney.WebRTCModule.EglUtils; import com.facebook.soloader.SoLoader;
import com.oney.WebRTCModule.WebRTCModuleOptions; import com.oney.WebRTCModule.RTCVideoViewManager;
import com.oney.WebRTCModule.WebRTCModule;
import org.devio.rn.splashscreen.SplashScreenModule; import org.devio.rn.splashscreen.SplashScreenModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger; import org.webrtc.SoftwareVideoDecoderFactory;
import org.webrtc.EglBase; import org.webrtc.SoftwareVideoEncoderFactory;
import org.webrtc.audio.AudioDeviceModule;
import org.webrtc.audio.JavaAudioDeviceModule;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
class ReactInstanceManagerHolder { class ReactInstanceManagerHolder {
private static final String TAG = ReactInstanceManagerHolder.class.getSimpleName();
/** /**
* FIXME (from linter): Do not place Android context classes in static * FIXME (from linter): Do not place Android context classes in static
* fields (static reference to ReactInstanceManager which has field * fields (static reference to ReactInstanceManager which has field
@@ -71,87 +71,42 @@ class ReactInstanceManagerHolder {
new SplashScreenModule(reactContext), new SplashScreenModule(reactContext),
new PictureInPictureModule(reactContext), new PictureInPictureModule(reactContext),
new ProximityModule(reactContext), new ProximityModule(reactContext),
new WiFiStatsModule(reactContext),
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext))); new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)));
if (AudioModeModule.useConnectionService()) { if (AudioModeModule.useConnectionService()) {
nativeModules.add(new RNConnectionService(reactContext)); nativeModules.add(new RNConnectionService(reactContext));
} }
// Initialize the WebRTC module by hand, since we want to override some
// initialization options.
WebRTCModule.Options options = new WebRTCModule.Options();
AudioDeviceModule adm = JavaAudioDeviceModule.builder(reactContext)
.createAudioDeviceModule();
options.setAudioDeviceModule(adm);
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; return nativeModules;
} }
private static List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { private static List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList(); return Arrays.<ViewManager>asList(
} // WebRTC, see createNativeModules for details.
new RTCVideoViewManager()
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 com.reactnativecommunity.webview.RNCWebViewPackage(),
new com.kevinresol.react_native_default_preference.RNDefaultPreferencePackage(),
new com.learnium.RNDeviceInfo.RNDeviceInfo(),
new com.oney.WebRTCModule.WebRTCModulePackage(),
new com.swmansion.gesturehandler.RNGestureHandlerPackage(),
new org.linusu.RNGetRandomValuesPackage(),
new com.rnimmersivemode.RNImmersiveModePackage(),
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.
JitsiMeetLogger.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.
JitsiMeetLogger.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.
JitsiMeetLogger.d(TAG, "Not loading RNGoogleSignInPackage");
}
return packages;
} }
/** /**
@@ -167,7 +122,7 @@ class ReactInstanceManagerHolder {
= ReactInstanceManagerHolder.getReactInstanceManager(); = ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) { if (reactInstanceManager != null) {
@SuppressLint("VisibleForTests") ReactContext reactContext ReactContext reactContext
= reactInstanceManager.getCurrentReactContext(); = reactInstanceManager.getCurrentReactContext();
if (reactContext != null) { if (reactContext != null) {
@@ -190,7 +145,7 @@ class ReactInstanceManagerHolder {
*/ */
static <T extends NativeModule> T getNativeModule( static <T extends NativeModule> T getNativeModule(
Class<T> nativeModuleClass) { Class<T> nativeModuleClass) {
@SuppressLint("VisibleForTests") ReactContext reactContext ReactContext reactContext
= reactInstanceManager != null = reactInstanceManager != null
? reactInstanceManager.getCurrentReactContext() : null; ? reactInstanceManager.getCurrentReactContext() : null;
@@ -198,6 +153,18 @@ class ReactInstanceManagerHolder {
? reactContext.getNativeModule(nativeModuleClass) : null; ? reactContext.getNativeModule(nativeModuleClass) : null;
} }
/**
* Gets the current {@link Activity} linked to React Native.
*
* @return An activity attached to React Native.
*/
static Activity getCurrentActivity() {
ReactContext reactContext
= reactInstanceManager != null
? reactInstanceManager.getCurrentReactContext() : null;
return reactContext != null ? reactContext.getCurrentActivity() : null;
}
static ReactInstanceManager getReactInstanceManager() { static ReactInstanceManager getReactInstanceManager() {
return reactInstanceManager; return reactInstanceManager;
} }
@@ -215,20 +182,44 @@ class ReactInstanceManagerHolder {
return; return;
} }
// Initialize the WebRTC module options. SoLoader.init(activity, /* native exopackage */ false);
WebRTCModuleOptions options = WebRTCModuleOptions.getInstance();
options.enableMediaProjectionService = true; List<ReactPackage> packages
if (options.videoDecoderFactory == null || options.videoEncoderFactory == null) { = new ArrayList<>(Arrays.asList(
EglBase.Context eglContext = EglUtils.getRootEglBaseContext(); new com.BV.LinearGradient.LinearGradientPackage(),
if (options.videoDecoderFactory == null) { new com.calendarevents.CalendarEventsPackage(),
options.videoDecoderFactory = new JitsiVideoDecoderFactory(eglContext); new com.corbt.keepawake.KCKeepAwakePackage(),
} new com.facebook.react.shell.MainReactPackage(),
if (options.videoEncoderFactory == null) { new com.horcrux.svg.SvgPackage(),
options.videoEncoderFactory = new JitsiVideoEncoderFactory(eglContext); 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.
} }
JitsiMeetLogger.d(TAG, "initializing RN"); // Keep on using JSC, the jury is out on Hermes.
JSCExecutorFactory jsFactory
= new JSCExecutorFactory("", "");
reactInstanceManager reactInstanceManager
= ReactInstanceManager.builder() = ReactInstanceManager.builder()
@@ -236,10 +227,20 @@ class ReactInstanceManagerHolder {
.setCurrentActivity(activity) .setCurrentActivity(activity)
.setBundleAssetName("index.android.bundle") .setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index.android") .setJSMainModulePath("index.android")
.setJavaScriptExecutorFactory(new HermesExecutorFactory()) .setJavaScriptExecutorFactory(jsFactory)
.addPackages(getReactNativePackages()) .addPackages(packages)
.setUseDeveloperSupport(BuildConfig.DEBUG) .setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED) .setInitialLifecycleState(LifecycleState.RESUMED)
.build(); .build();
// Disable delta updates on Android, they have caused trouble.
DevInternalSettings devSettings
= (DevInternalSettings)reactInstanceManager.getDevSupportManager().getDevSettings();
if (devSettings != null) {
devSettings.setBundleDeltasEnabled(false);
}
// Register our uncaught exception handler.
JitsiMeetUncaughtExceptionHandler.register();
} }
} }

View File

@@ -0,0 +1,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);
}
}

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

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

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ongoing_notification_title">Текущая встреча</string>
<string name="ongoing_notification_text">Нажмите, чтобы вернуться к встрече.</string>
<string name="ongoing_notification_action_hang_up">Отключиться</string>
<string name="ongoing_notification_action_mute">Отключить звук</string>
<string name="ongoing_notification_action_unmute">Включить звук</string>
<string name="ongoing_notification_channel_name">Ongoing Conference Notifications</string>
</resources>

View File

@@ -1,12 +1,6 @@
<resources> <resources>
<string name="app_name">Jitsi Meet SDK</string> <string name="app_name">Jitsi Meet SDK</string>
<string name="dropbox_app_key"></string> <string name="dropbox_app_key"></string>
<string name="media_projection_notification_title">Media projection</string>
<string name="media_projection_notification_text">You are currently sharing your screen.</string>
<string name="ongoing_notification_title">Ongoing meeting</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_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> </resources>

View File

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

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