mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-31 20:02:27 +00:00
Compare commits
1 Commits
8517
...
rm-dead-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38068b33e5 |
6
.buckconfig
Normal file
6
.buckconfig
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
[android]
|
||||
target = Google Inc.:Google APIs:23
|
||||
|
||||
[maven_repositories]
|
||||
central = https://repo1.maven.org/maven2
|
||||
@@ -3,7 +3,7 @@
|
||||
"image": "mcr.microsoft.com/devcontainers/universal:2",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "20"
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"hostRequirements": {
|
||||
|
||||
@@ -6,11 +6,8 @@ charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
max_line_length = 120
|
||||
max_line_length = 80
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# The build artifacts of the jitsi-meet project.
|
||||
build/*
|
||||
|
||||
doc/*
|
||||
|
||||
# Third-party source code which we (1) do not want to modify or (2) try to
|
||||
# modify as little as possible.
|
||||
libs/*
|
||||
@@ -17,8 +15,3 @@ react/features/face-landmarks/resources/*
|
||||
|
||||
# Not worth it.
|
||||
actionTypes.ts
|
||||
|
||||
# It's not complete until all files are copied at build time.
|
||||
react-native-sdk/
|
||||
|
||||
*.d.ts
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'extends': [
|
||||
'@jitsi/eslint-config'
|
||||
]
|
||||
],
|
||||
'ignorePatterns': [ '*.d.ts' ]
|
||||
};
|
||||
|
||||
48
.github/ISSUE_TEMPLATE/1-bug-report.md
vendored
Normal file
48
.github/ISSUE_TEMPLATE/1-bug-report.md
vendored
Normal 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. -->
|
||||
53
.github/ISSUE_TEMPLATE/1-bug.yml
vendored
53
.github/ISSUE_TEMPLATE/1-bug.yml
vendored
@@ -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).
|
||||
25
.github/ISSUE_TEMPLATE/2-feature-request.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/2-feature-request.md
vendored
Normal 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.
|
||||
|
||||
22
.github/ISSUE_TEMPLATE/2-feature.yml
vendored
22
.github/ISSUE_TEMPLATE/2-feature.yml
vendored
@@ -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.
|
||||
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Need help with your Jitsi Meet installation?
|
||||
- name: Need help with Jitsi Meet?
|
||||
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
16
.github/stale.yml
vendored
Normal 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
|
||||
|
||||
5
.github/workflows/ci-lua.yml
vendored
5
.github/workflows/ci-lua.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
name: Luacheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install luarocks
|
||||
run: sudo apt-get --install-recommends -y install luarocks
|
||||
@@ -17,8 +17,7 @@ jobs:
|
||||
|
||||
- name: Check lua codes
|
||||
run: |
|
||||
set -o pipefail && luacheck . \
|
||||
--exclude-files=resources/prosody-plugins/mod_firewall/mod_firewall.lua | awk -F: '
|
||||
set -o pipefail && luacheck . | awk -F: '
|
||||
{
|
||||
print $0
|
||||
printf "::warning file=%s,line=%s,col=%s::%s\n", $1, $2, $3, $4
|
||||
|
||||
181
.github/workflows/ci.yml
vendored
181
.github/workflows/ci.yml
vendored
@@ -7,179 +7,38 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
node-version: 16
|
||||
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
|
||||
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
|
||||
- run: npm run lint:ci
|
||||
linux-build:
|
||||
name: Build Frontend (Linux)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
node-version: 16
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: make
|
||||
macos-ci:
|
||||
name: Build Frontend (macOS)
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'npm'
|
||||
- name: Check Node / npm versions
|
||||
run: |
|
||||
node -v
|
||||
npm -v
|
||||
- run: npm install
|
||||
- run: make
|
||||
- run: sudo apt-get install -y debhelper
|
||||
- run: dpkg-buildpackage -A -rfakeroot -us -uc -d
|
||||
- run: make source-package
|
||||
|
||||
21
.github/workflows/stale.yml
vendored
21
.github/workflows/stale.yml
vendored
@@ -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
|
||||
29
.gitignore
vendored
29
.gitignore
vendored
@@ -38,7 +38,6 @@ DerivedData
|
||||
*.dSYM.zip
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
**/.xcode.env.local
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
@@ -62,10 +61,8 @@ buck-out/
|
||||
|
||||
# fastlane
|
||||
#
|
||||
.bundle/
|
||||
**/fastlane/report.xml
|
||||
**/fastlane/Preview.html
|
||||
**/fastlane/test_output
|
||||
*/fastlane/report.xml
|
||||
*/fastlane/Preview.html
|
||||
|
||||
# Build artifacts
|
||||
*.jsbundle
|
||||
@@ -96,25 +93,3 @@ twa/*.aab
|
||||
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
|
||||
|
||||
|
||||
6
.travis.yml
Normal file
6
.travis.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
osx_image: xcode11.1
|
||||
language: objective-c
|
||||
script:
|
||||
- "./ios/travis-ci/build-ipa.sh"
|
||||
after_script:
|
||||
- sleep 10
|
||||
173
CONTRIBUTING.md
173
CONTRIBUTING.md
@@ -1,20 +1,171 @@
|
||||
# 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
|
||||
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.
|
||||
* Comments from which documentation is automatically generated are **not**
|
||||
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.ts
|
||||
├── 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.ts`, `middlewares.native.ts` or `middlewares.web.ts` 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.
|
||||
|
||||
|
||||
### Avoiding bundle bloat
|
||||
|
||||
When adding a new feature it's possible that it triggers a build failure due to the increased bundle size. We have safeguards inplace to avoid bundles growing disproportionatelly. While there are legit reasons for increasing the limits, please analyze the bundle first to make sure no unintended dependencies have been included, causing the increase in size.
|
||||
|
||||
First, make a production build with bundle-analysis enabled:
|
||||
|
||||
```
|
||||
npx webpack -p --analyze-bundle
|
||||
```
|
||||
|
||||
Then open the interactive bundle analyzer tool:
|
||||
|
||||
```
|
||||
npx webpack-bundle-analyzer build/app-stats.json
|
||||
```
|
||||
|
||||
16
Gemfile
16
Gemfile
@@ -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"
|
||||
331
Gemfile.lock
331
Gemfile.lock
@@ -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
|
||||
31
Makefile
31
Makefile
@@ -17,16 +17,16 @@ STYLES_BUNDLE = css/all.bundle.css
|
||||
STYLES_DESTINATION = css/all.css
|
||||
STYLES_MAIN = css/main.scss
|
||||
ifeq ($(OS),Windows_NT)
|
||||
WEBPACK = .\node_modules\.bin\webpack --progress
|
||||
WEBPACK_DEV_SERVER = .\node_modules\.bin\webpack serve --mode development --progress
|
||||
WEBPACK = .\node_modules\.bin\webpack
|
||||
WEBPACK_DEV_SERVER = .\node_modules\.bin\webpack serve --mode development
|
||||
else
|
||||
WEBPACK = ./node_modules/.bin/webpack --progress
|
||||
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack serve --mode development --progress
|
||||
WEBPACK = ./node_modules/.bin/webpack
|
||||
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack serve --mode development
|
||||
endif
|
||||
|
||||
all: compile deploy
|
||||
all: compile deploy clean
|
||||
|
||||
compile: clean
|
||||
compile:
|
||||
NODE_OPTIONS=--max-old-space-size=8192 \
|
||||
$(WEBPACK)
|
||||
|
||||
@@ -44,16 +44,21 @@ deploy-appbundle:
|
||||
cp \
|
||||
$(BUILD_DIR)/app.bundle.min.js \
|
||||
$(BUILD_DIR)/app.bundle.min.js.map \
|
||||
$(BUILD_DIR)/do_external_connect.min.js \
|
||||
$(BUILD_DIR)/do_external_connect.min.js.map \
|
||||
$(BUILD_DIR)/external_api.min.js \
|
||||
$(BUILD_DIR)/external_api.min.js.map \
|
||||
$(BUILD_DIR)/dial_in_info_bundle.min.js \
|
||||
$(BUILD_DIR)/dial_in_info_bundle.min.js.map \
|
||||
$(BUILD_DIR)/alwaysontop.min.js \
|
||||
$(BUILD_DIR)/alwaysontop.min.js.map \
|
||||
$(OUTPUT_DIR)/analytics-ga.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.js.map \
|
||||
$(BUILD_DIR)/face-landmarks-worker.min.js \
|
||||
$(BUILD_DIR)/face-landmarks-worker.min.js.map \
|
||||
$(BUILD_DIR)/noise-suppressor-worklet.min.js \
|
||||
$(BUILD_DIR)/noise-suppressor-worklet.min.js.map \
|
||||
$(BUILD_DIR)/screenshot-capture-worker.min.js \
|
||||
$(BUILD_DIR)/screenshot-capture-worker.min.js.map \
|
||||
$(DEPLOY_DIR)
|
||||
cp \
|
||||
$(BUILD_DIR)/close3.min.js \
|
||||
@@ -62,7 +67,11 @@ deploy-appbundle:
|
||||
|
||||
deploy-lib-jitsi-meet:
|
||||
cp \
|
||||
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.* \
|
||||
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.min.js \
|
||||
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.min.map \
|
||||
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.e2ee-worker.js \
|
||||
$(LIBJITSIMEET_DIR)/connection_optimization/external_connect.js \
|
||||
$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-olm:
|
||||
@@ -120,9 +129,9 @@ deploy-local:
|
||||
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-olm deploy-tf-wasm deploy-excalidraw-dev deploy-face-landmarks
|
||||
$(WEBPACK_DEV_SERVER)
|
||||
|
||||
source-package: compile deploy
|
||||
source-package:
|
||||
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 && \
|
||||
(cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \
|
||||
rm -rf source_package
|
||||
|
||||
@@ -27,14 +27,14 @@ And many more!
|
||||
|
||||
## Using Jitsi Meet
|
||||
|
||||
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!
|
||||
Using Jitsi Meet is straightforward, as it's browser based. Head over to [meet.jit.si](https://meet.jit.si) and give it a try. It's anonymous, scalable and free to use. All browsers are supported!
|
||||
|
||||
Using mobile? No problem, you can either use your mobile web browser or our fully-featured
|
||||
mobile apps:
|
||||
|
||||
| Android | Android (F-Droid) | iOS |
|
||||
|:-:|:-:|:-:|
|
||||
| [<img src="resources/img/google-play-badge.png" height="50">](https://play.google.com/store/apps/details?id=org.jitsi.meet) | [<img src="resources/img/f-droid-badge.png" height="50">](https://f-droid.org/packages/org.jitsi.meet/) | [<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905) |
|
||||
| [<img src="resources/img/google-play-badge.png" height="50">](https://play.google.com/store/apps/details?id=org.jitsi.meet) | [<img src="resources/img/f-droid-badge.png" height="50">](https://f-droid.org/en/packages/org.jitsi.meet/) | [<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905) |
|
||||
|
||||
If you are feeling adventurous and want to get an early scoop of the features as they are being
|
||||
developed you can also sign up for our open beta testing here:
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
|
||||
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.**
|
||||
|
||||
163
analytics-ga.js
Normal file
163
analytics-ga.js
Normal 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 */
|
||||
@@ -16,6 +16,10 @@ android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
packagingOptions {
|
||||
exclude 'lib/*/libhermes*.so'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'org.jitsi.meet'
|
||||
versionCode vcode
|
||||
@@ -42,7 +46,6 @@ android {
|
||||
debug {
|
||||
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
|
||||
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
|
||||
applicationIdSuffix ".debug"
|
||||
}
|
||||
release {
|
||||
// Uncomment the following line for singing a test release build.
|
||||
@@ -69,24 +72,23 @@ android {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
namespace 'org.jitsi.meet'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.13'
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
// Sync with react-native-google-signin
|
||||
implementation 'com.google.android.gms:play-services-auth:20.5.0'
|
||||
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
||||
|
||||
// Firebase
|
||||
// - Crashlytics
|
||||
// - Dynamic Links
|
||||
implementation 'com.google.firebase:firebase-analytics:21.3.0'
|
||||
implementation 'com.google.firebase:firebase-crashlytics:18.4.3'
|
||||
implementation 'com.google.firebase:firebase-dynamic-links:21.1.0'
|
||||
implementation 'com.google.firebase:firebase-analytics:17.5.0'
|
||||
implementation 'com.google.firebase:firebase-crashlytics:17.2.1'
|
||||
implementation 'com.google.firebase:firebase-dynamic-links:19.1.0'
|
||||
}
|
||||
|
||||
implementation project(':sdk')
|
||||
|
||||
40
android/app/proguard-rules.pro
vendored
40
android/app/proguard-rules.pro
vendored
@@ -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.
|
||||
# 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
|
||||
@@ -14,25 +9,20 @@
|
||||
|
||||
# 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
|
||||
|
||||
# Keep our interfaces so they can be used by other ProGuard rules.
|
||||
# See http://sourceforge.net/p/proguard/bugs/466/
|
||||
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
|
||||
-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
|
||||
-keep @com.facebook.proguard.annotations.DoNotStrip class *
|
||||
-keep @com.facebook.common.internal.DoNotStrip class *
|
||||
-keepclassmembers class * {
|
||||
@com.facebook.proguard.annotations.DoNotStrip *;
|
||||
}
|
||||
|
||||
-keep @com.facebook.proguard.annotations.DoNotStripAny class * {
|
||||
*;
|
||||
@com.facebook.common.internal.DoNotStrip *;
|
||||
}
|
||||
|
||||
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
|
||||
@@ -40,32 +30,32 @@
|
||||
*** get*();
|
||||
}
|
||||
|
||||
-keep class * implements com.facebook.react.bridge.JavaScriptModule { *; }
|
||||
-keep class * implements com.facebook.react.bridge.NativeModule { *; }
|
||||
-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
|
||||
-keep class * extends com.facebook.react.bridge.NativeModule { *; }
|
||||
-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.ReactPropGroup <methods>; }
|
||||
|
||||
-dontwarn com.facebook.react.**
|
||||
-keep,includedescriptorclasses class com.facebook.react.bridge.** { *; }
|
||||
-keep,includedescriptorclasses class com.facebook.react.turbomodule.core.** { *; }
|
||||
|
||||
# hermes
|
||||
-keep class com.facebook.jni.** { *; }
|
||||
# okhttp
|
||||
|
||||
-keepattributes Signature
|
||||
-keepattributes *Annotation*
|
||||
-keep class okhttp3.** { *; }
|
||||
-keep interface okhttp3.** { *; }
|
||||
-dontwarn okhttp3.**
|
||||
|
||||
# okio
|
||||
|
||||
-keep class sun.misc.Unsafe { *; }
|
||||
-dontwarn java.nio.file.*
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-keep class 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
|
||||
|
||||
-keep class org.webrtc.** { *; }
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.jitsi.meet"
|
||||
android:installLocation="auto">
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:name=".MainApplication"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/AppTheme">
|
||||
<meta-data
|
||||
@@ -15,8 +17,7 @@
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleInstance"
|
||||
android:taskAffinity=""
|
||||
android:launchMode="singleTask"
|
||||
android:name=".MainActivity"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
|
||||
@@ -30,16 +30,15 @@ import android.view.KeyEvent;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.oney.WebRTCModule.WebRTCModuleOptions;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeet;
|
||||
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions;
|
||||
import org.webrtc.Logging;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* The one and only Activity that the Jitsi Meet app needs. The
|
||||
@@ -76,16 +75,13 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
*/
|
||||
private String defaultURL;
|
||||
|
||||
|
||||
// JitsiMeetActivity overrides
|
||||
//
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
JitsiMeet.showSplashScreen(this);
|
||||
|
||||
WebRTCModuleOptions options = WebRTCModuleOptions.getInstance();
|
||||
options.loggingSeverity = Logging.Severity.LS_ERROR;
|
||||
|
||||
super.onCreate(null);
|
||||
}
|
||||
|
||||
@@ -151,12 +147,13 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
}
|
||||
|
||||
private void setJitsiMeetConferenceDefaultOptions() {
|
||||
|
||||
// Set default options
|
||||
JitsiMeetConferenceOptions defaultOptions
|
||||
= new JitsiMeetConferenceOptions.Builder()
|
||||
.setServerURL(buildURL(defaultURL))
|
||||
.setFeatureFlag("welcomepage.enabled", true)
|
||||
.setFeatureFlag("call-integration.enabled", false)
|
||||
.setFeatureFlag("resolution", 360)
|
||||
.setFeatureFlag("server-url-change.enabled", !configurationByRestrictions)
|
||||
.build();
|
||||
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
|
||||
@@ -218,6 +215,11 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
super.onPictureInPictureModeChanged(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
|
||||
@@ -226,7 +228,7 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
private @Nullable URL buildURL(String urlStr) {
|
||||
try {
|
||||
return new URL(urlStr);
|
||||
} catch (Exception e) {
|
||||
} catch (MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright @ 2022-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet;
|
||||
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiReactNativeHost;
|
||||
|
||||
/**
|
||||
* Application class for Jitsi Meet. The only reason why this exists is for Detox
|
||||
* to believe our app is a "greenfield" app. SDK users need not use this.
|
||||
*/
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
private final ReactNativeHost mReactNativeHost = new JitsiReactNativeHost(this);
|
||||
|
||||
@Override
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// Initialize RN
|
||||
Log.d(this.getClass().getCanonicalName(), "app onCreate");
|
||||
getReactNativeHost().getReactInstanceManager();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<restrictions xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Server URL configuration -->
|
||||
<restriction
|
||||
android:description="@string/restriction_server_url_description"
|
||||
android:key="SERVER_URL"
|
||||
android:restrictionType="string"
|
||||
android:title="@string/restriction_server_url_title"/>
|
||||
</restrictions>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<restrictions xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Server URL configuration -->
|
||||
<restriction
|
||||
android:defaultValue="https://meet.jit.si"
|
||||
android:description="@string/restriction_server_url_description"
|
||||
android:key="SERVER_URL"
|
||||
android:restrictionType="string"
|
||||
android:title="@string/restriction_server_url_title"/>
|
||||
</restrictions>
|
||||
|
||||
@@ -10,22 +10,28 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.google.gms:google-services:4.4.0'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
|
||||
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||
classpath 'com.google.gms:google-services:4.3.14'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
kotlinVersion = "1.9.24"
|
||||
buildToolsVersion = "34.0.0"
|
||||
compileSdkVersion = 34
|
||||
minSdkVersion = 26
|
||||
targetSdkVersion = 34
|
||||
buildToolsVersion = "31.0.0"
|
||||
compileSdkVersion = 32
|
||||
minSdkVersion = 23
|
||||
targetSdkVersion = 32
|
||||
supportLibVersion = "28.0.0"
|
||||
ndkVersion = "26.1.10909125"
|
||||
|
||||
// The Maven artifact groupId of the third-party react-native modules which
|
||||
if (System.properties['os.arch'] == "aarch64") {
|
||||
// For M1 Users we need to use the NDK 24 which added support for aarch64
|
||||
ndkVersion = "24.0.8215888"
|
||||
} else {
|
||||
// Otherwise we default to the side-by-side NDK version from AGP.
|
||||
ndkVersion = "21.4.7075529"
|
||||
}
|
||||
|
||||
// The Maven artifact groupdId of the third-party react-native modules which
|
||||
// Jitsi Meet SDK for Android depends on and which are not available in
|
||||
// third-party Maven repositories so we have to deploy to a Maven repository
|
||||
// of ours.
|
||||
@@ -40,14 +46,21 @@ ext {
|
||||
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
|
||||
|
||||
googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild
|
||||
|
||||
//React Native and Hermes Version
|
||||
rnVersion = "0.75.5"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
// React Native (JS, Obj-C sources, Android binaries) is installed from npm.
|
||||
maven { url "$rootDir/../node_modules/react-native/android" }
|
||||
// Android JSC is installed from npm.
|
||||
maven { url("$rootDir/../node_modules/jsc-android/dist") }
|
||||
mavenCentral {
|
||||
// We don't want to fetch react-native from Maven Central as there are
|
||||
// older versions over there.
|
||||
content {
|
||||
excludeGroup "com.facebook.react"
|
||||
}
|
||||
}
|
||||
google()
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
}
|
||||
@@ -57,41 +70,20 @@ allprojects {
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
eachDependency { DependencyResolveDetails details ->
|
||||
if (details.requested.group == 'com.facebook.react') {
|
||||
if (details.requested.name == 'react-native') {
|
||||
details.useTarget "com.facebook.react:react-android:$rnVersion"
|
||||
}
|
||||
if (details.requested.name == 'react-android') {
|
||||
details.useVersion rootProject.ext.rnVersion
|
||||
}
|
||||
if (details.requested.group == 'com.facebook.react'
|
||||
&& details.requested.name == 'react-native') {
|
||||
def file = new File("$rootDir/../node_modules/react-native/package.json")
|
||||
def version = new JsonSlurper().parseText(file.text).version
|
||||
details.useVersion version
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// on and which are not available in third-party Maven repositories need to
|
||||
// be deployed in a Maven repository of ours.
|
||||
//
|
||||
|
||||
if (project.name.startsWith('react-native-')) {
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx1024m -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.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
@@ -27,4 +27,4 @@ android.enableJetifier=true
|
||||
android.bundle.enableUncompressedNativeLibs=false
|
||||
|
||||
appVersion=99.0.0
|
||||
sdkVersion=0.0.0
|
||||
sdkVersion=99.0.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -9,6 +9,9 @@ THE_MVN_REPO=${MVN_REPO:-${1:-$DEFAULT_MVN_REPO}}
|
||||
MVN_HTTP=0
|
||||
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
|
||||
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
|
||||
MVN_HTTP=1
|
||||
@@ -22,7 +25,60 @@ export MVN_REPO=$THE_MVN_REPO
|
||||
echo "Releasing Jitsi Meet SDK ${SDK_VERSION}"
|
||||
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
|
||||
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!"
|
||||
@@ -33,17 +89,20 @@ fi
|
||||
# Now build and publish the Jitsi Meet SDK and its dependencies
|
||||
echo "Building and publishing the Jitsi Meet SDK"
|
||||
pushd ${THIS_DIR}/../
|
||||
./gradlew clean
|
||||
./gradlew assembleRelease
|
||||
./gradlew clean
|
||||
./gradlew assembleRelease
|
||||
./gradlew publish
|
||||
popd
|
||||
|
||||
# The artifacts are now on the Maven repo, commit them
|
||||
if [[ $MVN_HTTP == 0 ]]; then
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
# The artifacts are now on the Maven repo, commit them
|
||||
pushd ${MVN_REPO_PATH}
|
||||
git add -A .
|
||||
git commit -m "Jitsi Meet SDK + dependencies: ${SDK_VERSION}"
|
||||
popd
|
||||
|
||||
# Tag the release
|
||||
git tag android-sdk-${SDK_VERSION}
|
||||
fi
|
||||
|
||||
# Done!
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
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
|
||||
|
||||
@@ -8,7 +8,6 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
buildConfigField "String", "SDK_VERSION", "\"$sdkVersion\""
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -31,7 +30,6 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace 'org.jitsi.meet.sdk'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -41,24 +39,23 @@ dependencies {
|
||||
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
|
||||
api "com.facebook.react:react-android:$rootProject.ext.rnVersion"
|
||||
api "com.facebook.react:hermes-android:$rootProject.ext.rnVersion"
|
||||
|
||||
//noinspection GradleDynamicVersion
|
||||
api 'com.facebook.react:react-native:+'
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation 'org.webkit:android-jsc:+'
|
||||
|
||||
implementation 'com.facebook.fresco:animated-gif:2.5.0'
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
|
||||
implementation 'com.jakewharton.timber:timber:5.0.1'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.squareup.duktape:duktape-android:1.3.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'androidx.startup:startup-runtime:1.1.0'
|
||||
implementation "androidx.startup:startup-runtime:1.1.0"
|
||||
|
||||
// Only add these packages if we are NOT doing a LIBRE_BUILD
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation project(':react-native-amplitude')
|
||||
implementation project(':react-native-giphy')
|
||||
implementation(project(':react-native-google-signin')) {
|
||||
implementation(project(":react-native-google-signin")) {
|
||||
exclude group: 'com.google.android.gms'
|
||||
exclude group: 'androidx'
|
||||
}
|
||||
@@ -77,7 +74,7 @@ dependencies {
|
||||
}
|
||||
implementation project(':react-native-gesture-handler')
|
||||
implementation project(':react-native-get-random-values')
|
||||
implementation project(':react-native-immersive-mode')
|
||||
implementation project(':react-native-immersive')
|
||||
implementation project(':react-native-keep-awake')
|
||||
implementation project(':react-native-orientation-locker')
|
||||
implementation project(':react-native-pager-view')
|
||||
@@ -89,11 +86,9 @@ dependencies {
|
||||
implementation project(':react-native-splash-screen')
|
||||
implementation project(':react-native-svg')
|
||||
implementation project(':react-native-video')
|
||||
implementation project(':react-native-webrtc')
|
||||
implementation project(':react-native-webview')
|
||||
|
||||
// Use `api` here so consumers can use WebRTCModuleOptions.
|
||||
api project(':react-native-webrtc')
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
@@ -142,7 +137,8 @@ android.libraryVariants.all { def variant ->
|
||||
// Run the bundler
|
||||
commandLine(
|
||||
"node",
|
||||
"node_modules/react-native/scripts/bundle.js",
|
||||
"node_modules/react-native/local-cli/cli.js",
|
||||
"bundle",
|
||||
"--platform", "android",
|
||||
"--dev", "${devEnabled}",
|
||||
"--reset-cache",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.jitsi.meet.sdk">
|
||||
<!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
@@ -12,10 +13,6 @@
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<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
|
||||
android:glEsVersion="0x00020000"
|
||||
@@ -51,7 +48,7 @@
|
||||
|
||||
<service
|
||||
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
|
||||
android:foregroundServiceType="mediaPlayback|microphone" />
|
||||
android:foregroundServiceType="mediaProjection" />
|
||||
|
||||
<provider
|
||||
android:name="com.reactnativecommunity.webview.RNCWebViewFileProvider"
|
||||
@@ -69,4 +66,4 @@
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright @ 2017-present 8x8, Inc.
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,7 +25,6 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -33,11 +32,7 @@ import java.util.Map;
|
||||
class AppInfoModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String BUILD_CONFIG = "org.jitsi.meet.sdk.BuildConfig";
|
||||
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) {
|
||||
super(reactContext);
|
||||
@@ -80,9 +75,8 @@ class AppInfoModule
|
||||
constants.put(
|
||||
"version",
|
||||
packageInfo == null ? "" : packageInfo.versionName);
|
||||
constants.put("sdkVersion", SDK_VERSION);
|
||||
constants.put("LIBRE_BUILD", LIBRE_BUILD);
|
||||
constants.put("GOOGLE_SERVICES_ENABLED", GOOGLE_SERVICES_ENABLED);
|
||||
constants.put("LIBRE_BUILD", BuildConfig.LIBRE_BUILD);
|
||||
constants.put("GOOGLE_SERVICES_ENABLED", BuildConfig.GOOGLE_SERVICES_ENABLED);
|
||||
|
||||
return constants;
|
||||
}
|
||||
@@ -91,60 +85,4 @@ class AppInfoModule
|
||||
public String getName() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.telecom.CallAudioState;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
@@ -33,6 +34,7 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
|
||||
* Android versions >= O when ConnectionService is enabled.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
class AudioDeviceHandlerConnectionService implements
|
||||
AudioModeModule.AudioDeviceHandlerInterface,
|
||||
RNConnectionService.CallAudioStateListener {
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.media.AudioAttributes;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioFocusRequest;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -44,12 +45,6 @@ class AudioDeviceHandlerGeneric implements
|
||||
*/
|
||||
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.
|
||||
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
|
||||
@@ -86,12 +81,10 @@ class AudioDeviceHandlerGeneric implements
|
||||
devices.add(AudioModeModule.DEVICE_EARPIECE);
|
||||
break;
|
||||
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
|
||||
case AudioDeviceInfo.TYPE_HDMI:
|
||||
devices.add(AudioModeModule.DEVICE_SPEAKER);
|
||||
break;
|
||||
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
|
||||
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
|
||||
case TYPE_HEARING_AID:
|
||||
case TYPE_USB_HEADSET:
|
||||
devices.add(AudioModeModule.DEVICE_HEADPHONES);
|
||||
break;
|
||||
@@ -226,17 +219,22 @@ class AudioDeviceHandlerGeneric implements
|
||||
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
audioManager.setMicrophoneMute(false);
|
||||
|
||||
int gotFocus = audioManager.requestAudioFocus(new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
|
||||
.setAudioAttributes(
|
||||
new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
||||
.build()
|
||||
)
|
||||
.setAcceptsDelayedFocusGain(true)
|
||||
.setOnAudioFocusChangeListener(this)
|
||||
.build()
|
||||
);
|
||||
int gotFocus;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
gotFocus = audioManager.requestAudioFocus(new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
|
||||
.setAudioAttributes(
|
||||
new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
||||
.build()
|
||||
)
|
||||
.setAcceptsDelayedFocusGain(true)
|
||||
.setOnAudioFocusChangeListener(this)
|
||||
.build()
|
||||
);
|
||||
} else {
|
||||
gotFocus = audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN);
|
||||
}
|
||||
|
||||
if (gotFocus == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
JitsiMeetLogger.w(TAG + " Audio focus request failed");
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
@@ -57,6 +58,7 @@ import java.util.concurrent.Executors;
|
||||
* Before a call has started and after it has ended the
|
||||
* {@code AudioModeModule.DEFAULT} mode should be used.
|
||||
*/
|
||||
@SuppressLint("AnnotateVersionCheck")
|
||||
@ReactModule(name = AudioModeModule.NAME)
|
||||
class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
public static final String NAME = "AudioMode";
|
||||
@@ -82,10 +84,11 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
/**
|
||||
* 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() {
|
||||
return useConnectionService_;
|
||||
return supportsConnectionService && useConnectionService_;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,11 +139,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
private String userSelectedDevice;
|
||||
|
||||
/**
|
||||
* Whether or not audio is disabled.
|
||||
*/
|
||||
private boolean audioDisabled;
|
||||
|
||||
/**
|
||||
* Initializes a new module instance. There shall be a single instance of
|
||||
* this module throughout the lifetime of the application.
|
||||
@@ -241,12 +239,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
audioDeviceHandler.stop();
|
||||
}
|
||||
|
||||
audioDeviceHandler = null;
|
||||
|
||||
if (audioDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (useConnectionService()) {
|
||||
audioDeviceHandler = new AudioDeviceHandlerConnectionService(audioManager);
|
||||
} else {
|
||||
@@ -289,27 +281,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
});
|
||||
}
|
||||
|
||||
@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.
|
||||
*
|
||||
@@ -319,12 +290,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setMode(final int mode, final Promise promise) {
|
||||
if (audioDisabled) {
|
||||
promise.resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode < DEFAULT || mode > VIDEO_CALL) {
|
||||
if (mode != DEFAULT && mode != AUDIO_CALL && mode != VIDEO_CALL) {
|
||||
promise.reject("setMode", "Invalid audio mode " + mode);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@ package org.jitsi.meet.sdk;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Wraps the name and extra data for events that were broadcasted locally.
|
||||
*/
|
||||
@@ -10,21 +16,57 @@ public class BroadcastAction {
|
||||
private static final String TAG = BroadcastAction.class.getSimpleName();
|
||||
|
||||
private final Type type;
|
||||
private final Bundle data;
|
||||
private final HashMap<String, Object> data;
|
||||
|
||||
public BroadcastAction(Intent intent) {
|
||||
this.type = Type.buildTypeFromAction(intent.getAction());
|
||||
this.data = intent.getExtras();
|
||||
this.data = buildDataFromBundle(intent.getExtras());
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public Bundle getData() {
|
||||
public HashMap<String, Object> getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public WritableNativeMap getDataAsWritableNativeMap() {
|
||||
WritableNativeMap nativeMap = new WritableNativeMap();
|
||||
|
||||
for (String key : this.data.keySet()) {
|
||||
try {
|
||||
if (this.data.get(key) instanceof Boolean) {
|
||||
nativeMap.putBoolean(key, (Boolean) this.data.get(key));
|
||||
} else if (this.data.get(key) instanceof Integer) {
|
||||
nativeMap.putInt(key, (Integer) this.data.get(key));
|
||||
} else if (this.data.get(key) instanceof Double) {
|
||||
nativeMap.putDouble(key, (Double) this.data.get(key));
|
||||
} else if (this.data.get(key) instanceof String) {
|
||||
nativeMap.putString(key, (String) this.data.get(key));
|
||||
} else {
|
||||
throw new Exception("Unsupported extra data type");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
JitsiMeetLogger.w(TAG + " invalid extra data in event", e);
|
||||
}
|
||||
}
|
||||
|
||||
return nativeMap;
|
||||
}
|
||||
|
||||
private static HashMap<String, Object> buildDataFromBundle(Bundle bundle) {
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
if (bundle != null) {
|
||||
for (String key : bundle.keySet()) {
|
||||
map.put(key, bundle.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
enum Type {
|
||||
SET_AUDIO_MUTED("org.jitsi.meet.SET_AUDIO_MUTED"),
|
||||
HANG_UP("org.jitsi.meet.HANG_UP"),
|
||||
@@ -35,14 +77,7 @@ public class BroadcastAction {
|
||||
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");
|
||||
SET_CLOSED_CAPTIONS_ENABLED("org.jitsi.meet.SET_CLOSED_CAPTIONS_ENABLED");
|
||||
|
||||
private final String action;
|
||||
|
||||
|
||||
@@ -75,8 +75,6 @@ public class BroadcastEvent {
|
||||
}
|
||||
|
||||
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"),
|
||||
@@ -89,14 +87,8 @@ public class BroadcastEvent {
|
||||
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");
|
||||
READY_TO_CLOSE("org.jitsi.meet.READY_TO_CLOSE");
|
||||
|
||||
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";
|
||||
@@ -110,10 +102,6 @@ public class BroadcastEvent {
|
||||
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;
|
||||
|
||||
@@ -136,10 +124,6 @@ public class BroadcastEvent {
|
||||
|
||||
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:
|
||||
@@ -166,14 +150,6 @@ public class BroadcastEvent {
|
||||
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;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -19,21 +17,18 @@ public class BroadcastIntentHelper {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -45,113 +40,18 @@ public class BroadcastIntentHelper {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,6 @@ 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;
|
||||
|
||||
@@ -31,14 +28,7 @@ public class BroadcastReceiver extends android.content.BroadcastReceiver {
|
||||
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());
|
||||
}
|
||||
|
||||
ReactInstanceManagerHolder.emitEvent(actionName, action.getDataAsWritableNativeMap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import android.telecom.PhoneAccount;
|
||||
import android.telecom.PhoneAccountHandle;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.telecom.VideoProfile;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
@@ -37,6 +36,7 @@ import java.util.Objects;
|
||||
*
|
||||
* @author Pawel Domas
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class ConnectionService extends android.telecom.ConnectionService {
|
||||
|
||||
/**
|
||||
@@ -357,7 +357,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
JitsiMeetLogger.i(TAG + " onDisconnect " + getCallUUID());
|
||||
WritableNativeMap data = new WritableNativeMap();
|
||||
data.putString("callUUID", getCallUUID());
|
||||
RNConnectionService.getInstance().emitEvent(
|
||||
ReactInstanceManagerHolder.emitEvent(
|
||||
"org.jitsi.meet:features/connection_service#disconnect",
|
||||
data);
|
||||
// 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());
|
||||
WritableNativeMap data = new WritableNativeMap();
|
||||
data.putString("callUUID", getCallUUID());
|
||||
RNConnectionService.getInstance().emitEvent(
|
||||
ReactInstanceManagerHolder.emitEvent(
|
||||
"org.jitsi.meet:features/connection_service#abort",
|
||||
data);
|
||||
// The JavaScript side will not go back to the native with
|
||||
@@ -406,7 +406,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
@Override
|
||||
public void onCallAudioStateChanged(CallAudioState state) {
|
||||
JitsiMeetLogger.d(TAG + " onCallAudioStateChanged: " + state);
|
||||
RNConnectionService module = RNConnectionService.getInstance();
|
||||
RNConnectionService module = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
|
||||
if (module != null) {
|
||||
module.onCallAudioStateChange(state);
|
||||
}
|
||||
|
||||
@@ -96,13 +96,6 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
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;
|
||||
}
|
||||
|
||||
46
android/sdk/src/main/java/org/jitsi/meet/sdk/H264Utils.java
Normal file
46
android/sdk/src/main/java/org/jitsi/meet/sdk/H264Utils.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import org.webrtc.VideoCodecInfo;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
/** Container for static helper functions related to dealing with H264 codecs. */
|
||||
class H264Utils {
|
||||
public static final String H264_FMTP_PROFILE_LEVEL_ID = "profile-level-id";
|
||||
public static final String H264_FMTP_LEVEL_ASYMMETRY_ALLOWED = "level-asymmetry-allowed";
|
||||
public static final String H264_FMTP_PACKETIZATION_MODE = "packetization-mode";
|
||||
|
||||
public static final String H264_PROFILE_CONSTRAINED_BASELINE = "42e0";
|
||||
public static final String H264_PROFILE_CONSTRAINED_HIGH = "640c";
|
||||
public static final String H264_LEVEL_3_1 = "1f"; // 31 in hex.
|
||||
public static final String H264_CONSTRAINED_HIGH_3_1 =
|
||||
H264_PROFILE_CONSTRAINED_HIGH + H264_LEVEL_3_1;
|
||||
public static final String H264_CONSTRAINED_BASELINE_3_1 =
|
||||
H264_PROFILE_CONSTRAINED_BASELINE + H264_LEVEL_3_1;
|
||||
|
||||
public static Map<String, String> getDefaultH264Params(boolean isHighProfile) {
|
||||
final Map<String, String> params = new HashMap<>();
|
||||
params.put(VideoCodecInfo.H264_FMTP_LEVEL_ASYMMETRY_ALLOWED, "1");
|
||||
params.put(VideoCodecInfo.H264_FMTP_PACKETIZATION_MODE, "1");
|
||||
params.put(VideoCodecInfo.H264_FMTP_PROFILE_LEVEL_ID,
|
||||
isHighProfile ? VideoCodecInfo.H264_CONSTRAINED_HIGH_3_1
|
||||
: VideoCodecInfo.H264_CONSTRAINED_BASELINE_3_1);
|
||||
return params;
|
||||
}
|
||||
|
||||
public static VideoCodecInfo DEFAULT_H264_BASELINE_PROFILE_CODEC =
|
||||
new VideoCodecInfo("H264", getDefaultH264Params(/* isHighProfile= */ false));
|
||||
public static VideoCodecInfo DEFAULT_H264_HIGH_PROFILE_CODEC =
|
||||
new VideoCodecInfo("H264", getDefaultH264Params(/* isHighProfile= */ true));
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
@@ -54,8 +53,6 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
private static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE";
|
||||
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) {
|
||||
@@ -126,8 +123,6 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
JitsiMeetLogger.i("onDestroy()");
|
||||
|
||||
// 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
|
||||
// with other content. If this Activity is "swiped out" from the recent list we will get
|
||||
@@ -135,10 +130,7 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
// 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
|
||||
// conference terminated. Thus, try our best to clean up.
|
||||
if (!isReadyToClose) {
|
||||
JitsiMeetLogger.i("onDestroy(): leaving...");
|
||||
leave();
|
||||
}
|
||||
leave();
|
||||
|
||||
this.jitsiView = null;
|
||||
|
||||
@@ -156,12 +148,8 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (!isReadyToClose) {
|
||||
JitsiMeetLogger.i("finish(): leaving...");
|
||||
leave();
|
||||
}
|
||||
leave();
|
||||
|
||||
JitsiMeetLogger.i("finish(): finishing...");
|
||||
super.finish();
|
||||
}
|
||||
|
||||
@@ -181,19 +169,16 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
public void join(JitsiMeetConferenceOptions options) {
|
||||
if (this.jitsiView != null) {
|
||||
this.jitsiView.join(options);
|
||||
if (this.jitsiView != null) {
|
||||
this.jitsiView .join(options);
|
||||
} else {
|
||||
JitsiMeetLogger.w("Cannot join, view is null");
|
||||
}
|
||||
}
|
||||
|
||||
protected void leave() {
|
||||
if (this.jitsiView != null) {
|
||||
this.jitsiView.abort();
|
||||
} else {
|
||||
JitsiMeetLogger.w("Cannot leave, view is null");
|
||||
}
|
||||
Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
|
||||
}
|
||||
|
||||
private @Nullable
|
||||
@@ -263,26 +248,9 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
|
||||
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
|
||||
//
|
||||
|
||||
@@ -314,8 +282,8 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
|
||||
@Override
|
||||
protected void onUserLeaveHint() {
|
||||
if (this.jitsiView != null) {
|
||||
this.jitsiView.enterPictureInPicture();
|
||||
if (this.jitsiView != null) {
|
||||
this.jitsiView .enterPictureInPicture();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +295,6 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
JitsiMeetActivityDelegate.requestPermissions(this, permissions, requestCode, listener);
|
||||
}
|
||||
|
||||
@SuppressLint("MissingSuperCall")
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
@@ -366,18 +333,6 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class JitsiMeetActivityDelegate {
|
||||
/**
|
||||
* 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() {
|
||||
return permissionListener != null;
|
||||
|
||||
@@ -21,7 +21,6 @@ import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
/**
|
||||
@@ -230,12 +229,6 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
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
|
||||
* that this {@link Builder} instance specified.
|
||||
@@ -270,6 +263,11 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
Bundle asProps() {
|
||||
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);
|
||||
|
||||
Bundle urlProps = new Bundle();
|
||||
|
||||
@@ -16,10 +16,6 @@
|
||||
|
||||
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.NotificationManager;
|
||||
import android.app.Service;
|
||||
@@ -27,22 +23,15 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
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.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
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
|
||||
@@ -51,39 +40,34 @@ import java.util.Random;
|
||||
*
|
||||
* 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 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);
|
||||
|
||||
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver();
|
||||
|
||||
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);
|
||||
public static void launch(Context context, HashMap<String, Object> extraData) {
|
||||
OngoingNotification.createOngoingConferenceNotificationChannel();
|
||||
|
||||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
|
||||
Bundle extraDataBundle = new Bundle();
|
||||
extraDataBundle.putSerializable(EXTRA_DATA_KEY, extraData);
|
||||
|
||||
intent.putExtra(EXTRA_DATA_BUNDLE_KEY, extraDataBundle);
|
||||
intent.putExtra(ACTIVITY_DATA_KEY, activity.getClass().getCanonicalName());
|
||||
|
||||
ComponentName componentName;
|
||||
|
||||
try {
|
||||
componentName = context.startForegroundService(intent);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
componentName = context.startForegroundService(intent);
|
||||
} else {
|
||||
componentName = context.startService(intent);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
|
||||
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
|
||||
@@ -96,55 +80,6 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
context.stopService(intent);
|
||||
@@ -154,18 +89,13 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this, tapBackActivity);
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
|
||||
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);
|
||||
}
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
JitsiMeetLogger.i(TAG + " Service started");
|
||||
}
|
||||
|
||||
OngoingConferenceTracker.getInstance().addListener(this);
|
||||
@@ -190,37 +120,25 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
final String actionName = intent.getAction();
|
||||
final Action action = Action.fromName(actionName);
|
||||
|
||||
if (action != Action.HANGUP) {
|
||||
Boolean isAudioMuted = tryParseIsAudioMuted(intent);
|
||||
Boolean isAudioMuted = tryParseIsAudioMuted(intent);
|
||||
|
||||
if (isAudioMuted != null) {
|
||||
this.isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
|
||||
}
|
||||
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);
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
} else {
|
||||
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
}
|
||||
}
|
||||
|
||||
final String actionName = intent.getAction();
|
||||
final Action action = Action.fromName(actionName);
|
||||
|
||||
// When starting the service, there is no action passed in the intent
|
||||
if (action != null) {
|
||||
switch (action) {
|
||||
@@ -293,15 +211,14 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
|
||||
|
||||
@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);
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't update service, notification is null");
|
||||
} else {
|
||||
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
|
||||
JitsiMeetLogger.i(TAG + " audio muted changed");
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.rnimmersive.RNImmersiveModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
@@ -33,9 +34,10 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
public class JitsiMeetView extends FrameLayout {
|
||||
|
||||
/**
|
||||
* Background color. Should match the background color set in JS.
|
||||
* Background color used by {@code BaseReactView} and the React Native root
|
||||
* view.
|
||||
*/
|
||||
private static final int BACKGROUND_COLOR = 0xFF040404;
|
||||
private static final int BACKGROUND_COLOR = 0xFF111111;
|
||||
|
||||
/**
|
||||
* React Native root view.
|
||||
@@ -155,14 +157,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
setProps(options != null ? options.asProps() : new Bundle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method which aborts running RN by passing empty props.
|
||||
* This is only meant to be used from the enclosing Activity's onDestroy.
|
||||
*/
|
||||
public void abort() {
|
||||
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}.
|
||||
@@ -227,4 +221,22 @@ public class JitsiMeetView extends FrameLayout {
|
||||
dispose();
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the window containing this view gains or loses focus.
|
||||
*
|
||||
* @param hasFocus If the window of this view now has focus, {@code true};
|
||||
* otherwise, {@code false}.
|
||||
*/
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
|
||||
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
|
||||
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
|
||||
|
||||
if (hasFocus && immersive != null) {
|
||||
immersive.emitImmersiveStateChangeEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is the minimal implementation of ReactNativeHost that will make things like the
|
||||
* Detox testing framework believe we are a "greenfield" app.
|
||||
*
|
||||
* Generally speaking, apps using the SDK (other than the Jitsi Meet app itself) should not
|
||||
* need to use this because the
|
||||
*/
|
||||
public class JitsiReactNativeHost extends ReactNativeHost {
|
||||
public JitsiReactNativeHost(Application application) {
|
||||
super(application);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getUseDeveloperSupport() {
|
||||
// Unused since we override `createReactInstanceManager`.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
// Unused since we override `createReactInstanceManager`.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReactInstanceManager createReactInstanceManager() {
|
||||
ReactInstanceManagerHolder.initReactInstanceManager(this.getApplication());
|
||||
|
||||
return ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
}
|
||||
}
|
||||
@@ -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()]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class NotificationChannels {
|
||||
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
|
||||
|
||||
public static List<String> allIds = new ArrayList<String>() {{ add(ONGOING_CONFERENCE_CHANNEL_ID); }};
|
||||
}
|
||||
@@ -16,19 +16,22 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
import static org.jitsi.meet.sdk.NotificationChannels.ONGOING_CONFERENCE_CHANNEL_ID;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Helper class for creating the ongoing notification which is used with
|
||||
@@ -38,11 +41,15 @@ import androidx.core.app.NotificationCompat;
|
||||
class OngoingNotification {
|
||||
private static final String TAG = OngoingNotification.class.getSimpleName();
|
||||
|
||||
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
|
||||
private static long startingTime = 0;
|
||||
|
||||
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
|
||||
static void createOngoingConferenceNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return;
|
||||
}
|
||||
|
||||
static void createNotificationChannel(Activity context) {
|
||||
Context context = ReactInstanceManagerHolder.getCurrentActivity();
|
||||
if (context == null) {
|
||||
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
|
||||
return;
|
||||
@@ -53,13 +60,12 @@ class OngoingNotification {
|
||||
|
||||
NotificationChannel channel
|
||||
= notificationManager.getNotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID);
|
||||
|
||||
if (channel != null) {
|
||||
// The channel was already created, no need to do it again.
|
||||
return;
|
||||
}
|
||||
|
||||
channel = new NotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID, context.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
channel = new NotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID, context.getString(R.string.ongoing_notification_action_unmute), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
channel.enableLights(false);
|
||||
channel.enableVibration(false);
|
||||
channel.setShowBadge(false);
|
||||
@@ -67,13 +73,14 @@ class OngoingNotification {
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
static Notification buildOngoingConferenceNotification(Boolean isMuted, Context context, Class tapBackActivity) {
|
||||
static Notification buildOngoingConferenceNotification(boolean isMuted) {
|
||||
Context context = ReactInstanceManagerHolder.getCurrentActivity();
|
||||
if (context == null) {
|
||||
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
|
||||
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);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, ONGOING_CONFERENCE_CHANNEL_ID);
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.PictureInPictureParams;
|
||||
import android.os.Build;
|
||||
import android.util.Rational;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
@@ -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
|
||||
// to use ActivityManager.isLowRamDevice().
|
||||
// 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,6 +82,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||
* including when the activity is not visible (paused or stopped), if the
|
||||
* screen is locked or if the user has an activity pinned.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public void enterPictureInPicture() {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
|
||||
@@ -3,25 +3,21 @@ package org.jitsi.meet.sdk;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.telecom.DisconnectCause;
|
||||
import android.telecom.PhoneAccount;
|
||||
import android.telecom.PhoneAccountHandle;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.telecom.VideoProfile;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
@@ -31,6 +27,7 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
*
|
||||
* @author Pawel Domas
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@ReactModule(name = RNConnectionService.NAME)
|
||||
class RNConnectionService extends ReactContextBaseJavaModule {
|
||||
|
||||
@@ -38,7 +35,6 @@ class RNConnectionService extends ReactContextBaseJavaModule {
|
||||
|
||||
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
|
||||
* 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
|
||||
* {@link android.telecom.CallAudioState} constants prefixed with "ROUTE_".
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
static void setAudioRoute(int audioRoute) {
|
||||
for (ConnectionService.ConnectionImpl c
|
||||
: ConnectionService.getConnections()) {
|
||||
@@ -60,11 +57,6 @@ class RNConnectionService extends ReactContextBaseJavaModule {
|
||||
|
||||
RNConnectionService(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
sRNConnectionServiceInstance = this;
|
||||
}
|
||||
|
||||
static RNConnectionService getInstance() {
|
||||
return sRNConnectionServiceInstance;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
@@ -234,22 +226,4 @@ class RNConnectionService extends ReactContextBaseJavaModule {
|
||||
interface CallAudioStateListener {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,31 +16,33 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
import com.facebook.react.jscexecutor.JSCExecutorFactory;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.oney.WebRTCModule.EglUtils;
|
||||
import com.oney.WebRTCModule.WebRTCModuleOptions;
|
||||
import com.oney.WebRTCModule.RTCVideoViewManager;
|
||||
import com.oney.WebRTCModule.WebRTCModule;
|
||||
|
||||
import org.devio.rn.splashscreen.SplashScreenModule;
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.audio.AudioDeviceModule;
|
||||
import org.webrtc.audio.JavaAudioDeviceModule;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
class ReactInstanceManagerHolder {
|
||||
@@ -77,11 +79,30 @@ class ReactInstanceManagerHolder {
|
||||
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)
|
||||
.setEnableVolumeLogger(false)
|
||||
.createAudioDeviceModule();
|
||||
options.setAudioDeviceModule(adm);
|
||||
|
||||
EglBase.Context eglContext = EglUtils.getRootEglBaseContext();
|
||||
|
||||
options.setVideoDecoderFactory(new WebRTCVideoDecoderFactory(eglContext));
|
||||
options.setVideoEncoderFactory(new WebRTCVideoEncoderFactory(eglContext));
|
||||
|
||||
nativeModules.add(new WebRTCModule(reactContext, options));
|
||||
|
||||
return nativeModules;
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -101,10 +122,9 @@ class ReactInstanceManagerHolder {
|
||||
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.rnimmersive.RNImmersivePackage(),
|
||||
new com.swmansion.rnscreens.RNScreensPackage(),
|
||||
new com.zmxv.RNSound.RNSoundPackage(),
|
||||
new com.th3rdwave.safeareacontext.SafeAreaContextPackage(),
|
||||
@@ -124,36 +144,41 @@ class ReactInstanceManagerHolder {
|
||||
// AmplitudeReactNativePackage
|
||||
try {
|
||||
Class<?> amplitudePackageClass = Class.forName("com.amplitude.reactnative.AmplitudeReactNativePackage");
|
||||
Constructor<?> constructor = amplitudePackageClass.getConstructor();
|
||||
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");
|
||||
Log.d(TAG, "Not loading AmplitudeReactNativePackage");
|
||||
}
|
||||
|
||||
// GiphyReactNativeSdkPackage
|
||||
try {
|
||||
Class<?> giphyPackageClass = Class.forName("com.giphyreactnativesdk.GiphyReactNativeSdkPackage");
|
||||
Constructor<?> constructor = giphyPackageClass.getConstructor();
|
||||
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");
|
||||
Log.d(TAG, "Not loading GiphyReactNativeSdkPackage");
|
||||
}
|
||||
|
||||
// RNGoogleSignInPackage
|
||||
try {
|
||||
Class<?> googlePackageClass = Class.forName("com.reactnativegooglesignin.RNGoogleSigninPackage");
|
||||
Constructor<?> constructor = googlePackageClass.getConstructor();
|
||||
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");
|
||||
Log.d(TAG, "Not loading RNGoogleSignInPackage");
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
static JSCExecutorFactory getReactNativeJSFactory() {
|
||||
// Keep on using JSC, the jury is out on Hermes.
|
||||
return new JSCExecutorFactory("", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to send an event to JavaScript.
|
||||
*
|
||||
@@ -167,7 +192,7 @@ class ReactInstanceManagerHolder {
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
@SuppressLint("VisibleForTests") ReactContext reactContext
|
||||
ReactContext reactContext
|
||||
= reactInstanceManager.getCurrentReactContext();
|
||||
|
||||
if (reactContext != null) {
|
||||
@@ -190,7 +215,7 @@ class ReactInstanceManagerHolder {
|
||||
*/
|
||||
static <T extends NativeModule> T getNativeModule(
|
||||
Class<T> nativeModuleClass) {
|
||||
@SuppressLint("VisibleForTests") ReactContext reactContext
|
||||
ReactContext reactContext
|
||||
= reactInstanceManager != null
|
||||
? reactInstanceManager.getCurrentReactContext() : null;
|
||||
|
||||
@@ -198,10 +223,51 @@ class ReactInstanceManagerHolder {
|
||||
? 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() {
|
||||
return reactInstanceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to initialize the React Native instance manager. We
|
||||
* create a single instance in order to load the JavaScript bundle a single
|
||||
* time. All {@code ReactRootView} instances will be tied to the one and
|
||||
* only {@code ReactInstanceManager}.
|
||||
*
|
||||
* This method is only meant to be called when integrating with {@code JitsiReactNativeHost}.
|
||||
*
|
||||
* @param app {@code Application} current running Application.
|
||||
*/
|
||||
static void initReactInstanceManager(Application app) {
|
||||
if (reactInstanceManager != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "initializing RN with Application");
|
||||
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
.setApplication(app)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.setJavaScriptExecutorFactory(getReactNativeJSFactory())
|
||||
.addPackages(getReactNativePackages())
|
||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to initialize the React Native instance manager. We
|
||||
* create a single instance in order to load the JavaScript bundle a single
|
||||
@@ -215,20 +281,7 @@ class ReactInstanceManagerHolder {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the WebRTC module options.
|
||||
WebRTCModuleOptions options = WebRTCModuleOptions.getInstance();
|
||||
options.enableMediaProjectionService = true;
|
||||
if (options.videoDecoderFactory == null || options.videoEncoderFactory == null) {
|
||||
EglBase.Context eglContext = EglUtils.getRootEglBaseContext();
|
||||
if (options.videoDecoderFactory == null) {
|
||||
options.videoDecoderFactory = new JitsiVideoDecoderFactory(eglContext);
|
||||
}
|
||||
if (options.videoEncoderFactory == null) {
|
||||
options.videoEncoderFactory = new JitsiVideoEncoderFactory(eglContext);
|
||||
}
|
||||
}
|
||||
|
||||
JitsiMeetLogger.d(TAG, "initializing RN");
|
||||
Log.d(ReactInstanceManagerHolder.class.getCanonicalName(), "initializing RN with Activity");
|
||||
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
@@ -236,7 +289,7 @@ class ReactInstanceManagerHolder {
|
||||
.setCurrentActivity(activity)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.setJavaScriptExecutorFactory(new HermesExecutorFactory())
|
||||
.setJavaScriptExecutorFactory(getReactNativeJSFactory())
|
||||
.addPackages(getReactNativePackages())
|
||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
/** Enumeration of supported video codec types. */
|
||||
public enum VideoCodecMimeType {
|
||||
VP8("video/x-vnd.on2.vp8"),
|
||||
VP9("video/x-vnd.on2.vp9"),
|
||||
H264("video/avc"),
|
||||
AV1("video/av01");
|
||||
|
||||
private final String mimeType;
|
||||
|
||||
private VideoCodecMimeType(String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
|
||||
String mimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.HardwareVideoDecoderFactory;
|
||||
import org.webrtc.SoftwareVideoDecoderFactory;
|
||||
import org.webrtc.VideoCodecInfo;
|
||||
import org.webrtc.VideoDecoder;
|
||||
import org.webrtc.VideoDecoderFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is a custom video decoder factory for WebRTC which behaves similarly
|
||||
* to the default one in iOS. It supports the following codecs:
|
||||
*
|
||||
* - In hardware: H.264 (baseline)
|
||||
* - In software: VP8, VP9, AV1
|
||||
*/
|
||||
public class WebRTCVideoDecoderFactory implements VideoDecoderFactory {
|
||||
private final VideoDecoderFactory hardwareVideoDecoderFactory;
|
||||
private final VideoDecoderFactory softwareVideoDecoderFactory = new SoftwareVideoDecoderFactory();
|
||||
|
||||
public WebRTCVideoDecoderFactory(@Nullable EglBase.Context eglContext) {
|
||||
this.hardwareVideoDecoderFactory = new HardwareVideoDecoderFactory(eglContext);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public VideoDecoder createDecoder(VideoCodecInfo codecInfo) {
|
||||
if (codecInfo.name.equalsIgnoreCase(VideoCodecMimeType.H264.name())) {
|
||||
return this.hardwareVideoDecoderFactory.createDecoder(codecInfo);
|
||||
}
|
||||
|
||||
return this.softwareVideoDecoderFactory.createDecoder(codecInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoCodecInfo[] getSupportedCodecs() {
|
||||
List<VideoCodecInfo> codecs = new ArrayList<>();
|
||||
|
||||
codecs.add(H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC);
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP8.name(), new HashMap<>()));
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP9.name(), new HashMap<>()));
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.AV1.name(), new HashMap<>()));
|
||||
|
||||
return codecs.toArray(new VideoCodecInfo[codecs.size()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.HardwareVideoEncoderFactory;
|
||||
import org.webrtc.SoftwareVideoEncoderFactory;
|
||||
import org.webrtc.VideoCodecInfo;
|
||||
import org.webrtc.VideoEncoder;
|
||||
import org.webrtc.VideoEncoderFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is a custom video encoder factory for WebRTC which behaves similarly
|
||||
* to the default one in iOS. It supports the following codecs:
|
||||
*
|
||||
* - In hardware: H.264 (baseline)
|
||||
* - In software: VP8, VP9, AV1
|
||||
*/
|
||||
public class WebRTCVideoEncoderFactory implements VideoEncoderFactory {
|
||||
private final VideoEncoderFactory hardwareVideoEncoderFactory;
|
||||
private final VideoEncoderFactory softwareVideoEncoderFactory = new SoftwareVideoEncoderFactory();
|
||||
|
||||
public WebRTCVideoEncoderFactory(@Nullable EglBase.Context eglContext) {
|
||||
this.hardwareVideoEncoderFactory =
|
||||
new HardwareVideoEncoderFactory(eglContext, false, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public VideoEncoder createEncoder(VideoCodecInfo codecInfo) {
|
||||
if (codecInfo.name.equalsIgnoreCase(VideoCodecMimeType.H264.name())) {
|
||||
return this.hardwareVideoEncoderFactory.createEncoder(codecInfo);
|
||||
}
|
||||
|
||||
return this.softwareVideoEncoderFactory.createEncoder(codecInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoCodecInfo[] getSupportedCodecs() {
|
||||
List<VideoCodecInfo> codecs = new ArrayList<>();
|
||||
|
||||
codecs.add(H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC);
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP8.name(), new HashMap<>()));
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP9.name(), new HashMap<>()));
|
||||
codecs.add(new VideoCodecInfo(VideoCodecMimeType.AV1.name(), new HashMap<>()));
|
||||
|
||||
return codecs.toArray(new VideoCodecInfo[codecs.size()]);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1,8 +1,6 @@
|
||||
<resources>
|
||||
<string name="app_name">Jitsi Meet SDK</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_text">You are currently in a meeting. Tap to return to it.</string>
|
||||
<string name="ongoing_notification_action_hang_up">Hang up</string>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
rootProject.name = 'jitsi-meet'
|
||||
|
||||
include ':app', ':sdk'
|
||||
includeBuild('../node_modules/react-native-gradle-plugin')
|
||||
|
||||
include ':react-native-amplitude'
|
||||
project(':react-native-amplitude').projectDir = new File(rootProject.projectDir, '../node_modules/@amplitude/react-native/android')
|
||||
project(':react-native-amplitude').projectDir = new File(rootProject.projectDir, '../node_modules/@amplitude/react-native//android')
|
||||
include ':react-native-async-storage'
|
||||
project(':react-native-async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-async-storage/async-storage/android')
|
||||
include ':react-native-background-timer'
|
||||
@@ -11,7 +12,7 @@ project(':react-native-background-timer').projectDir = new File(rootProject.proj
|
||||
include ':react-native-calendar-events'
|
||||
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')
|
||||
include ':react-native-community_clipboard'
|
||||
project(':react-native-community_clipboard').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-clipboard/clipboard/android')
|
||||
project(':react-native-community_clipboard').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/clipboard/android')
|
||||
include ':react-native-community_netinfo'
|
||||
project(':react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android')
|
||||
include ':react-native-default-preference'
|
||||
@@ -26,8 +27,8 @@ include ':react-native-giphy'
|
||||
project(':react-native-giphy').projectDir = new File(rootProject.projectDir, '../node_modules/@giphy/react-native-sdk/android')
|
||||
include ':react-native-google-signin'
|
||||
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-google-signin/google-signin/android')
|
||||
include ':react-native-immersive-mode'
|
||||
project(':react-native-immersive-mode').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive-mode/android')
|
||||
include ':react-native-immersive'
|
||||
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
|
||||
include ':react-native-keep-awake'
|
||||
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
|
||||
include ':react-native-orientation-locker'
|
||||
|
||||
47
app.js
47
app.js
@@ -1,4 +1,4 @@
|
||||
/* Jitsi Meet app main entrypoint. */
|
||||
/* application specific logic */
|
||||
|
||||
// Re-export jQuery
|
||||
// FIXME: Remove this requirement from torture tests.
|
||||
@@ -10,32 +10,6 @@ import '@matrix-org/olm';
|
||||
|
||||
import 'focus-visible';
|
||||
|
||||
/*
|
||||
* Safari polyfill for createImageBitmap
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/createImageBitmap
|
||||
*
|
||||
* Support source image types: Canvas.
|
||||
*/
|
||||
if (!('createImageBitmap' in window)) {
|
||||
window.createImageBitmap = function(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let dataURL;
|
||||
|
||||
if (data instanceof HTMLCanvasElement) {
|
||||
dataURL = data.toDataURL();
|
||||
} else {
|
||||
reject(new Error('createImageBitmap does not handle the provided image source type'));
|
||||
}
|
||||
const img = document.createElement('img');
|
||||
|
||||
img.addEventListener('load', () => {
|
||||
resolve(img);
|
||||
});
|
||||
img.src = dataURL;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// We need to setup the jitsi-local-storage as early as possible so that we can start using it.
|
||||
// NOTE: If jitsi-local-storage is used before the initial setup is performed this will break the use case when we use
|
||||
// the local storage from the parent page when the localStorage is disabled. Also the setup is relying that
|
||||
@@ -44,6 +18,7 @@ import './react/features/base/jitsi-local-storage/setup';
|
||||
import conference from './conference';
|
||||
import API from './modules/API';
|
||||
import UI from './modules/UI/UI';
|
||||
import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
|
||||
import translation from './modules/translation/translation';
|
||||
|
||||
// Initialize Olm as early as possible.
|
||||
@@ -57,6 +32,24 @@ if (window.Olm) {
|
||||
window.APP = {
|
||||
API,
|
||||
conference,
|
||||
|
||||
// Used by do_external_connect.js if we receive the attach data after
|
||||
// connect was already executed. status property can be 'initialized',
|
||||
// 'ready', or 'connecting'. We are interested in 'ready' status only which
|
||||
// means that connect was executed but we have to wait for the attach data.
|
||||
// In status 'ready' handler property will be set to a function that will
|
||||
// finish the connect process when the attach data or error is received.
|
||||
connect: {
|
||||
handler: null,
|
||||
status: 'initialized'
|
||||
},
|
||||
|
||||
// Used for automated performance tests.
|
||||
connectionTimes: {
|
||||
'index.loaded': window.indexLoadedTime
|
||||
},
|
||||
|
||||
keyboardshortcut,
|
||||
translation,
|
||||
UI
|
||||
};
|
||||
|
||||
@@ -5,12 +5,5 @@ module.exports = {
|
||||
plugins: [ 'react-native-paper/babel' ]
|
||||
}
|
||||
},
|
||||
|
||||
// This happens because react native has conflict with @babel/plugin-transform-private-methods plugin
|
||||
// https://github.com/ethers-io/ethers.js/discussions/4309#discussioncomment-6694524
|
||||
plugins: [ 'optional-require',
|
||||
[ '@babel/plugin-transform-private-methods', {
|
||||
'loose': true
|
||||
} ]
|
||||
]
|
||||
plugins: [ 'optional-require' ]
|
||||
};
|
||||
|
||||
1338
conference.js
1338
conference.js
File diff suppressed because it is too large
Load Diff
254
connection.js
Normal file
254
connection.js
Normal file
@@ -0,0 +1,254 @@
|
||||
/* global APP, JitsiMeetJS, config */
|
||||
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import Logger from '@jitsi/logger';
|
||||
|
||||
import { redirectToTokenAuthService } from './modules/UI/authentication/AuthHandler';
|
||||
import { LoginDialog } from './react/features/authentication/components';
|
||||
import { isTokenAuthEnabled } from './react/features/authentication/functions';
|
||||
import {
|
||||
connectionEstablished,
|
||||
connectionFailed,
|
||||
constructOptions
|
||||
} from './react/features/base/connection/actions.web';
|
||||
import { openDialog } from './react/features/base/dialog/actions';
|
||||
import { setJWT } from './react/features/base/jwt';
|
||||
import {
|
||||
JitsiConnectionErrors,
|
||||
JitsiConnectionEvents
|
||||
} from './react/features/base/lib-jitsi-meet';
|
||||
import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-meet/functions';
|
||||
import { getCustomerDetails } from './react/features/jaas/actions.any';
|
||||
import { getJaasJWT, isVpaasMeeting } from './react/features/jaas/functions';
|
||||
import {
|
||||
setPrejoinDisplayNameRequired
|
||||
} from './react/features/prejoin/actions';
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
* The feature announced so we can distinguish jibri participants.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri';
|
||||
|
||||
/**
|
||||
* Checks if we have data to use attach instead of connect. If we have the data
|
||||
* executes attach otherwise check if we have to wait for the data. If we have
|
||||
* to wait for the attach data we are setting handler to APP.connect.handler
|
||||
* which is going to be called when the attach data is received otherwise
|
||||
* executes connect.
|
||||
*
|
||||
* @param {string} [id] user id
|
||||
* @param {string} [password] password
|
||||
* @param {string} [roomName] the name of the conference.
|
||||
*/
|
||||
function checkForAttachParametersAndConnect(id, password, connection) {
|
||||
if (window.XMPPAttachInfo) {
|
||||
APP.connect.status = 'connecting';
|
||||
|
||||
// When connection optimization is not deployed or enabled the default
|
||||
// value will be window.XMPPAttachInfo.status = "error"
|
||||
// If the connection optimization is deployed and enabled and there is
|
||||
// a failure the value will be window.XMPPAttachInfo.status = "error"
|
||||
if (window.XMPPAttachInfo.status === 'error') {
|
||||
connection.connect({
|
||||
id,
|
||||
password
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const attachOptions = window.XMPPAttachInfo.data;
|
||||
|
||||
if (attachOptions) {
|
||||
connection.attach(attachOptions);
|
||||
delete window.XMPPAttachInfo.data;
|
||||
} else {
|
||||
connection.connect({
|
||||
id,
|
||||
password
|
||||
});
|
||||
}
|
||||
} else {
|
||||
APP.connect.status = 'ready';
|
||||
APP.connect.handler
|
||||
= checkForAttachParametersAndConnect.bind(
|
||||
null,
|
||||
id, password, connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to open connection using provided credentials.
|
||||
* @param {string} [id]
|
||||
* @param {string} [password]
|
||||
* @returns {Promise<JitsiConnection>} connection if
|
||||
* everything is ok, else error.
|
||||
*/
|
||||
export async function connect(id, password) {
|
||||
const state = APP.store.getState();
|
||||
let { jwt } = state['features/base/jwt'];
|
||||
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
|
||||
|
||||
if (!iAmRecorder && !iAmSipGateway && isVpaasMeeting(state)) {
|
||||
await APP.store.dispatch(getCustomerDetails());
|
||||
|
||||
if (!jwt) {
|
||||
jwt = await getJaasJWT(state);
|
||||
APP.store.dispatch(setJWT(jwt));
|
||||
}
|
||||
}
|
||||
|
||||
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, constructOptions(state));
|
||||
|
||||
if (config.iAmRecorder) {
|
||||
connection.addFeature(DISCO_JIBRI_FEATURE);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
handleConnectionEstablished);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
handleConnectionFailed);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailedHandler);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.DISPLAY_NAME_REQUIRED,
|
||||
displayNameRequiredHandler
|
||||
);
|
||||
|
||||
/* eslint-disable max-params */
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function connectionFailedHandler(error, message, credentials, details) {
|
||||
/* eslint-enable max-params */
|
||||
APP.store.dispatch(
|
||||
connectionFailed(
|
||||
connection, {
|
||||
credentials,
|
||||
details,
|
||||
message,
|
||||
name: error
|
||||
}));
|
||||
|
||||
if (isFatalJitsiConnectionError(error)) {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailedHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function unsubscribe() {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
handleConnectionEstablished);
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
handleConnectionFailed);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function handleConnectionEstablished() {
|
||||
APP.store.dispatch(connectionEstablished(connection, Date.now()));
|
||||
unsubscribe();
|
||||
resolve(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function handleConnectionFailed(err) {
|
||||
unsubscribe();
|
||||
logger.error('CONNECTION FAILED:', err);
|
||||
reject(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the display name for the prejoin screen as required.
|
||||
* This can happen if a user tries to join a room with lobby enabled.
|
||||
*/
|
||||
function displayNameRequiredHandler() {
|
||||
APP.store.dispatch(setPrejoinDisplayNameRequired());
|
||||
}
|
||||
|
||||
checkForAttachParametersAndConnect(id, password, connection);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open JitsiConnection using provided credentials.
|
||||
* If retry option is true it will show auth dialog on PASSWORD_REQUIRED error.
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {string} [options.id]
|
||||
* @param {string} [options.password]
|
||||
* @param {string} [options.roomName]
|
||||
* @param {boolean} [retry] if we should show auth dialog
|
||||
* on PASSWORD_REQUIRED error.
|
||||
*
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
export function openConnection({ id, password, retry, roomName }) {
|
||||
const usernameOverride
|
||||
= jitsiLocalStorage.getItem('xmpp_username_override');
|
||||
const passwordOverride
|
||||
= jitsiLocalStorage.getItem('xmpp_password_override');
|
||||
|
||||
if (usernameOverride && usernameOverride.length > 0) {
|
||||
id = usernameOverride; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
if (passwordOverride && passwordOverride.length > 0) {
|
||||
password = passwordOverride; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
return connect(id, password, roomName).catch(err => {
|
||||
if (retry) {
|
||||
const { jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED && !jwt) {
|
||||
return requestAuth(roomName);
|
||||
}
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Authentication Dialog and try to connect with new credentials.
|
||||
* If failed to connect because of PASSWORD_REQUIRED error
|
||||
* then ask for password again.
|
||||
* @param {string} [roomName] name of the conference room
|
||||
*
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
function requestAuth(roomName) {
|
||||
const config = APP.store.getState()['features/base/config'];
|
||||
|
||||
if (isTokenAuthEnabled(config)) {
|
||||
// This Promise never resolves as user gets redirected to another URL
|
||||
return new Promise(() => redirectToTokenAuthService(roomName));
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
const onSuccess = connection => {
|
||||
resolve(connection);
|
||||
};
|
||||
|
||||
APP.store.dispatch(
|
||||
openDialog(LoginDialog, { onSuccess,
|
||||
roomName })
|
||||
);
|
||||
});
|
||||
}
|
||||
3
connection_optimization/.eslintrc.js
Normal file
3
connection_optimization/.eslintrc.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
'extends': '../react/.eslintrc.js'
|
||||
};
|
||||
86
connection_optimization/do_external_connect.js
Normal file
86
connection_optimization/do_external_connect.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/* global config, createConnectionExternally */
|
||||
|
||||
import getRoomName from '../react/features/base/config/getRoomName';
|
||||
import { parseURLParams } from '../react/features/base/util/parseURLParams';
|
||||
|
||||
/**
|
||||
* Implements external connect using createConnectionExternally function defined
|
||||
* in external_connect.js for Jitsi Meet. Parses the room name and JSON Web
|
||||
* Token (JWT) from the URL and executes createConnectionExternally.
|
||||
*
|
||||
* NOTE: If you are using lib-jitsi-meet without Jitsi Meet, you should use this
|
||||
* file as reference only because the implementation is Jitsi Meet-specific.
|
||||
*
|
||||
* NOTE: For optimal results this file should be included right after
|
||||
* external_connect.js.
|
||||
*/
|
||||
|
||||
if (typeof createConnectionExternally === 'function') {
|
||||
// URL params have higher priority than config params.
|
||||
// Do not use external connect if websocket is enabled.
|
||||
let url
|
||||
= parseURLParams(window.location, true, 'hash')[
|
||||
'config.externalConnectUrl']
|
||||
|| config.websocket ? undefined : config.externalConnectUrl;
|
||||
const isRecorder
|
||||
= parseURLParams(window.location, true, 'hash')['config.iAmRecorder'];
|
||||
|
||||
let roomName;
|
||||
|
||||
if (url && (roomName = getRoomName()) && !isRecorder) {
|
||||
url += `?room=${roomName}`;
|
||||
|
||||
const token = parseURLParams(window.location, true, 'search').jwt;
|
||||
|
||||
if (token) {
|
||||
url += `&token=${token}`;
|
||||
}
|
||||
|
||||
createConnectionExternally(
|
||||
url,
|
||||
connectionInfo => {
|
||||
// Sets that global variable to be used later by connect method
|
||||
// in connection.js.
|
||||
window.XMPPAttachInfo = {
|
||||
status: 'success',
|
||||
data: connectionInfo
|
||||
};
|
||||
checkForConnectHandlerAndConnect();
|
||||
},
|
||||
errorCallback);
|
||||
} else {
|
||||
errorCallback();
|
||||
}
|
||||
} else {
|
||||
errorCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connect from connection.js was executed and executes the handler
|
||||
* that is going to finish the connect work.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkForConnectHandlerAndConnect() {
|
||||
window.APP
|
||||
&& window.APP.connect.status === 'ready'
|
||||
&& window.APP.connect.handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a callback to be invoked if anything goes wrong.
|
||||
*
|
||||
* @param {Error} error - The specifics of what went wrong.
|
||||
* @returns {void}
|
||||
*/
|
||||
function errorCallback(error) {
|
||||
// The value of error is undefined if external connect is disabled.
|
||||
error && console.warn(error);
|
||||
|
||||
// Sets that global variable to be used later by connect method in
|
||||
// connection.js.
|
||||
window.XMPPAttachInfo = {
|
||||
status: 'error'
|
||||
};
|
||||
checkForConnectHandlerAndConnect();
|
||||
}
|
||||
117
css/_atlaskit_overrides.scss
Normal file
117
css/_atlaskit_overrides.scss
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Mixins that mimic the way Atlaskit fills the screen with modals at low screen widths.
|
||||
*/
|
||||
@mixin full-size-modal-positioner() {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@mixin full-size-modal-dialog() {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the @atlaskit/flag container up a little bit so it does not cover the
|
||||
* toolbar with the first notification.
|
||||
*/
|
||||
.atlaskit-portal > #notifications-container {
|
||||
bottom: calc(#{$newToolbarSizeWithPadding}) !important;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Keep overflow menu within screen vertical bounds and make it scrollable.
|
||||
*/
|
||||
.toolbox-button-wth-dialog > div:nth-child(2) {
|
||||
background: $menuBG;
|
||||
max-height: calc(100vh - #{$newToolbarSizeWithPadding} - 46px);
|
||||
margin-bottom: 4px;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove background color and box-shadow for the context menu container.
|
||||
*/
|
||||
.toolbox-button-wth-dialog.context-menu > div:nth-child(2) {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
overflow-y: initial;
|
||||
}
|
||||
|
||||
.audio-preview > div:nth-child(2),
|
||||
.video-preview > div:nth-child(2) {
|
||||
margin-bottom: 4px;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following selectors keep the chat modal full-size anywhere between 100px
|
||||
* and 580px for desktop or 680px for mobile.
|
||||
*/
|
||||
@media (min-width: 100px) and (max-width: 320px) {
|
||||
.smiley-input {
|
||||
display: none;
|
||||
}
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 480px) and (max-width: 580px) {
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 580px) {
|
||||
// Override Atlaskit inline style for the modal background.
|
||||
// Important is unfortunately needed for that.
|
||||
.shift-right .focus-lock [role="dialog"][style] {
|
||||
background-color: $chatBackgroundColor !important;
|
||||
}
|
||||
|
||||
// Remove Atlaskit padding from the chat dialog.
|
||||
.shift-right .focus-lock [role="dialog"] > div:first-child > div:nth-child(2) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
div.Tooltip {
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
// make modal full screen on landscape orientation
|
||||
@media (max-height: 420px) {
|
||||
.atlaskit-portal {
|
||||
.css-1oc7v0j {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
max-width: 100%;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
&> div {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
103
css/_audio-preview.scss
Normal file
103
css/_audio-preview.scss
Normal file
@@ -0,0 +1,103 @@
|
||||
.audio-preview {
|
||||
display: inline-block;
|
||||
|
||||
&-content {
|
||||
position: relative;
|
||||
right: auto;
|
||||
margin-bottom: 8px;
|
||||
max-height: 456px;
|
||||
overflow: auto;
|
||||
width: 300px;
|
||||
|
||||
&-ul {
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style-type: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-header:hover {
|
||||
background-color: initial;
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
&-entry-text {
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 213px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
&.left-margin {
|
||||
margin-left: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
&-speaker {
|
||||
position: relative;
|
||||
|
||||
&:hover, &:focus-within, &:focus {
|
||||
.audio-preview-test-button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.audio-preview-entry-text {
|
||||
max-width: 178px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-preview-entry-text {
|
||||
max-width: 238px;
|
||||
}
|
||||
}
|
||||
|
||||
&-microphone {
|
||||
position: relative;
|
||||
|
||||
&--nometer {
|
||||
.audio-preview-entry-text {
|
||||
max-width: 238px;
|
||||
}
|
||||
}
|
||||
|
||||
&--withmeter {
|
||||
.audio-preview-entry-text {
|
||||
max-width: 178px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&-icon {
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
|
||||
&--exclamation {
|
||||
margin-left: 6px;
|
||||
|
||||
& svg {
|
||||
fill: #E54B4B;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-test-button {
|
||||
display: none;
|
||||
padding: 4px 10px;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
&-meter-mic {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 14px;
|
||||
}
|
||||
|
||||
&-checkbox-container {
|
||||
padding: 10px 16px;
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,8 @@ body {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
overflow: hidden;
|
||||
color: #F1F1F1;
|
||||
background: #040404; // should match DEFAULT_BACKGROUND from interface_config
|
||||
color: $defaultColor;
|
||||
background: $defaultBackground;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,6 +66,10 @@ body, input, textarea, keygen, select, button {
|
||||
font-family: $baseFontFamily !important;
|
||||
}
|
||||
|
||||
#nowebrtc {
|
||||
display:none;
|
||||
}
|
||||
|
||||
button, input, select, textarea {
|
||||
margin: 0;
|
||||
vertical-align: baseline;
|
||||
@@ -90,7 +94,7 @@ input[type='text'], input[type='password'], textarea {
|
||||
|
||||
button {
|
||||
color: #FFF;
|
||||
background-color: #44A5FF;
|
||||
background-color: $buttonBackground;
|
||||
border-radius: $borderRadius;
|
||||
|
||||
&.no-icon {
|
||||
@@ -111,22 +115,21 @@ form {
|
||||
height: $watermarkHeight;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
z-index: $watermarkZ;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
.leftwatermark {
|
||||
max-width: 140px;
|
||||
max-height:70px;
|
||||
left: 32px;
|
||||
top: 32px;
|
||||
background-position: center left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
&.no-margin {
|
||||
left:0;
|
||||
top:0;
|
||||
}
|
||||
.leftwatermarknomargin {
|
||||
background-position: center left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.rightwatermark {
|
||||
@@ -142,7 +145,22 @@ form {
|
||||
font-size: 11pt;
|
||||
color: rgba(255,255,255,.50);
|
||||
text-decoration: none;
|
||||
z-index: $watermarkZ;
|
||||
z-index: $poweredByZ;
|
||||
}
|
||||
|
||||
.connected {
|
||||
color: #21B9FC;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.lastN, .disconnected {
|
||||
color: #a3a3a3;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#inviteLinkRef {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,7 +185,7 @@ form {
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #3D3D3D;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -175,16 +193,3 @@ form {
|
||||
.jitsi-icon svg path {
|
||||
fill: inherit !important;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
border: 0 !important;
|
||||
clip: rect(1px, 1px, 1px, 1px) !important;
|
||||
clip-path: inset(50%) !important;
|
||||
height: 1px !important;
|
||||
margin: -1px !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
191
css/_chat.scss
191
css/_chat.scss
@@ -1,8 +1,37 @@
|
||||
@use './variables';
|
||||
#sideToolbarContainer {
|
||||
background-color: $chatBackgroundColor;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: width .16s ease-in-out;
|
||||
width: $sidebarWidth;
|
||||
z-index: $sideToolbarContainerZ;
|
||||
|
||||
@media (max-width: 580px) {
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// extract header + tabs height
|
||||
height: calc(100% - 119px);
|
||||
}
|
||||
|
||||
.chat-panel-no-tabs {
|
||||
// extract header height
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
|
||||
#chat-conversation-container {
|
||||
// extract message input height
|
||||
box-sizing: border-box;
|
||||
height: calc(100% - 64px);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@@ -12,7 +41,7 @@
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
font-size: 10pt;
|
||||
height: calc(100% - 10px);
|
||||
height: 100%;
|
||||
line-height: 20px;
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
@@ -22,13 +51,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.focus-visible {
|
||||
outline: 0;
|
||||
margin: 4px;
|
||||
border-radius: 0 0 variables.$borderRadius variables.$borderRadius;
|
||||
box-shadow: 0px 0px 0px 2px #4687ed; // focus01/primary07
|
||||
}
|
||||
|
||||
& > :first-child {
|
||||
margin-top: auto;
|
||||
}
|
||||
@@ -54,6 +76,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.chat-header {
|
||||
height: 70px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
|
||||
.jitsi-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input-container {
|
||||
padding: 0 16px 24px;
|
||||
}
|
||||
@@ -69,6 +112,61 @@
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.smiley-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
margin: 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#chat-input .smiley-button {
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background-color: #484A4F;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
color: #B8C7E0;
|
||||
}
|
||||
|
||||
.usrmsg-form {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
-ms-overflow-style: none;
|
||||
border: 0px none;
|
||||
border-radius:0;
|
||||
box-shadow: none;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
resize: none;
|
||||
scrollbar-width: none;
|
||||
width: 100%;
|
||||
word-break: break-word;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#usermsg:hover {
|
||||
border: 0px none;
|
||||
box-shadow: none;
|
||||
}
|
||||
#usermsg:focus,
|
||||
#usermsg:active {
|
||||
border-bottom: 1px solid white;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
#nickname {
|
||||
text-align: center;
|
||||
color: #9d9d9d;
|
||||
@@ -76,6 +174,11 @@
|
||||
margin: auto 0;
|
||||
padding: 0 16px;
|
||||
|
||||
#nickname-title {
|
||||
margin-bottom: 5px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
label[for="nickinput"] {
|
||||
> div > span {
|
||||
color: #B8C7E0;
|
||||
@@ -88,6 +191,24 @@
|
||||
label {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.enter-chat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
height: 40px;
|
||||
background: #1B67EC;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
color: #AFB6BC;
|
||||
background: #11336E;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-browser {
|
||||
@@ -95,6 +216,14 @@
|
||||
input {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.enter-chat {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.chatmessage .usermessage {
|
||||
@@ -102,7 +231,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sideToolbarContainer {
|
||||
* {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
border: 0 !important;
|
||||
clip: rect(1px, 1px, 1px, 1px) !important;
|
||||
clip-path: inset(50%) !important;
|
||||
height: 1px !important;
|
||||
margin: -1px !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
&.localuser {
|
||||
background-color: $chatLocalMessageBackgroundColor;
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-radius: 0px;
|
||||
|
||||
@@ -112,7 +266,7 @@
|
||||
}
|
||||
|
||||
.usermessage {
|
||||
color: #ffffff;
|
||||
color: red;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -134,6 +288,13 @@
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#smileysarea {
|
||||
display: flex;
|
||||
max-height: 150px;
|
||||
min-height: 35px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.smiley-input {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
@@ -163,7 +324,7 @@
|
||||
|
||||
#smileysContainer {
|
||||
background-color: $chatBackgroundColor;
|
||||
border-top: 1px solid #A4B8D1;
|
||||
border-top: 1px solid $chatInputSeparatorColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,11 +340,15 @@
|
||||
}
|
||||
|
||||
.smileyContainer:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
background-color: $newToolbarButtonToggleColor;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#usermsg::-webkit-scrollbar-track-piece {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
|
||||
.chat-message-group {
|
||||
&.local {
|
||||
align-items: flex-end;
|
||||
|
||||
75
css/_country-picker.scss
Normal file
75
css/_country-picker.scss
Normal file
@@ -0,0 +1,75 @@
|
||||
.cpick {
|
||||
border: 1px solid #A4B8D1;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
font-size: 15px;
|
||||
height: 38px;
|
||||
line-height: 24px;
|
||||
|
||||
&-selector {
|
||||
align-items: center;
|
||||
background-color: #283447;
|
||||
border-right: 1px solid #A4B8D1;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 8px 10px;
|
||||
position: relative;
|
||||
width: 88px;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
margin-right: 8px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 12px;
|
||||
|
||||
& > svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&-input {
|
||||
padding: 8px;
|
||||
background: #1C2025;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
caret-color: #0376DA;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&-dropdown {
|
||||
height: 190px;
|
||||
overflow-y: auto;
|
||||
width: 343px;
|
||||
}
|
||||
|
||||
&-dropdown-entry {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: #66768b;
|
||||
}
|
||||
|
||||
&-text {
|
||||
color: #fff;
|
||||
flex-grow: 1;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Override @Atlaskit/inline-dialog styles
|
||||
.cpick-container > div:nth-child(2) {
|
||||
outline: none;
|
||||
padding: 8px 0 0 0;
|
||||
}
|
||||
89
css/_drawer.scss
Normal file
89
css/_drawer.scss
Normal file
@@ -0,0 +1,89 @@
|
||||
.drawer-portal {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: $drawerZ;
|
||||
border-radius: 16px 16px 0 0;
|
||||
|
||||
&.notification-portal {
|
||||
z-index: $dropdownZ;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-portal::after {
|
||||
content: '';
|
||||
background-color: $participantsPaneBgColor;
|
||||
margin-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
.drawer-menu-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.drawer-menu {
|
||||
overflow-y: auto;
|
||||
margin-bottom: env(safe-area-inset-bottom, 0);
|
||||
width: 100%;
|
||||
|
||||
.drawer-toggle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
fill: none;
|
||||
}
|
||||
}
|
||||
|
||||
&#{&} .overflow-menu {
|
||||
margin: auto;
|
||||
font-size: 1.2em;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
height: calc(80vh - 144px - 64px);
|
||||
overflow-y: auto;
|
||||
|
||||
.overflow-menu-item {
|
||||
box-sizing: border-box;
|
||||
height: 48px;
|
||||
padding: 12px 16px;
|
||||
|
||||
align-items: center;
|
||||
color: $overflowMenuItemColor;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-text {
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
css/_e2ee.scss
Normal file
21
css/_e2ee.scss
Normal file
@@ -0,0 +1,21 @@
|
||||
#e2ee-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
font-size: 13px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.control-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-top: 15px;
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,20 +3,20 @@
|
||||
@include border-radius(4px);
|
||||
padding: 40px 38px 44px;
|
||||
color: #fff;
|
||||
background: lighten(#474747, 20%);
|
||||
background: $inlayColorBg;
|
||||
text-align: center;
|
||||
|
||||
&__title {
|
||||
margin: 17px 0;
|
||||
padding-bottom: 17px;
|
||||
color: #ffffff;
|
||||
color: $popoverFontColor;
|
||||
font-size: 21px;
|
||||
letter-spacing: 0.3px;
|
||||
border-bottom: 1px solid lighten(#FFFFFF, 10%);
|
||||
border-bottom: 1px solid $inlayBorderColor;
|
||||
}
|
||||
|
||||
&__text {
|
||||
color: #ffffff;
|
||||
color: $popoverFontColor;
|
||||
display: block;
|
||||
margin-top: 22px;
|
||||
font-size: 16px;
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
/*Initialize*/
|
||||
div.loginmenu {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
top: 40px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
a.disabled {
|
||||
color: gray !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loginmenu.extendedToolbarPopup {
|
||||
top: 20px;
|
||||
left: 40px;
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
|
||||
.meetings-list-empty {
|
||||
text-align: center;
|
||||
@@ -19,11 +19,11 @@
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
color: #2f3237;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin-bottom: 16px;
|
||||
max-width: 436px;
|
||||
color: #2f3237;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin-bottom: 16px;
|
||||
max-width: 436px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
}
|
||||
|
||||
.left-column {
|
||||
order: -1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 0;
|
||||
@@ -93,7 +92,6 @@
|
||||
.right-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex-grow: 1;
|
||||
padding-left: 16px;
|
||||
padding-top: 13px;
|
||||
@@ -101,11 +99,11 @@
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #5E6D7A;
|
||||
@@ -156,30 +154,14 @@
|
||||
margin-right: 16px;
|
||||
position: absolute;
|
||||
|
||||
&>svg {
|
||||
&> svg {
|
||||
fill: #0074e0;
|
||||
}
|
||||
}
|
||||
|
||||
.item:hover,
|
||||
.item:focus,
|
||||
.item:focus-within {
|
||||
.item:hover, .item:focus, .item:focus-within {
|
||||
.delete-meeting {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.delete-meeting:hover {
|
||||
&>svg {
|
||||
fill: #4687ED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) { /* Targets iPads and smaller devices */
|
||||
.item {
|
||||
.delete-meeting {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.filmstrip-toolbox,
|
||||
.always-on-top-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border-radius: 3px;
|
||||
@@ -28,3 +29,7 @@
|
||||
transform: translateX(-50%);
|
||||
padding: 3px !important;
|
||||
}
|
||||
|
||||
.filmstrip-toolbox {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
29
css/_modaldialog.scss
Normal file
29
css/_modaldialog.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
.jqistates {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.jqistates h2 {
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 18px;
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
color: #424242;
|
||||
}
|
||||
|
||||
.jqistates input {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.jqistates input[type='text'], input[type='password'] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button.jqidefaultbutton #inviteLinkRef {
|
||||
color: #2c8ad2;
|
||||
}
|
||||
|
||||
#inviteLinkRef {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
@@ -75,3 +75,6 @@
|
||||
margin-bottom: 36px;
|
||||
width: 100%;
|
||||
}
|
||||
.navigate-section-list-empty {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
15
css/_notice.scss
Normal file
15
css/_notice.scss
Normal file
@@ -0,0 +1,15 @@
|
||||
.notice {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
z-index: $zindex3;
|
||||
margin-top: 6px;
|
||||
|
||||
@include transform(translateX(-50%));
|
||||
|
||||
&__message {
|
||||
background-color: #000000;
|
||||
color: white;
|
||||
padding: 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
23
css/_notifications.scss
Normal file
23
css/_notifications.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
.notification-appear, .notification-enter {
|
||||
opacity: 0;
|
||||
position: relative;
|
||||
left: -200px;
|
||||
transition: all .2s !important; // !important needed to overwrite atlaskit default style
|
||||
|
||||
&-active {
|
||||
opacity: 1;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-exit {
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
left: 0;
|
||||
transition: all .2s !important; // !important needed to overwrite atlaskit default style
|
||||
|
||||
&-active {
|
||||
opacity: 0;
|
||||
left: -200px;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,42 @@
|
||||
.participants_pane {
|
||||
background-color: $participantsPaneBgColor;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: width .16s ease-in-out;
|
||||
width: 315px;
|
||||
z-index: $zindex0;
|
||||
}
|
||||
|
||||
.participants_pane-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-weight: 600;
|
||||
height: 100%;
|
||||
width: 315px;
|
||||
|
||||
& > *:first-child,
|
||||
& > *:last-child {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 580px) {
|
||||
.participants_pane {
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.participants_pane-content {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.jitsi-icon {
|
||||
&-dominant-speaker {
|
||||
background-color: #1EC26A;
|
||||
|
||||
3
css/_polls.scss
Normal file
3
css/_polls.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
#polls-panel {
|
||||
height: calc(100% - 119px);
|
||||
}
|
||||
@@ -1,33 +1,49 @@
|
||||
/**
|
||||
* Mousemove padding styles are used to add invisible elements to the popover
|
||||
* to allow mouse movement from the popover trigger to the popover itself
|
||||
* without triggering a mouseleave event.
|
||||
*/
|
||||
%vertical-popover-padding {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 20px;
|
||||
padding: 20px 0;
|
||||
top: -20px;
|
||||
}
|
||||
|
||||
%horizontal-popover-padding {
|
||||
height: 25px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
padding: 0 35px;
|
||||
left: -35px;
|
||||
}
|
||||
|
||||
.popover-mousemove-padding-left {
|
||||
@extend %vertical-popover-padding;
|
||||
left: -35px;
|
||||
}
|
||||
|
||||
.popover-mousemove-padding-right {
|
||||
@extend %vertical-popover-padding;
|
||||
right: -35px;
|
||||
}
|
||||
|
||||
.popover-mousemove-padding-bottom {
|
||||
@extend %horizontal-popover-padding;
|
||||
bottom: -40px;
|
||||
}
|
||||
|
||||
.popover-mousemove-padding-top {
|
||||
@extend %horizontal-popover-padding;
|
||||
top: -40px;
|
||||
}
|
||||
|
||||
.popover {
|
||||
z-index: 8;
|
||||
|
||||
.popover-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&.hover {
|
||||
margin: -16px -24px;
|
||||
|
||||
.popover-content {
|
||||
margin: 16px 24px;
|
||||
|
||||
&.top {
|
||||
bottom: 8px;
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
&.left {
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
&.right {
|
||||
left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
margin: -16px -24px;
|
||||
z-index: $popoverZ;
|
||||
}
|
||||
|
||||
.excalidraw .popover {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
@use 'sass:math';
|
||||
|
||||
.reactions-menu {
|
||||
width: 330px;
|
||||
background: #242528;
|
||||
width: 280px;
|
||||
background: $menuBG;
|
||||
box-shadow: 0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 6px;
|
||||
border-radius: 3px;
|
||||
padding: 16px;
|
||||
|
||||
&.with-gif {
|
||||
width: 380px;
|
||||
width: 328px;
|
||||
|
||||
.reactions-row .toolbox-button:last-of-type {
|
||||
top: 3px;
|
||||
@@ -104,7 +104,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.reactions-animations-overflow-container {
|
||||
.reactions-menu-container {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.reactions-animations-container {
|
||||
position: absolute;
|
||||
width: 20%;
|
||||
bottom: 0;
|
||||
@@ -117,13 +121,6 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.reactions-animations-container {
|
||||
left: 50%;
|
||||
bottom: 0px;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
$reactionCount: 20;
|
||||
|
||||
@function random($min, $max) {
|
||||
@@ -161,17 +158,17 @@ $reactionCount: 20;
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translate(40px, -70dvh) scale(1.5);
|
||||
transform: translate(40px, -70vh) scale(1.5);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translate(40px, -70dvh) scale(1.5);
|
||||
transform: translate(40px, -70vh) scale(1.5);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(140px, -50dvh) scale(1);
|
||||
transform: translate(140px, -50vh) scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -194,17 +191,17 @@ $reactionCount: 20;
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translate(#{$topX}px, -#{$topY}dvh) scale(1.5);
|
||||
transform: translate(#{$topX}px, -#{$topY}vh) scale(1.5);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translate(#{$topX}px, -#{$topY}dvh) scale(1.5);
|
||||
transform: translate(#{$topX}px, -#{$topY}vh) scale(1.5);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(#{$bottomX}px, -#{$bottomY}dvh) scale(1);
|
||||
transform: translate(#{$bottomX}px, -#{$bottomY}vh) scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
.recordingSpinner {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.recording-dialog {
|
||||
flex: 0;
|
||||
flex-direction: column;
|
||||
@@ -15,10 +19,6 @@
|
||||
font-size: 14px;
|
||||
margin-left: 16px;
|
||||
max-width: 70%;
|
||||
|
||||
&-no-space {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.space-top {
|
||||
@@ -50,6 +50,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.recording-switch-disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.recording-icon-container {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -152,7 +156,8 @@
|
||||
*/
|
||||
font-size: 14px;
|
||||
|
||||
.broadcast-dropdown {
|
||||
.broadcast-dropdown,
|
||||
.broadcast-dropdown-trigger {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -181,7 +186,7 @@
|
||||
}
|
||||
|
||||
.google-error {
|
||||
color: #c61600;
|
||||
color: $errorColor;
|
||||
}
|
||||
|
||||
.google-panel {
|
||||
@@ -192,6 +197,14 @@
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.helper-link {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
color:#FFD740;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
font-size: 24px;
|
||||
|
||||
.thanks-msg {
|
||||
border-bottom: 1px solid #FFFFFF;
|
||||
border-bottom: 1px solid $selectBg;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
p {
|
||||
@@ -14,7 +14,7 @@
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
.hint-msg {
|
||||
.hint-msg{
|
||||
p {
|
||||
margin: 26px auto;
|
||||
font-weight: 600;
|
||||
@@ -28,13 +28,7 @@
|
||||
width: 120px;
|
||||
height: 86px;
|
||||
margin: 0 auto;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
.forbidden-msg {
|
||||
p {
|
||||
font-size: 16px;
|
||||
margin-top: 15px;
|
||||
background: $happySoftwareBackground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,33 +3,30 @@
|
||||
display: block;
|
||||
|
||||
#enter_room {
|
||||
position: relative;
|
||||
height: 42px;
|
||||
|
||||
.welcome-page-button {
|
||||
font-size: 16px;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 68px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: #002637;
|
||||
|
||||
.insecure-room-name-warning {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#enter_room {
|
||||
width: 100%;
|
||||
|
||||
.join-meeting-container {
|
||||
padding: 0;
|
||||
flex-direction: column;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.enter-room-input-container {
|
||||
padding-right: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.warning-without-link,
|
||||
.warning-with-link {
|
||||
top: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,10 +50,7 @@
|
||||
}
|
||||
|
||||
.welcome-footer-row-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap:12px;
|
||||
align-items: center;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,20 +34,20 @@
|
||||
background: #F2F3F4;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
|
||||
|
||||
& svg {
|
||||
& > svg {
|
||||
fill: #040404;
|
||||
}
|
||||
|
||||
&.settings-button-small-icon--disabled {
|
||||
background: #36383C;
|
||||
|
||||
& svg {
|
||||
&> svg {
|
||||
fill: #929292;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& svg {
|
||||
& > svg {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
background-color: #36383c;
|
||||
cursor: default;
|
||||
|
||||
& svg {
|
||||
&> svg {
|
||||
fill: #929292;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.subject-info {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: 4px;
|
||||
max-width: 80%;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.details-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@@ -41,6 +49,7 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 48px;
|
||||
max-width: calc(100% - 24px);
|
||||
}
|
||||
|
||||
@keyframes hideSubject {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user