Compare commits

..

1 Commits

Author SHA1 Message Date
Calin-Teodor
e049cfc85a chore(rn, versions): bump app and SDK versions 2024-07-31 18:12:33 +03:00
1487 changed files with 26020 additions and 72934 deletions

View File

@@ -14,12 +14,3 @@ trim_trailing_whitespace = false
[Makefile]
indent_style = tab
[*.{java,kt}]
indent_size = 4
[*.xml]
indent_size = 2
[*.{swift,m,mm,h}]
indent_size = 4

View File

@@ -17,8 +17,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

View File

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

View File

@@ -30,10 +30,12 @@ body:
- label: Android mobile app
- label: iOS mobile app
- label: Custom app using a mobile SDK
validations:
required: true
- type: input
attributes:
label: Browser / app / sdk version
description: Please provide the version of the browser / app / sdk where the problem manifests.
description: Please provice the version of the browser / app / sdk where the problem manifests.
validations:
required: true
- type: textarea

View File

@@ -18,7 +18,7 @@ jobs:
npm -v
- name: Get changed files
id: changed-files
uses: jitsi/changed-files@main
uses: tj-actions/changed-files@v41
- 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"
@@ -34,13 +34,9 @@ jobs:
- 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]
linux-build:
name: Build Frontend (Linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -53,11 +49,20 @@ jobs:
npm -v
- run: npm install
- run: make
- name: Check config.js syntax
run: node config.js
android-rn-bundle-build:
macos-ci:
name: Build Frontend (macOS)
runs-on: macOS-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm install
- run: make
android-build:
name: Build mobile bundle (Android)
runs-on: macos-15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -70,9 +75,13 @@ jobs:
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:
ios-build:
name: Build mobile bundle (iOS)
runs-on: macos-15
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-13, macos-14]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -88,87 +97,20 @@ jobs:
run: |
uname -a
xcode-select -p
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
xcodebuild -version
- name: setup-cocoapods
uses: ruby/setup-ruby@v1
uses: maxim-lobanov/setup-cocoapods@v1
with:
ruby-version: '3.4'
bundler-cache: true
- run: npx react-native info
podfile-path: ios/Podfile.lock
- name: Install Pods
working-directory: ./ios
run: bundle exec pod install --repo-update --deployment
run: |
pod --version
cd ios
pod install --repo-update
- 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: 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:v18.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: clean Xcode
run: |
rm -rf ios/sdk/out
xcodebuild clean \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeetSDK
- 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 -downloadPlatform iOS -buildVersion 18.2
xcodebuild archive \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeetSDK \
-configuration Release \
-sdk iphoneos \
-destination 'generic/platform=iOS' \
-archivePath ios/sdk/out/ios-device \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
xcodebuild -create-xcframework \
-framework ios/sdk/out/ios-device.xcarchive/Products/Library/Frameworks/JitsiMeetSDK.framework \
-output ios/sdk/out/JitsiMeetSDK.xcframework
- run: ls -lR ios/sdk/out
debian-build:
name: Test Debian packages build
runs-on: ubuntu-latest

10
.gitignore vendored
View File

@@ -38,7 +38,6 @@ DerivedData
*.dSYM.zip
*.xcuserstate
project.xcworkspace
**/.xcode.env.local
# Android/IntelliJ
#
@@ -62,7 +61,6 @@ buck-out/
# fastlane
#
.bundle/
**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/test_output
@@ -102,9 +100,6 @@ tsconfig.json
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
@@ -113,8 +108,3 @@ react-native-sdk/node_modules
react-native-sdk/react
react-native-sdk/service
react-native-sdk/sounds
# tests
tests/.env
test-results

2
.nvmrc
View File

@@ -1 +1 @@
22
20

267
CLAUDE.md
View File

@@ -1,267 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
### Building and Development
- `npm run lint-fix` - Automatically fix linting issues
- `npm run tsc:ci` - Run TypeScript checks for both web and native platforms
- `npm run tsc:web` - TypeScript check for web platform only
- `npm run tsc:native` - TypeScript check for native platform only
- `npm run lint:ci` - Run ESLint without type checking
- `make dev` - Start development server with webpack-dev-server
- `make compile` - Build production bundles
- `make clean` - Clean build directory
- `make all` - Full build (compile + deploy)
### Testing
- `npm test` - Run full test suite using WebDriverIO
- `npm run test-single -- <spec-file>` - Run single test file
- `npm run test-dev` - Run tests against development environment
- `npm run test-dev-single -- <spec-file>` - Run single test in dev mode
### Language Tools
- `npm run lang-sort` - Sort language files
- `npm run lint:lang` - Validate JSON language files
### Platform-Specific TypeScript
TypeScript configuration is split between web and native platforms with separate tsconfig files.
## Architecture Overview
### Multi-Platform Structure
Jitsi Meet supports both web and React Native platforms with platform-specific file extensions and directories:
- `.web.ts/.web.tsx` - Web-specific implementations
- `.native.ts/.native.tsx` - React Native-specific implementations
- `.any.ts/.any.tsx` - Shared cross-platform code
- `.android.ts/.android.tsx` - Android-specific code
- `.ios.ts/.ios.tsx` - iOS-specific code
- `web/` directories - Web-specific components and modules
- `native/` directories - React Native-specific components and modules
- `react/features/mobile/` - Native-only features
### Core Directories
- `react/features/` - Main application features organized by domain (83+ feature modules)
- `modules/` - Legacy JavaScript modules and APIs
- `css/` - SCSS stylesheets compiled to CSS
- `libs/` - Compiled output directory for JavaScript bundles
- `static/` - Static assets and HTML files
- `tests/` - WebDriverIO end-to-end tests
### Feature-Driven Architecture
The application is organized under `react/features/` with each feature containing:
- **`actionTypes.ts`** - Redux action type constants
- **`actions.ts`** - Redux action creators (platform-specific variants with `.any.ts`, `.web.ts`, `.native.ts`)
- **`reducer.ts`** - Redux reducer functions
- **`middleware.ts`** - Redux middleware for side effects
- **`functions.ts`** - Utility functions and selectors
- **`constants.ts`** - Feature-specific constants
- **`logger.ts`** - Feature-specific logger instance
- **`types.ts`** - TypeScript type definitions
### Key Application Files
- `app.js` - Main web application entry point
- `webpack.config.js` - Multi-bundle Webpack configuration
- `Makefile` - Build system for development and production
- `package.json` - Dependencies and scripts with version requirements
### Bundle Architecture
The application builds multiple bundles:
- `app.bundle.js` / `app.bundle.min.js` - Main application bundle (entry: `./app.js`)
- `external_api.js` / `external_api.min.js` - External API for embedders (entry: `./modules/API/external/index.js`)
- `alwaysontop.js` / `alwaysontop.min.js` - Always-on-top window functionality (entry: `./react/features/always-on-top/index.tsx`)
- `close3.js` / `close3.min.js` - Close3 functionality (entry: `./static/close3.js`)
- `face-landmarks-worker.js` / `face-landmarks-worker.min.js` - Face landmarks detection worker (entry: `./react/features/face-landmarks/faceLandmarksWorker.ts`)
- `noise-suppressor-worklet.js` / `noise-suppressor-worklet.min.js` - Audio noise suppression worklet (entry: `./react/features/stream-effects/noise-suppression/NoiseSuppressorWorklet.ts`)
- `screenshot-capture-worker.js` / `screenshot-capture-worker.min.js` - Screenshot capture worker (entry: `./react/features/screenshot-capture/worker.ts`)
### Redux Architecture
Features follow a Redux-based architecture with:
- Actions, reducers, and middleware in each feature directory
- Cross-platform state management
- Modular feature organization with clear boundaries
The codebase uses a registry-based Redux architecture:
- **ReducerRegistry** - Features register their reducers independently
- **MiddlewareRegistry** - Features register middleware without cross-dependencies
- **IReduxState** - Global state is strongly typed with 80+ feature states
### Dependencies
- Uses `lib-jitsi-meet` as the core WebRTC library
- React with TypeScript support
- React Native for mobile applications
- Webpack for bundling with development server
### TypeScript Configuration
- `tsconfig.web.json` - Web platform TypeScript config (excludes native files)
- `tsconfig.native.json` - React Native TypeScript config (excludes web files)
- Strict TypeScript settings with ES2024 target
- Platform-specific module suffixes (`.web`, `.native`)
### Key Base Features
- **`base/app/`** - Application lifecycle management
- **`base/conference/`** - Core conference logic
- **`base/tracks/`** - Media track management
- **`base/participants/`** - Participant management
- **`base/config/`** - Configuration management
- **`base/redux/`** - Redux infrastructure
### Component Patterns
- **Abstract Components** - Base classes for cross-platform components
- **Platform-Specific Components** - Separate implementations in `web/` and `native/` directories
- **Hook-based patterns** - Modern React patterns for component logic
### Testing Framework
- WebDriverIO for end-to-end testing
- Test files are located in `tests/specs/` and use page objects in `tests/pageobjects/`.
- Environment configuration via `.env` files
- Support for Chrome, Firefox, and grid testing
## Development Guidelines
### Adding New Features
1. Create feature directory under `react/features/[feature-name]/`
2. Follow the standard file structure (actionTypes, actions, reducer, etc.)
3. Register reducers and middleware using the registry pattern
4. Define TypeScript interfaces for state and props
5. Use platform-specific files for web/native differences
6. Add feature-specific logger for debugging
### Working with Existing Features
1. Check for existing `.any.ts`, `.web.ts`, `.native.ts` variants
2. Follow established action-reducer-middleware patterns
3. Use existing base utilities rather than creating new ones
4. Leverage abstract components for cross-platform logic
5. Maintain type safety across the entire state tree
### Testing
The project uses WebDriver (WebdriverIO) for end-to-end testing. Test files are located in `tests/specs/` and use page objects in `tests/pageobjects/`.
### Build System
- **Webpack** - Main build system for web bundles
- **Makefile** - Coordinates build process and asset deployment
- **Metro** - React Native bundler (configured in `metro.config.js`)
### Platform-Specific Notes
- Web builds exclude files matching `**/native/*`, `**/*.native.ts`, etc.
- Native builds exclude files matching `**/web/*`, `**/*.web.ts`, etc.
- Use `moduleSuffixes` in TypeScript config to handle platform-specific imports
- Check `tsconfig.web.json` and `tsconfig.native.json` for platform-specific exclusions
## Environment and Setup Requirements
### System Requirements
- **Node.js and npm** are required
- Development server runs at https://localhost:8080/
- Certificate errors in development are expected (self-signed certificates)
### Development Workflow
- Development server proxies to configurable target (default: https://alpha.jitsi.net)
- Hot module replacement enabled for development
- Bundle analysis available via `ANALYZE_BUNDLE=true` environment variable
- Circular dependency detection via `DETECT_CIRCULAR_DEPS=true`
## Code Quality Requirements
- All code must pass `npm run lint:ci` and `npm run tsc:ci` with 0 warnings before committing
- TypeScript strict mode enabled - avoid `any` type
- ESLint config extends `@jitsi/eslint-config`
- Prefer TypeScript for new features, convert existing JavaScript when possible
## Code Style and Standards
### Conventional Commits Format
Follow [Conventional Commits](https://www.conventionalcommits.org) with **mandatory scopes**:
```
feat(feature-name): description
fix(feature-name): description
docs(section): description
```
Available types: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test
### Feature Layout Structure
When adding new features:
```
react/features/sample/
├── actionTypes.ts
├── actions.ts
├── components/
│ ├── AnotherComponent.tsx
│ └── OneComponent.tsx
├── middleware.ts
└── reducer.ts
```
### TypeScript Requirements
- All new features must be written in TypeScript
- Convert JavaScript to TypeScript when modifying existing code
- Import middleware in `react/features/app/middlewares.{any,native,web}.js`
- Import reducers in appropriate registry files
- Avoid `index` files
### Bundle Size Management
- Bundle size limits are enforced to prevent bloat
- For increases, analyze first: `npx webpack -p --analyze-bundle`
- Open analyzer: `npx webpack-bundle-analyzer build/app-stats.json`
- Justify any dependency additions that increase bundle size
## Testing and Quality Assurance
### Tests
- End-to-end tests are defined in the tests/
- Tests run automatically for project member PRs via Jenkins
- Tests cover peer-to-peer, invites, iOS, Android, and web platforms
- Beta testing available at https://beta.meet.jit.si/
### Manual Testing Checklist
- Test with 2 participants (P2P mode)
- Test with 3+ participants (JVB mode)
- Verify audio/video in both modes
- Test mobile apps if changes affect mobile
- Check that TLS certificate chain is complete for mobile app compatibility
## Common Issues and Debugging
### P2P vs JVB Problems
- **Works with 2 participants, fails with 3+**: JVB/firewall issue, check UDP 10000
- **Works on web, fails on mobile apps**: TLS certificate chain issue, need fullchain.pem
- Use the tests from tests/ directory to verify functionality across platforms
### Development Server Issues
- Certificate warnings are normal for development (self-signed)
- Use different backend with WEBPACK_DEV_SERVER_PROXY_TARGET environment variable
- Check firewall settings if local development fails
### Configuration and Customization
- Extensive configuration options documented in handbook
- See `config.js` for client-side options
- Options marked 🚫 are not overwritable through `configOverwrite`
- Reference [Configuration Guide](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-configuration) for details
## Architecture Deep Dive
### Core Application Files
- **`./conference.js`** - Foundation for user-conference interactions (connection, joining, muting)
- **`./modules/external-api`** - External API for iframe integration and events
- **`./lang/`** - Translations in `main-[language].json` files
- **`./css/`** - SCSS files organized by features, matching React feature structure
### State Management Flow
1. Actions dispatched from components
2. Middleware processes side effects
3. Reducers update state
4. Components re-render based on state changes
5. Registry pattern keeps features decoupled
### Cross-Platform Strategy
- Abstract components handle shared logic
- Platform files (.web.ts, .native.ts) handle platform differences
- Build system excludes irrelevant platform files
- TypeScript configs ensure proper platform targeting
## External Resources
- [Jitsi Handbook](https://jitsi.github.io/handbook/) - Comprehensive documentation
- [Community Forum](https://community.jitsi.org/) - Ask questions and get support
- [Architecture Guide](https://jitsi.github.io/handbook/docs/architecture) - System overview
- [Contributing Guidelines](https://jitsi.github.io/handbook/docs/dev-guide/contributing) - Detailed contribution process

View File

@@ -1,20 +1,14 @@
# Follow Our Updated Guide to See How You Can Contribute
**Hello there! 👋**
Hello there! 👋
We're thrilled that you're eager to contribute to **Jitsi Meet! ❤️**
We're thrilled that you're eager to contribute to Jitsi Meet! ❤️
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.
### 📖 Get Started
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!
### 💬 Join the Discussion
Have questions or need help? Join our community discussions on the [Jitsi Forum](https://community.jitsi.org/) where contributors and maintainers can assist you.
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!
### ❗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.
**Happy coding!**
Happy coding!

16
Gemfile
View File

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

View File

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

View File

@@ -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)
@@ -48,6 +48,8 @@ deploy-appbundle:
$(BUILD_DIR)/external_api.min.js.map \
$(BUILD_DIR)/alwaysontop.min.js \
$(BUILD_DIR)/alwaysontop.min.js.map \
$(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 \

View File

@@ -34,7 +34,7 @@ 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:

View File

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

View File

@@ -1 +0,0 @@
OK

View File

@@ -1,5 +1,4 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
// Crashlytics integration is done as part of Firebase now, so it gets
// automagically activated with google-services.json
@@ -16,6 +15,12 @@ def vcode = (int) (((new Date().getTime() / 1000) - 1546297200) / 10)
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
packagingOptions {
jniLibs {
excludes += ['lib/*/libhermes*.so']
}
}
defaultConfig {
applicationId 'org.jitsi.meet'
@@ -28,14 +33,6 @@ android {
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
externalNativeBuild {
cmake {
arguments "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DANDROID_STL=c++_shared"
cppFlags "-std=c++17"
cFlags "-DANDROID_PLATFORM=android-26"
}
}
}
signingConfigs {
@@ -51,11 +48,10 @@ android {
debug {
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
applicationIdSuffix ".debug"
}
release {
// Uncomment the following line for signing a test release build.
// signingConfig signingConfigs.debug
// Uncomment the following line for singing a test release build.
//signingConfig signingConfigs.debug
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-release.pro'
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
@@ -75,18 +71,9 @@ android {
}
compileOptions {
sourceCompatibility rootProject.ext.javaVersion
targetCompatibility rootProject.ext.javaVersion
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = rootProject.ext.jvmTargetVersion
}
kotlin {
jvmToolchain(rootProject.ext.jvmToolchainVersion)
}
namespace 'org.jitsi.meet'
}
@@ -101,8 +88,10 @@ dependencies {
// 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 project(':sdk')
@@ -110,6 +99,8 @@ dependencies {
gradle.projectsEvaluated {
// Dropbox integration
//
def dropboxAppKey
if (project.file('dropbox.key').exists()) {
dropboxAppKey = project.file('dropbox.key').text.trim() - 'db-'
@@ -180,6 +171,7 @@ gradle.projectsEvaluated {
packageTask.dependsOn(currentRunPackagerTask)
}
}
if (googleServicesEnabled) {

View File

@@ -4,7 +4,3 @@
-keepattributes *Annotation*
-keepattributes SourceFile,LineNumberTable
-keep public class * extends java.lang.Exception
# R8 missing classes - suppress warnings
-dontwarn com.facebook.memory.config.MemorySpikeConfig
-dontwarn kotlinx.parcelize.Parcelize

View File

@@ -96,3 +96,8 @@
# Rule to avoid build errors related to SVGs.
-keep public class com.horcrux.svg.** {*;}
# https://github.com/facebook/fresco/issues/2638
-keep public class com.facebook.imageutils.** {
public *;
}

View File

@@ -4,6 +4,7 @@ import android.net.Uri;
import android.util.Log;
import com.google.firebase.crashlytics.FirebaseCrashlytics;
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks;
import org.jitsi.meet.sdk.JitsiMeet;
import org.jitsi.meet.sdk.JitsiMeetActivity;
@@ -21,6 +22,18 @@ final class GoogleServicesHelper {
Log.d(activity.getClass().getSimpleName(), "Initializing Google Services");
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!JitsiMeet.isCrashReportingDisabled(activity));
FirebaseDynamicLinks.getInstance().getDynamicLink(activity.getIntent())
.addOnSuccessListener(activity, pendingDynamicLinkData -> {
Uri dynamicLink = null;
if (pendingDynamicLinkData != null) {
dynamicLink = pendingDynamicLinkData.getLink();
}
if (dynamicLink != null) {
activity.join(dynamicLink.toString());
}
});
}
}
}

View File

@@ -30,12 +30,9 @@ 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.URL;
@@ -82,10 +79,6 @@ public class MainActivity extends JitsiMeetActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
JitsiMeet.showSplashScreen(this);
WebRTCModuleOptions options = WebRTCModuleOptions.getInstance();
options.loggingSeverity = Logging.Severity.LS_ERROR;
super.onCreate(null);
}
@@ -218,6 +211,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

View File

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

View File

@@ -3,7 +3,6 @@
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:navigationBarColor">@color/navigationBarColor</item>
<item name="android:statusBarColor">@color/statusBarColor</item>
<item name="android:windowDisablePreview">true</item>
</style>
</resources>

View File

@@ -5,54 +5,52 @@ import org.gradle.util.VersionNumber
// sub-projects/modules.
buildscript {
ext {
kotlinVersion = "2.0.21"
gradlePluginVersion = "8.6.0"
buildToolsVersion = "35.0.0"
compileSdkVersion = 35
minSdkVersion = 26
targetSdkVersion = 35
supportLibVersion = "28.0.0"
ndkVersion = "27.1.12297006"
// The Maven artifact groupId of the third-party react-native modules which
// Jitsi Meet SDK for Android depends on and which are not available in
// third-party Maven repositories so we have to deploy to a Maven repository
// of ours.
moduleGroupId = 'com.facebook.react'
// Maven repo where artifacts will be published
mavenRepo = System.env.MVN_REPO ?: ""
mavenUser = System.env.MVN_USER ?: ""
mavenPassword = System.env.MVN_PASSWORD ?: ""
// Libre build
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild
//React Native and Hermes Version
rnVersion = "0.77.2"
// Java dependencies
javaVersion = JavaVersion.VERSION_17
jvmToolchainVersion = 17
jvmTargetVersion = '17'
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.kotlinVersion"
classpath "com.android.tools.build:gradle:$rootProject.ext.gradlePluginVersion"
classpath 'com.android.tools.build:gradle:7.4.2'
classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
}
}
ext {
kotlinVersion = "1.7.0"
buildToolsVersion = "33.0.2"
compileSdkVersion = 34
minSdkVersion = 24
targetSdkVersion = 34
supportLibVersion = "28.0.0"
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
// The Maven artifact groupId of the third-party react-native modules which
// Jitsi Meet SDK for Android depends on and which are not available in
// third-party Maven repositories so we have to deploy to a Maven repository
// of ours.
moduleGroupId = 'com.facebook.react'
// Maven repo where artifacts will be published
mavenRepo = System.env.MVN_REPO ?: ""
mavenUser = System.env.MVN_USER ?: ""
mavenPassword = System.env.MVN_PASSWORD ?: ""
// Libre build
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild
//React Native Version
rnVersion = "0.73.8"
}
allprojects {
repositories {
// Android JSC is installed from npm.
maven { url("$rootDir/../node_modules/jsc-android/dist") }
mavenCentral()
google()
maven { url 'https://www.jitpack.io' }
@@ -78,6 +76,7 @@ allprojects {
// 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'
@@ -120,7 +119,7 @@ allprojects {
project.version = "${json.version}-jitsi-${versionQualifierNumber}"
task jitsiAndroidSourcesJar(type: Jar) {
archiveClassifier = 'sources'
classifier = 'sources'
from android.sourceSets.main.java.source
}
@@ -171,46 +170,16 @@ allprojects {
}
}
// Force the version of the Android build tools we have chosen on all subprojects.
// Force the version of the Android build tools we have chosen on all
// subprojects. The forcing was introduced for react-native and the third-party
// modules that we utilize such as react-native-background-timer.
subprojects { subproject ->
afterEvaluate{
if ((subproject.plugins.hasPlugin('android')
|| subproject.plugins.hasPlugin('android-library'))
&& rootProject.ext.has('buildToolsVersion')) {
android {
buildToolsVersion rootProject.ext.buildToolsVersion
buildFeatures {
buildConfig true
}
// Set JVM target across all subprojects
compileOptions {
sourceCompatibility rootProject.ext.javaVersion
targetCompatibility rootProject.ext.javaVersion
}
// Disable lint errors for problematic third-party modules
// react-native-background-timer
// react-native-calendar-events
lint {
abortOnError = false
}
}
}
// Add Kotlin configuration for subprojects that use Kotlin
if (subproject.plugins.hasPlugin('kotlin-android')) {
subproject.kotlin {
jvmToolchain(rootProject.ext.jvmToolchainVersion)
}
// Set Kotlin JVM target
subproject.android {
kotlinOptions {
jvmTarget = rootProject.ext.jvmTargetVersion
}
}
}
}

View File

@@ -11,26 +11,20 @@
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx4048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# This one fixes a weird WebRTC runtime problem on some devices.
# https://github.com/jitsi/jitsi-meet/issues/7911#issuecomment-714323255
android.enableDexingArtifactTransform.desugaring=false
android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false
# Use this property to enable or disable the Hermes JS engine.
hermesEnabled=true
appVersion=99.0.0
sdkVersion=0.0.0
appVersion=24.3.0
sdkVersion=9.3.0

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,113 +0,0 @@
#!/bin/bash
progname="${0##*/}"
progname="${progname%.sh}"
# usage: check_elf_alignment.sh [path to *.so files|path to *.apk]
cleanup_trap() {
if [ -n "${tmp}" -a -d "${tmp}" ]; then
rm -rf ${tmp}
fi
exit $1
}
usage() {
echo "Host side script to check the ELF alignment of shared libraries."
echo "Shared libraries are reported ALIGNED when their ELF regions are"
echo "16 KB or 64 KB aligned. Otherwise they are reported as UNALIGNED."
echo
echo "Usage: ${progname} [input-path|input-APK|input-APEX]"
}
if [ ${#} -ne 1 ]; then
usage
exit
fi
case ${1} in
--help | -h | -\?)
usage
exit
;;
*)
dir="${1}"
;;
esac
if ! [ -f "${dir}" -o -d "${dir}" ]; then
echo "Invalid file: ${dir}" >&2
exit 1
fi
if [[ "${dir}" == *.apk ]]; then
trap 'cleanup_trap' EXIT
echo
echo "Recursively analyzing $dir"
echo
if { zipalign --help 2>&1 | grep -q "\-P <pagesize_kb>"; }; then
echo "=== APK zip-alignment ==="
zipalign -v -c -P 16 4 "${dir}" | egrep 'lib/arm64-v8a|lib/x86_64|Verification'
echo "========================="
else
echo "NOTICE: Zip alignment check requires build-tools version 35.0.0-rc3 or higher."
echo " You can install the latest build-tools by running the below command"
echo " and updating your \$PATH:"
echo
echo " sdkmanager \"build-tools;35.0.0-rc3\""
fi
dir_filename=$(basename "${dir}")
tmp=$(mktemp -d -t "${dir_filename%.apk}_out_XXXXX")
unzip "${dir}" lib/* -d "${tmp}" >/dev/null 2>&1
dir="${tmp}"
fi
if [[ "${dir}" == *.apex ]]; then
trap 'cleanup_trap' EXIT
echo
echo "Recursively analyzing $dir"
echo
dir_filename=$(basename "${dir}")
tmp=$(mktemp -d -t "${dir_filename%.apex}_out_XXXXX")
deapexer extract "${dir}" "${tmp}" || { echo "Failed to deapex." && exit 1; }
dir="${tmp}"
fi
RED="\e[31m"
GREEN="\e[32m"
ENDCOLOR="\e[0m"
unaligned_libs=()
echo
echo "=== ELF alignment ==="
matches="$(find "${dir}" -type f)"
IFS=$'\n'
for match in $matches; do
# We could recursively call this script or rewrite it to though.
[[ "${match}" == *".apk" ]] && echo "WARNING: doesn't recursively inspect .apk file: ${match}"
[[ "${match}" == *".apex" ]] && echo "WARNING: doesn't recursively inspect .apex file: ${match}"
[[ $(file "${match}") == *"ELF"* ]] || continue
res="$(objdump -p "${match}" | grep LOAD | awk '{ print $NF }' | head -1)"
if [[ $res =~ 2\*\*(1[4-9]|[2-9][0-9]|[1-9][0-9]{2,}) ]]; then
echo -e "${match}: ${GREEN}ALIGNED${ENDCOLOR} ($res)"
else
echo -e "${match}: ${RED}UNALIGNED${ENDCOLOR} ($res)"
unaligned_libs+=("${match}")
fi
done
if [ ${#unaligned_libs[@]} -gt 0 ]; then
echo -e "${RED}Found ${#unaligned_libs[@]} unaligned libs (only arm64-v8a/x86_64 libs need to be aligned).${ENDCOLOR}"
elif [ -n "${dir_filename}" ]; then
echo -e "ELF Verification Successful"
fi
echo "====================="

View File

@@ -9,6 +9,8 @@ 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}}
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 +24,34 @@ 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 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 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!"
@@ -38,12 +67,15 @@ pushd ${THIS_DIR}/../
./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!

View File

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

View File

@@ -8,7 +8,6 @@ android {
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
buildConfigField "String", "SDK_VERSION", "\"$sdkVersion\""
}
buildTypes {
@@ -42,19 +41,22 @@ dependencies {
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
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.squareup.duktape:duktape-android:1.3.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'androidx.startup:startup-runtime:1.1.0'
implementation 'com.google.j2objc:j2objc-annotations:3.0.0'
// Only add these packages if we are NOT doing a LIBRE_BUILD
if (!rootProject.ext.libreBuild) {
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'
}
@@ -73,6 +75,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-keep-awake')
implementation project(':react-native-orientation-locker')
implementation project(':react-native-pager-view')
@@ -81,14 +84,11 @@ dependencies {
implementation project(':react-native-screens')
implementation project(':react-native-slider')
implementation project(':react-native-sound')
implementation project(':react-native-splash-view')
implementation project(':react-native-splash-screen')
implementation project(':react-native-svg')
implementation project(':react-native-video')
implementation project(':react-native-webrtc')
implementation project(':react-native-webview')
implementation project(':react-native-worklets-core')
// Use `api` here so consumers can use WebRTCModuleOptions.
api project(':react-native-webrtc')
testImplementation 'junit:junit:4.12'
}
@@ -136,17 +136,10 @@ android.libraryVariants.all { def variant ->
def devEnabled = !targetName.toLowerCase().contains("release")
// Run the bundler
// Use full path to node to avoid PATH issues in Gradle
def nodePath = System.getenv('NVM_BIN') ? "${System.getenv('NVM_BIN')}/node" : "node"
// Debug: Print the node path and environment
println "Using node path: ${nodePath}"
println "NVM_BIN: ${System.getenv('NVM_BIN')}"
println "Working directory: ${reactRoot}"
commandLine(
nodePath,
"node_modules/react-native/scripts/bundle.js",
"node",
"node_modules/react-native/local-cli/cli.js",
"bundle",
"--platform", "android",
"--dev", "${devEnabled}",
"--reset-cache",
@@ -158,70 +151,6 @@ android.libraryVariants.all { def variant ->
enabled !devEnabled
}
// GRADLE REQUIREMENTS (Gradle 8.7+ / AGP 8.5.0+):
// This task requires explicit dependencies on resource tasks from all React Native modules
// due to Gradle's strict validation of task dependencies.
// Without these dependencies,
// builds will fail with errors like:
// "Task ':sdk:bundleReleaseJsAndAssets' uses the output of task ':react-native-amplitude:packageReleaseResources'
// without declaring a dependency on it."
// The automatic dependency resolution below ensures all required resource tasks are properly
// declared as dependencies before this task executes.
if (variant.name.toLowerCase().contains("release")) {
rootProject.subprojects.each { subproject ->
if (
subproject.name.startsWith("react-native-") ||
subproject.name.startsWith("@react-native-") ||
subproject.name.startsWith("@giphy/")
) {
[
"packageReleaseResources",
"generateReleaseResValues",
"generateReleaseResources",
"generateReleaseBuildConfig",
"processReleaseManifest",
"writeReleaseAarMetadata",
"generateReleaseRFile",
"compileReleaseLibraryResources",
"compileReleaseJavaWithJavac",
"javaPreCompileRelease",
"bundleLibCompileToJarRelease",
"exportReleaseConsumerProguardFiles",
"mergeReleaseGeneratedProguardFiles",
"mergeReleaseJniLibFolders",
"mergeReleaseShaders",
"packageReleaseAssets",
"processReleaseJavaRes",
"prepareReleaseArtProfile",
"copyReleaseJniLibsProjectOnly",
"extractDeepLinksRelease",
"createFullJarRelease",
"generateReleaseLintModel",
"writeReleaseLintModelMetadata",
"generateReleaseLintVitalModel",
"lintVitalAnalyzeRelease",
"lintReportRelease",
"lintAnalyzeRelease",
"lintReportDebug",
"lintAnalyzeDebug"
].each { taskName ->
if (subproject.tasks.findByName(taskName)) {
currentBundleTask.dependsOn(subproject.tasks.named(taskName))
}
}
// Also depend on the main build task to ensure all sub-tasks are completed
if (subproject.tasks.findByName("build")) {
currentBundleTask.dependsOn(subproject.tasks.named("build"))
}
}
}
}
currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)

View File

@@ -62,8 +62,7 @@
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
android:exported="false">
<meta-data android:name="org.jitsi.meet.sdk.JitsiInitializer"
android:value="androidx.startup" />
</provider>

View File

@@ -1,5 +1,5 @@
/*
* Copyright @ 2017-present 8x8, Inc.
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,7 +37,6 @@ class AppInfoModule
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,7 +79,6 @@ 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);
@@ -118,19 +116,6 @@ class AppInfoModule
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.
*

View File

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

View File

@@ -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;
@@ -226,17 +227,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");

View File

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

View File

@@ -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"),
@@ -36,13 +78,7 @@ public class BroadcastAction {
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");
TOGGLE_CAMERA("org.jitsi.meet.TOGGLE_CAMERA");
private final String action;

View File

@@ -91,9 +91,7 @@ public class BroadcastEvent {
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");
CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED("org.jitsi.meet.CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED");
private static final String CONFERENCE_BLURRED_NAME = "CONFERENCE_BLURRED";
private static final String CONFERENCE_FOCUSED_NAME = "CONFERENCE_FOCUSED";
@@ -111,9 +109,7 @@ public class BroadcastEvent {
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 static final String CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED_NAME = "CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED";
private final String action;
@@ -168,12 +164,8 @@ public class BroadcastEvent {
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;
case CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED_NAME:
return CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED;
}
return null;

View File

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

View File

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

View File

@@ -37,6 +37,7 @@ import java.util.Objects;
*
* @author Pawel Domas
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public class ConnectionService extends android.telecom.ConnectionService {
/**

View File

@@ -97,12 +97,6 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
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;
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import com.squareup.duktape.Duktape;
@ReactModule(name = JavaScriptSandboxModule.NAME)
class JavaScriptSandboxModule extends ReactContextBaseJavaModule {
public static final String NAME = "JavaScriptSandbox";
public JavaScriptSandboxModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Evaluates the given code in a Duktape VM.
* @param code - The code that needs to evaluated.
* @param promise - Resolved with the output in case of success or rejected with an exception
* in case of failure.
*/
@ReactMethod
public void evaluate(String code, Promise promise) {
Duktape vm = Duktape.create();
try {
Object res = vm.evaluate(code);
promise.resolve(res.toString());
} catch (Throwable tr) {
promise.reject(tr);
} finally {
vm.close();
}
}
@Override
public String getName() {
return NAME;
}
}

View File

@@ -23,10 +23,8 @@ import androidx.annotation.NonNull;
import androidx.startup.Initializer;
import com.facebook.soloader.SoLoader;
import com.facebook.react.soloader.OpenSourceMergedSoMapping;
import org.wonday.orientation.OrientationActivityLifecycle;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
@@ -37,11 +35,7 @@ public class JitsiInitializer implements Initializer<Boolean> {
public Boolean create(@NonNull Context context) {
Log.d(this.getClass().getCanonicalName(), "create");
try {
SoLoader.init(context, OpenSourceMergedSoMapping.INSTANCE);
} catch (IOException e) {
throw new RuntimeException(e);
}
SoLoader.init(context, /* native exopackage */ false);
// Register our uncaught exception handler.
JitsiMeetUncaughtExceptionHandler.register();
@@ -49,10 +43,6 @@ public class JitsiInitializer implements Initializer<Boolean> {
// Register activity lifecycle handler for the orientation locker module.
((Application) context).registerActivityLifecycleCallbacks(OrientationActivityLifecycle.getInstance());
// Initialize ReactInstanceManager during application startup
// This ensures it's ready before any Activity onCreate is called
ReactInstanceManagerHolder.initReactInstanceManager((Application) context);
return true;
}

View File

@@ -22,7 +22,7 @@ import android.os.Bundle;
import com.facebook.react.ReactInstanceManager;
import com.splashview.SplashView;
import org.devio.rn.splashscreen.SplashScreen;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
public class JitsiMeet {
@@ -92,7 +92,7 @@ public class JitsiMeet {
*/
public static void showSplashScreen(Activity activity) {
try {
SplashView.INSTANCE.showSplashView(activity);
SplashScreen.show(activity);
} catch (Exception e) {
JitsiMeetLogger.e(e, "Failed to show splash screen");
}

View File

@@ -24,17 +24,10 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.facebook.react.modules.core.PermissionListener;
@@ -61,8 +54,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) {
@@ -94,27 +85,6 @@ public class JitsiMeetActivity extends AppCompatActivity
launch(context, options);
}
public static void addTopBottomInsets(@NonNull Window w, @NonNull View v) {
View decorView = w.getDecorView();
decorView.post(() -> {
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(decorView);
if (insets != null) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
params.topMargin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top;
params.bottomMargin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;
v.setLayoutParams(params);
decorView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
view.setBackgroundColor(JitsiMeetView.BACKGROUND_COLOR);
return windowInsets;
});
}
});
}
// Overrides
//
@@ -130,17 +100,7 @@ public class JitsiMeetActivity extends AppCompatActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ReactInstanceManager is now initialized by JitsiInitializer during application startup
// Just call onHostResume since the manager is already ready
JitsiMeetActivityDelegate.onHostResume(this);
setContentView(R.layout.activity_jitsi_meet);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM
&& getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
addTopBottomInsets(getWindow(), findViewById(android.R.id.content));
}
this.jitsiView = findViewById(R.id.jitsiView);
registerForBroadcastMessages();
@@ -164,8 +124,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
@@ -173,10 +131,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;
@@ -194,12 +149,8 @@ public class JitsiMeetActivity extends AppCompatActivity
@Override
public void finish() {
if (!isReadyToClose) {
JitsiMeetLogger.i("finish(): leaving...");
leave();
}
leave();
JitsiMeetLogger.i("finish(): finishing...");
super.finish();
}
@@ -219,8 +170,8 @@ 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");
}
@@ -301,7 +252,6 @@ public class JitsiMeetActivity extends AppCompatActivity
protected void onReadyToClose() {
JitsiMeetLogger.i("SDK is ready to close");
isReadyToClose = true;
finish();
}
@@ -309,17 +259,9 @@ public class JitsiMeetActivity extends AppCompatActivity
// 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);
// }
// protected void onCustomOverflowMenuButtonPressed(HashMap<String, Object> extraData) {
// JitsiMeetLogger.i("Custom overflow menu button pressed: " + extraData);
// }
// Activity lifecycle methods
//
@@ -404,18 +346,12 @@ 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;
// case TRANSCRIPTION_CHUNK_RECEIVED:
// onTranscriptionChunkReceived(event.getData());
// break;
// case CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED:
// onCustomOverflowMenuButtonPressed(event.getData());
// break;
}
}
}

View File

@@ -37,11 +37,12 @@ public class JitsiMeetActivityDelegate {
* React Native module.
*/
private static PermissionListener permissionListener;
private static Callback permissionsCallback;
/**
* 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;
@@ -141,6 +142,11 @@ public class JitsiMeetActivityDelegate {
if (reactInstanceManager != null) {
reactInstanceManager.onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
}
if (permissionsCallback != null) {
permissionsCallback.invoke();
permissionsCallback = null;
}
}
/**
@@ -163,10 +169,15 @@ public class JitsiMeetActivityDelegate {
public static void onRequestPermissionsResult(
final int requestCode, final String[] permissions, final int[] grantResults) {
// Invoke the callback immediately
if (permissionListener != null && permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
permissionListener = null;
}
permissionsCallback = new Callback() {
@Override
public void invoke(Object... args) {
if (permissionListener != null
&& permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
permissionListener = null;
}
}
};
}
public static void requestPermissions(Activity activity, String[] permissions, int requestCode, PermissionListener listener) {

View File

@@ -270,6 +270,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();

View File

@@ -51,39 +51,39 @@ 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 static final int PERMISSIONS_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE);
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);
OngoingNotification.createNotificationChannel((Activity) context);
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
@@ -99,7 +99,6 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
public static void launch(Context context, HashMap<String, Object> extraData) {
List<String> permissionsList = new ArrayList<>();
Activity activity = (Activity) context;
PermissionListener listener = new PermissionListener() {
@Override
@@ -135,7 +134,7 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
if (permissionsArray.length > 0) {
JitsiMeetActivityDelegate.requestPermissions(
activity,
(Activity) context,
permissionsArray,
PERMISSIONS_REQUEST_CODE,
listener
@@ -155,25 +154,17 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
public void onCreate() {
super.onCreate();
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this, tapBackActivity);
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
try {
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);
}
} catch (Exception e) {
// Handle ForegroundServiceStartNotAllowedException when app is in background and cannot start foreground service.
// See: https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start#wiu-restrictions
JitsiMeetLogger.w(TAG + " Failed to start foreground service", e);
stopSelf();
return;
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);
}
}
@@ -199,28 +190,13 @@ 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, this);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
@@ -230,6 +206,9 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
}
}
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) {
@@ -302,9 +281,8 @@ 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, context);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't update service, notification is null");

View File

@@ -17,7 +17,6 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
@@ -34,9 +33,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.
*/
public static final int BACKGROUND_COLOR = 0xFF040404;
private static final int BACKGROUND_COLOR = 0xFF111111;
/**
* React Native root view.
@@ -197,6 +197,8 @@ public class JitsiMeetView extends FrameLayout {
}
setBackgroundColor(BACKGROUND_COLOR);
ReactInstanceManagerHolder.initReactInstanceManager((Activity) context);
}
/**

View File

@@ -1,84 +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 android.media.MediaCodecInfo;
import androidx.annotation.Nullable;
import com.oney.WebRTCModule.webrtcutils.SoftwareVideoDecoderFactoryProxy;
import org.webrtc.EglBase;
import org.webrtc.HardwareVideoDecoderFactory;
import org.webrtc.JitsiPlatformVideoDecoderFactory;
import org.webrtc.Predicate;
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 VideoDecoderFactory platformSoftwareVideoDecoderFactory;
/**
* Predicate to filter out the AV1 hardware decoder, as we've seen decoding issues with it.
*/
private static final String GOOGLE_AV1_DECODER = "c2.google.av1";
private static final Predicate<MediaCodecInfo> hwCodecPredicate = arg -> {
// Filter out the Google AV1 codec.
return !arg.getName().startsWith(GOOGLE_AV1_DECODER);
};
private static final Predicate<MediaCodecInfo> swCodecPredicate = arg -> {
// Noop, just making sure we can customize it easily if needed.
return true;
};
/**
* Create decoder factory using default hardware decoder factory.
*/
public JitsiVideoDecoderFactory(@Nullable EglBase.Context eglContext) {
this.hardwareVideoDecoderFactory = new HardwareVideoDecoderFactory(eglContext, hwCodecPredicate);
this.platformSoftwareVideoDecoderFactory = new JitsiPlatformVideoDecoderFactory(eglContext, swCodecPredicate);
}
@Override
public @Nullable VideoDecoder createDecoder(VideoCodecInfo codecType) {
VideoDecoder softwareDecoder = softwareVideoDecoderFactory.createDecoder(codecType);
final VideoDecoder hardwareDecoder = hardwareVideoDecoderFactory.createDecoder(codecType);
if (softwareDecoder == 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()));
supportedCodecInfos.addAll(Arrays.asList(platformSoftwareVideoDecoderFactory.getSupportedCodecs()));
return supportedCodecInfos.toArray(new VideoCodecInfo[supportedCodecInfos.size()]);
}
}

View File

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

View File

@@ -29,6 +29,8 @@ import android.content.Intent;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import android.os.Build;
/**
* Helper class for creating the ongoing notification which is used with
@@ -43,6 +45,10 @@ class OngoingNotification {
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
static void createNotificationChannel(Activity context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;
@@ -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) {
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);

View File

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

View File

@@ -3,6 +3,7 @@ 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;
@@ -31,6 +32,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 {
@@ -51,6 +53,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()) {

View File

@@ -16,25 +16,28 @@
package org.jitsi.meet.sdk;
import android.annotation.SuppressLint;
import android.app.Application;
import android.app.Activity;
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.webrtcutils.H264AndSoftwareVideoDecoderFactory;
import com.oney.WebRTCModule.webrtcutils.H264AndSoftwareVideoEncoderFactory;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import org.devio.rn.splashscreen.SplashScreenModule;
import org.webrtc.EglBase;
import org.webrtc.Logging;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
@@ -64,8 +67,10 @@ class ReactInstanceManagerHolder {
new AudioModeModule(reactContext),
new DropboxModule(reactContext),
new ExternalAPIModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),
new SplashScreenModule(reactContext),
new PictureInPictureModule(reactContext),
new ProximityModule(reactContext),
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)));
@@ -87,7 +92,7 @@ class ReactInstanceManagerHolder {
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
new com.ocetnik.timer.BackgroundTimerPackage(),
new com.calendarevents.RNCalendarEventsPackage(),
new com.sayem.keepawake.KCKeepAwakePackage(),
new com.corbt.keepawake.KCKeepAwakePackage(),
new com.facebook.react.shell.MainReactPackage(),
new com.reactnativecommunity.clipboard.ClipboardPackage(),
new com.reactnativecommunity.netinfo.NetInfoPackage(),
@@ -101,13 +106,12 @@ class ReactInstanceManagerHolder {
new com.oney.WebRTCModule.WebRTCModulePackage(),
new com.swmansion.gesturehandler.RNGestureHandlerPackage(),
new org.linusu.RNGetRandomValuesPackage(),
new com.rnimmersivemode.RNImmersiveModePackage(),
new com.swmansion.rnscreens.RNScreensPackage(),
new com.zmxv.RNSound.RNSoundPackage(),
new com.th3rdwave.safeareacontext.SafeAreaContextPackage(),
new com.horcrux.svg.SvgPackage(),
new org.wonday.orientation.OrientationPackage(),
new com.splashview.SplashViewPackage(),
new com.worklets.WorkletsCorePackage(),
new ReactPackageAdapter() {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
@@ -122,36 +126,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.RTNGiphySdkPackage");
Constructor<?> constructor = giphyPackageClass.getConstructor();
Class<?> giphyPackageClass = Class.forName("com.giphyreactnativesdk.GiphyReactNativeSdkPackage");
Constructor constructor = giphyPackageClass.getConstructor();
packages.add((ReactPackage)constructor.newInstance());
} catch (Exception e) {
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
JitsiMeetLogger.d(TAG, "Not loading GiphyReactNativeSdkPackage");
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.
*
@@ -165,7 +174,7 @@ class ReactInstanceManagerHolder {
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) {
@SuppressLint("VisibleForTests") ReactContext reactContext
ReactContext reactContext
= reactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
@@ -188,7 +197,7 @@ class ReactInstanceManagerHolder {
*/
static <T extends NativeModule> T getNativeModule(
Class<T> nativeModuleClass) {
@SuppressLint("VisibleForTests") ReactContext reactContext
ReactContext reactContext
= reactInstanceManager != null
? reactInstanceManager.getCurrentReactContext() : null;
@@ -196,6 +205,18 @@ 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;
}
@@ -206,38 +227,35 @@ class ReactInstanceManagerHolder {
* time. All {@code ReactRootView} instances will be tied to the one and
* only {@code ReactInstanceManager}.
*
* @param app {@code Application}
* @param activity {@code Activity} current running Activity.
*/
static void initReactInstanceManager(Application app) {
static void initReactInstanceManager(Activity activity) {
if (reactInstanceManager != null) {
return;
}
// 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");
EglBase.Context eglContext = EglUtils.getRootEglBaseContext();
options.videoDecoderFactory = new H264AndSoftwareVideoDecoderFactory(eglContext);
options.videoEncoderFactory = new H264AndSoftwareVideoEncoderFactory(eglContext);
options.enableMediaProjectionService = true;
// options.loggingSeverity = Logging.Severity.LS_INFO;
Log.d(TAG, "initializing RN with Activity");
reactInstanceManager
= ReactInstanceManager.builder()
.setApplication(app)
.setCurrentActivity(null)
.setApplication(activity.getApplication())
.setCurrentActivity(activity)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index.android")
.setJavaScriptExecutorFactory(new HermesExecutorFactory())
.setJavaScriptExecutorFactory(getReactNativeJSFactory())
.addPackages(getReactNativePackages())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2018 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.webrtc;
import android.media.MediaCodecInfo;
import androidx.annotation.Nullable;
/** Factory for Android platform software VideoDecoders. */
public class JitsiPlatformVideoDecoderFactory extends MediaCodecVideoDecoderFactory {
/**
* Default allowed predicate.
*/
private static final Predicate<MediaCodecInfo> defaultAllowedPredicate =
codecInfo -> {
// We only want to use the platform software codecs.
return MediaCodecUtils.isSoftwareOnly(codecInfo);
};
/**
* Creates a PlatformSoftwareVideoDecoderFactory that supports surface texture rendering.
*
* @param sharedContext The textures generated will be accessible from this context. May be null,
* this disables texture support.
*/
public JitsiPlatformVideoDecoderFactory(@Nullable EglBase.Context sharedContext) {
super(sharedContext, defaultAllowedPredicate);
}
public JitsiPlatformVideoDecoderFactory(@Nullable EglBase.Context sharedContext, @Nullable Predicate<MediaCodecInfo> codecAllowedPredicate) {
super(sharedContext, codecAllowedPredicate == null ? defaultAllowedPredicate : codecAllowedPredicate.and(defaultAllowedPredicate));
}
}

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="navigationBarColor">#040404</color>
<color name="statusBarColor">#040404</color>
</resources>

View File

@@ -1,6 +1,3 @@
<resources>
<style name="JitsiMeetActivityStyle" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:navigationBarColor">@color/navigationBarColor</item>
<item name="android:statusBarColor">@color/statusBarColor</item>
</style>
<style name="JitsiMeetActivityStyle" parent="Theme.AppCompat.Light.NoActionBar"/>
</resources>

View File

@@ -1,7 +1,9 @@
rootProject.name = 'jitsi-meet'
include ':app', ':sdk'
include ':react-native-amplitude'
project(':react-native-amplitude').projectDir = new File(rootProject.projectDir, '../node_modules/@amplitude/analytics-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'
@@ -24,8 +26,10 @@ 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-keep-awake'
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/@sayem314/react-native-keep-awake/android')
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
include ':react-native-orientation-locker'
project(':react-native-orientation-locker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-orientation-locker/android')
include ':react-native-pager-view'
@@ -40,8 +44,8 @@ include ':react-native-slider'
project(':react-native-slider').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/slider/android')
include ':react-native-sound'
project(':react-native-sound').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sound/android')
include ':react-native-splash-view'
project(':react-native-splash-view').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-view/android')
include ':react-native-splash-screen'
project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android')
include ':react-native-svg'
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
include ':react-native-video'
@@ -50,5 +54,3 @@ include ':react-native-webrtc'
project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android')
include ':react-native-webview'
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
include ':react-native-worklets-core'
project(':react-native-worklets-core').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-worklets-core/android')

28
app.js
View File

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

View File

@@ -1,20 +1,9 @@
module.exports = {
presets: [ 'module:@react-native/babel-preset' ],
presets: [ 'module:metro-react-native-babel-preset' ],
env: {
production: {
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
}
],
'react-native-worklets-core/plugin'
]
plugins: [ 'optional-require' ]
};

View File

@@ -18,6 +18,8 @@ import {
maybeRedirectToWelcomePage,
reloadWithStoredParams
} from './react/features/app/actions';
import { showModeratedNotification } from './react/features/av-moderation/actions';
import { shouldShowModeratedNotification } from './react/features/av-moderation/functions';
import {
_conferenceWillJoin,
authStatusChanged,
@@ -25,7 +27,6 @@ import {
conferenceJoinInProgress,
conferenceJoined,
conferenceLeft,
conferencePropertiesChanged,
conferenceSubjectChanged,
conferenceTimestampChanged,
conferenceUniqueIdSet,
@@ -50,8 +51,7 @@ import {
commonUserJoinedHandling,
commonUserLeftHandling,
getConferenceOptions,
sendLocalParticipant,
updateTrackMuteState
sendLocalParticipant
} from './react/features/base/conference/functions';
import { getReplaceParticipant, getSsrcRewritingFeatureFlag } from './react/features/base/config/functions';
import { connect } from './react/features/base/connection/actions.web';
@@ -88,7 +88,7 @@ import {
setVideoMuted,
setVideoUnmutePermissions
} from './react/features/base/media/actions';
import { MEDIA_TYPE, VIDEO_MUTISM_AUTHORITY, VIDEO_TYPE } from './react/features/base/media/constants';
import { MEDIA_TYPE, VIDEO_TYPE } from './react/features/base/media/constants';
import {
getStartWithAudioMuted,
getStartWithVideoMuted,
@@ -111,7 +111,6 @@ import {
import {
getLocalParticipant,
getNormalizedDisplayName,
getParticipantByIdOrUndefined,
getVirtualScreenshareParticipantByOwnerId
} from './react/features/base/participants/functions';
import { updateSettings } from './react/features/base/settings/actions';
@@ -130,17 +129,17 @@ import {
createLocalTracksF,
getLocalJitsiAudioTrack,
getLocalJitsiVideoTrack,
getLocalTracks,
getLocalVideoTrack,
isLocalTrackMuted,
isUserInteractionRequiredForUnmute
} from './react/features/base/tracks/functions';
import { getLocalJitsiAudioTrackSettings } from './react/features/base/tracks/functions.web';
import { downloadJSON } from './react/features/base/util/downloadJSON';
import { getJitsiMeetGlobalNSConnectionTimes } from './react/features/base/util/helpers';
import { openLeaveReasonDialog } from './react/features/conference/actions.web';
import { showDesktopPicker } from './react/features/desktop-picker/actions';
import { appendSuffix } from './react/features/display-name/functions';
import { maybeOpenFeedbackDialog, submitFeedback } from './react/features/feedback/actions';
import { initKeyboardShortcuts } from './react/features/keyboard-shortcuts/actions';
import { maybeSetLobbyChatMessageListener } from './react/features/lobby/actions.any';
import { setNoiseSuppressionEnabled } from './react/features/noise-suppression/actions';
import {
@@ -153,27 +152,25 @@ import {
DATA_CHANNEL_CLOSED_NOTIFICATION_ID,
NOTIFICATION_TIMEOUT_TYPE
} from './react/features/notifications/constants';
import { isModerationNotificationDisplayed } from './react/features/notifications/functions';
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
import { suspendDetected } from './react/features/power-monitor/actions';
import { initPrejoin, isPrejoinPageVisible } from './react/features/prejoin/functions';
import { disableReceiver, stopReceiver } from './react/features/remote-control/actions';
import { setScreenAudioShareState } from './react/features/screen-share/actions.web';
import { isScreenAudioShared } from './react/features/screen-share/functions';
import { toggleScreenshotCaptureSummary } from './react/features/screenshot-capture/actions';
import { setAudioSettings } from './react/features/settings/actions.web';
import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect';
import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
import { transcriberJoined, transcriberLeft } from './react/features/transcribing/actions';
import { muteLocal } from './react/features/video-menu/actions.any';
const logger = Logger.getLogger('app:conference-web');
const logger = Logger.getLogger(__filename);
let room;
/*
* Logic to open a desktop picker put on the window global for
* lib-jitsi-meet to detect and invoke.
*
* TODO: remove once the Electron SDK supporting gDM has been out for a while.
* lib-jitsi-meet to detect and invoke
*/
window.JitsiMeetScreenObtainer = {
openDesktopPicker(options, onSourceChoose) {
@@ -205,6 +202,23 @@ function sendData(command, value) {
room.sendCommand(command, { value });
}
/**
* Mute or unmute local audio stream if it exists.
* @param {boolean} muted - if audio stream should be muted or unmuted.
*/
function muteLocalAudio(muted) {
APP.store.dispatch(setAudioMuted(muted));
}
/**
* Mute or unmute local video stream if it exists.
* @param {boolean} muted if video stream should be muted or unmuted.
*
*/
function muteLocalVideo(muted) {
APP.store.dispatch(setVideoMuted(muted));
}
/**
* A queue for the async replaceLocalTrack action so that multiple audio
* replacements cannot happen simultaneously. This solves the issue where
@@ -271,7 +285,7 @@ class ConferenceConnector {
},
descriptionKey: 'dialog.reservationErrorMsg',
titleKey: 'dialog.reservationError'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
break;
}
@@ -279,7 +293,7 @@ class ConferenceConnector {
APP.store.dispatch(showErrorNotification({
descriptionKey: 'dialog.gracefulShutdown',
titleKey: 'dialog.serviceUnavailable'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
break;
// FIXME FOCUS_DISCONNECTED is a confusing event name.
@@ -398,10 +412,9 @@ export default {
* without any audio tracks.
* @param {boolean} options.startWithVideoMuted - will start the conference
* without any video tracks.
* @param {boolean} recordTimeMetrics - If true time metrics will be recorded.
* @returns {Promise<JitsiLocalTrack[]>, Object}
*/
createInitialLocalTracks(options = {}, recordTimeMetrics = false) {
createInitialLocalTracks(options = {}) {
const errors = {};
// Always get a handle on the audio input device so that we have statistics (such as "No audio input" or
@@ -418,6 +431,15 @@ export default {
requestedVideo = true;
}
if (!config.disableInitialGUM) {
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,
browserName =>
APP.store.dispatch(
mediaPermissionPromptVisibilityChanged(true, browserName))
);
}
let tryCreateLocalTracks = Promise.resolve([]);
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
@@ -426,7 +448,8 @@ export default {
const timeout = browser.isElectron() ? 15000 : 60000;
const audioOptions = {
devices: [ MEDIA_TYPE.AUDIO ],
timeout
timeout,
firePermissionPromptIsShownEvent: true
};
// Spot uses the _desktopSharingSourceDevice config option to use an external video input device label as
@@ -461,14 +484,24 @@ export default {
} else if (requestedAudio || requestedVideo) {
tryCreateLocalTracks = APP.store.dispatch(createInitialAVTracks({
devices: initialDevices,
timeout
}, recordTimeMetrics)).then(({ tracks, errors: pErrors }) => {
timeout,
firePermissionPromptIsShownEvent: true
})).then(({ tracks, errors: pErrors }) => {
Object.assign(errors, pErrors);
return tracks;
});
}
// Hide the permissions prompt/overlay as soon as the tracks are created. Don't wait for the connection to
// be established, as in some cases like when auth is required, connection won't be established until the user
// inputs their credentials, but the dialog would be overshadowed by the overlay.
tryCreateLocalTracks.then(tracks => {
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
return tracks;
});
return {
tryCreateLocalTracks,
errors
@@ -526,10 +559,10 @@ export default {
* If prejoin page is enabled open an new connection in the background
* and create local tracks.
*
* @param {{ roomName: string, shouldDispatchConnect }} options
* @param {{ roomName: string }} options
* @returns {Promise}
*/
async init({ roomName, shouldDispatchConnect }) {
async init({ roomName }) {
const state = APP.store.getState();
const initialOptions = {
startAudioOnly: config.startAudioOnly,
@@ -537,12 +570,8 @@ export default {
startWithAudioMuted: getStartWithAudioMuted(state) || isUserInteractionRequiredForUnmute(state),
startWithVideoMuted: getStartWithVideoMuted(state) || isUserInteractionRequiredForUnmute(state)
};
const connectionTimes = getJitsiMeetGlobalNSConnectionTimes();
const startTime = window.performance.now();
connectionTimes['conference.init.start'] = startTime;
logger.debug(`Executed conference.init with roomName: ${roomName} (performance.now=${startTime})`);
logger.debug(`Executed conference.init with roomName: ${roomName}`);
this.roomName = roomName;
@@ -568,80 +597,39 @@ export default {
if (browser.isWebKitBased()) {
this.muteAudio(true, true);
} else {
localTracks = localTracks.filter(track => {
if (track.getType() === MEDIA_TYPE.AUDIO) {
track.stopStream();
return false;
}
return true;
});
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
}
}
return localTracks;
};
const { dispatch, getState } = APP.store;
const createLocalTracksStart = window.performance.now();
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
connectionTimes['conference.init.createLocalTracks.start'] = createLocalTracksStart;
logger.debug(`(TIME) createInitialLocalTracks: ${createLocalTracksStart} `);
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions, true);
tryCreateLocalTracks.then(tr => {
const createLocalTracksEnd = window.performance.now();
connectionTimes['conference.init.createLocalTracks.end'] = createLocalTracksEnd;
logger.debug(`(TIME) createInitialLocalTracks finished: ${createLocalTracksEnd} `);
dispatch(setInitialGUMPromise(tryCreateLocalTracks.then(async tr => {
const tracks = handleInitialTracks(initialOptions, tr);
this._initDeviceList(true);
const { initialGUMPromise } = getState()['features/base/media'];
if (isPrejoinPageVisible(getState())) {
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
// Since the conference is not yet created in redux this function will execute synchronous
// which will guarantee us that the local tracks are added to redux before we proceed.
initPrejoin(tracks, errors, dispatch);
connectionTimes['conference.init.end'] = window.performance.now();
// resolve the initialGUMPromise in case connect have finished so that we can proceed to join.
if (initialGUMPromise) {
logger.debug('Resolving the initialGUM promise! (prejoinVisible=true)');
initialGUMPromise.resolve({
tracks,
errors
});
}
logger.debug('Clear the initialGUM promise! (prejoinVisible=true)');
// For prejoin we don't need the initial GUM promise since the tracks are already added to the store
// via initPrejoin
dispatch(setInitialGUMPromise());
// Note: Not sure if initPrejoin needs to be async. But let's wait for it just to be sure the
// tracks are added.
initPrejoin(tracks, errors, dispatch);
} else {
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
setGUMPendingStateOnFailedTracks(tracks, APP.store.dispatch);
connectionTimes['conference.init.end'] = window.performance.now();
if (initialGUMPromise) {
logger.debug('Resolving the initialGUM promise!');
initialGUMPromise.resolve({
tracks,
errors
});
}
}
});
if (shouldDispatchConnect) {
logger.info('Dispatching connect from init since prejoin is not visible.');
return {
tracks,
errors
};
})));
if (!isPrejoinPageVisible(getState())) {
dispatch(connect());
}
},
@@ -699,10 +687,11 @@ export default {
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
*
* @param {boolean} mute true for mute and false for unmute.
* @param {boolean} [showUI] when set to false will not display any error
* dialogs in case of media permissions error.
* @returns {Promise}
*/
async muteAudio(mute) {
async muteAudio(mute, showUI = true) {
const state = APP.store.getState();
if (!mute
@@ -712,7 +701,56 @@ export default {
return;
}
await APP.store.dispatch(setAudioMuted(mute, true));
// check for A/V Moderation when trying to unmute
if (!mute && shouldShowModeratedNotification(MEDIA_TYPE.AUDIO, state)) {
if (!isModerationNotificationDisplayed(MEDIA_TYPE.AUDIO, state)) {
APP.store.dispatch(showModeratedNotification(MEDIA_TYPE.AUDIO));
}
return;
}
// Not ready to modify track's state yet
if (!this._localTracksInitialized) {
// This will only modify base/media.audio.muted which is then synced
// up with the track at the end of local tracks initialization.
muteLocalAudio(mute);
this.updateAudioIconEnabled();
return;
} else if (this.isLocalAudioMuted() === mute) {
// NO-OP
return;
}
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
if (!localAudio && !mute) {
const maybeShowErrorDialog = error => {
showUI && APP.store.dispatch(notifyMicError(error));
};
APP.store.dispatch(gumPending([ MEDIA_TYPE.AUDIO ], IGUMPendingState.PENDING_UNMUTE));
await createLocalTracksF({ devices: [ 'audio' ] })
.then(([ audioTrack ]) => audioTrack)
.catch(error => {
maybeShowErrorDialog(error);
// Rollback the audio muted status by using null track
return null;
})
.then(async audioTrack => {
await this._maybeApplyAudioMixerEffect(audioTrack);
return this.useAudioStream(audioTrack);
})
.finally(() => {
APP.store.dispatch(gumPending([ MEDIA_TYPE.AUDIO ], IGUMPendingState.NONE));
});
} else {
muteLocalAudio(mute);
}
},
/**
@@ -742,9 +780,16 @@ export default {
/**
* Simulates toolbar button click for video mute. Used by shortcuts and API.
* @param mute true for mute and false for unmute.
* @param {boolean} [showUI] when set to false will not display any error
* dialogs in case of media permissions error.
*/
muteVideo(mute) {
muteVideo(mute, showUI = true) {
if (this.videoSwitchInProgress) {
logger.warn('muteVideo - unable to perform operations while video switch is in progress');
return;
}
const state = APP.store.getState();
if (!mute
@@ -754,7 +799,65 @@ export default {
return;
}
APP.store.dispatch(setVideoMuted(mute, VIDEO_MUTISM_AUTHORITY.USER, true));
// check for A/V Moderation when trying to unmute and return early
if (!mute && shouldShowModeratedNotification(MEDIA_TYPE.VIDEO, state)) {
return;
}
// If not ready to modify track's state yet adjust the base/media
if (!this._localTracksInitialized) {
// This will only modify base/media.video.muted which is then synced
// up with the track at the end of local tracks initialization.
muteLocalVideo(mute);
this.setVideoMuteStatus();
return;
} else if (this.isLocalVideoMuted() === mute) {
// NO-OP
return;
}
const localVideo = getLocalJitsiVideoTrack(state);
if (!localVideo && !mute && !this.isCreatingLocalTrack) {
const maybeShowErrorDialog = error => {
showUI && APP.store.dispatch(notifyCameraError(error));
};
this.isCreatingLocalTrack = true;
APP.store.dispatch(gumPending([ MEDIA_TYPE.VIDEO ], IGUMPendingState.PENDING_UNMUTE));
// Try to create local video if there wasn't any.
// This handles the case when user joined with no video
// (dismissed screen sharing screen or in audio only mode), but
// decided to add it later on by clicking on muted video icon or
// turning off the audio only mode.
//
// FIXME when local track creation is moved to react/redux
// it should take care of the use case described above
createLocalTracksF({ devices: [ 'video' ] })
.then(([ videoTrack ]) => videoTrack)
.catch(error => {
// FIXME should send some feedback to the API on error ?
maybeShowErrorDialog(error);
// Rollback the video muted status by using null track
return null;
})
.then(videoTrack => {
logger.debug(`muteVideo: calling useVideoStream for track: ${videoTrack}`);
return this.useVideoStream(videoTrack);
})
.finally(() => {
this.isCreatingLocalTrack = false;
APP.store.dispatch(gumPending([ MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
});
} else {
// FIXME show error dialog if it fails (should be handled by react)
muteLocalVideo(mute);
}
},
/**
@@ -889,7 +992,7 @@ export default {
},
/**
* Will be filled with values only when config.testing.testMode is true.
* Will be filled with values only when config.debug is enabled.
* Its used by torture to check audio levels.
*/
audioLevelsMap: {},
@@ -935,14 +1038,6 @@ export default {
downloadJSON(logs, filename);
},
/**
* Download app state, a function that can be called from console while debugging.
* @param filename (optional) specify target filename
*/
saveState(filename = 'meet-state.json') {
downloadJSON(APP.store.getState(), filename);
},
/**
* Exposes a Command(s) API on this instance. It is necessitated by (1) the
* desire to keep room private to this instance and (2) the need of other
@@ -1007,6 +1102,7 @@ export default {
// Restore initial state.
this._localTracksInitialized = false;
this.isSharingScreen = false;
this.roomName = roomName;
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(options);
@@ -1185,6 +1281,8 @@ export default {
return Boolean(APP.store.getState()['features/base/audio-only'].enabled);
},
videoSwitchInProgress: false,
/**
* This fields stores a handler which will create a Promise which turns off
* the screen sharing and restores the previous video state (was there
@@ -1213,6 +1311,7 @@ export default {
*/
async _turnScreenSharingOff(didHaveVideo, ignoreDidHaveVideo) {
this._untoggleScreenSharing = null;
this.videoSwitchInProgress = true;
APP.store.dispatch(stopReceiver());
@@ -1264,11 +1363,13 @@ export default {
return promise.then(
() => {
this.videoSwitchInProgress = false;
sendAnalytics(createScreenSharingEvent('stopped',
duration === 0 ? null : duration));
logger.info('Screen sharing stopped.');
},
error => {
this.videoSwitchInProgress = false;
logger.error(`_turnScreenSharingOff failed: ${error}`);
throw error;
@@ -1278,19 +1379,28 @@ export default {
/**
* Creates desktop (screensharing) {@link JitsiLocalTrack}
*
* @param {Object} [options] - Screen sharing options that will be passed to
* createLocalTracks.
* @param {Object} [options.desktopSharing]
* @param {Object} [options.desktopStream] - An existing desktop stream to
* use instead of creating a new desktop stream.
* @return {Promise.<JitsiLocalTrack>} - A Promise resolved with
* {@link JitsiLocalTrack} for the screensharing or rejected with
* {@link JitsiTrackError}.
*
* @private
*/
_createDesktopTrack() {
_createDesktopTrack(options = {}) {
const didHaveVideo = !this.isLocalVideoMuted();
const getDesktopStreamPromise = createLocalTracksF({
desktopSharingSourceDevice: config._desktopSharingSourceDevice,
devices: [ 'desktop' ]
});
const getDesktopStreamPromise = options.desktopStream
? Promise.resolve([ options.desktopStream ])
: createLocalTracksF({
desktopSharingSourceDevice: options.desktopSharingSources
? null : config._desktopSharingSourceDevice,
desktopSharingSources: options.desktopSharingSources,
devices: [ 'desktop' ]
});
return getDesktopStreamPromise.then(desktopStreams => {
// Stores the "untoggle" handler which remembers whether was
@@ -1298,13 +1408,14 @@ export default {
this._untoggleScreenSharing
= this._turnScreenSharingOff.bind(this, didHaveVideo);
const desktopVideoStream = desktopStreams.find(stream => stream.getType() === MEDIA_TYPE.VIDEO);
const desktopAudioStream = desktopStreams.find(stream => stream.getType() === MEDIA_TYPE.AUDIO);
if (desktopAudioStream) {
desktopAudioStream.on(
JitsiTrackEvents.LOCAL_TRACK_STOPPED,
() => {
logger.debug('Local screensharing audio track stopped.');
logger.debug(`Local screensharing audio track stopped. ${this.isSharingScreen}`);
// Handle case where screen share was stopped from the browsers 'screen share in progress'
// window. If audio screen sharing is stopped via the normal UX flow this point shouldn't
@@ -1316,6 +1427,21 @@ export default {
);
}
if (desktopVideoStream) {
desktopVideoStream.on(
JitsiTrackEvents.LOCAL_TRACK_STOPPED,
() => {
logger.debug(`Local screensharing track stopped. ${this.isSharingScreen}`);
// If the stream was stopped during screen sharing
// session then we should switch back to video.
this.isSharingScreen
&& this._untoggleScreenSharing
&& this._untoggleScreenSharing();
}
);
}
return desktopStreams;
}, error => {
throw error;
@@ -1375,7 +1501,7 @@ export default {
}
APP.store.dispatch(updateRemoteParticipantFeatures(user));
logger.log(`USER ${id} connected`);
logger.log(`USER ${id} connected:`, user);
APP.UI.addUser(user);
});
@@ -1409,6 +1535,7 @@ export default {
}
APP.store.dispatch(localParticipantRoleChanged(role));
APP.API.notifyUserRoleChanged(id, role);
} else {
APP.store.dispatch(participantRoleChanged(id, role));
}
@@ -1450,9 +1577,9 @@ export default {
newLvl = 0;
}
if (config.testing?.testMode) {
if (config.debug) {
this.audioLevelsMap[id] = newLvl;
if (config.testing?.debugAudioLevels) {
if (config.debugAudioLevels) {
logger.log(`AudioLevel:${id}/${newLvl}`);
}
}
@@ -1463,6 +1590,10 @@ export default {
room.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED, (track, participantThatMutedUs) => {
if (participantThatMutedUs) {
APP.store.dispatch(participantMutedUs(participantThatMutedUs, track));
if (this.isSharingScreen && track.isVideoTrack()) {
logger.debug('TRACK_MUTE_CHANGED while screen sharing');
this._turnScreenSharingOff(false);
}
}
});
@@ -1552,16 +1683,6 @@ export default {
}
);
room.on(
JitsiConferenceEvents.TRANSCRIPTION_STATUS_CHANGED,
(status, id, abruptly) => {
if (status === JitsiMeetJS.constants.transcriptionStatus.ON) {
APP.store.dispatch(transcriberJoined(id));
} else if (status === JitsiMeetJS.constants.transcriptionStatus.OFF) {
APP.store.dispatch(transcriberLeft(id, abruptly));
}
});
room.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(participant, data) => {
@@ -1588,10 +1709,6 @@ export default {
JitsiConferenceEvents.LOCK_STATE_CHANGED,
(...args) => APP.store.dispatch(lockStateChanged(room, ...args)));
room.on(
JitsiConferenceEvents.PROPERTIES_CHANGED,
properties => APP.store.dispatch(conferencePropertiesChanged(properties)));
room.on(JitsiConferenceEvents.KICKED, (participant, reason, isReplaced) => {
if (isReplaced) {
// this event triggers when the local participant is kicked, `participant`
@@ -1658,30 +1775,50 @@ export default {
room.addCommandListener(
this.commands.defaults.AVATAR_URL,
(data, from) => {
const participant = getParticipantByIdOrUndefined(APP.store, from);
// if already set from presence(jwt), skip the command processing
if (!participant?.avatarURL) {
APP.store.dispatch(
participantUpdated({
conference: room,
id: from,
avatarURL: data.value
}));
}
APP.store.dispatch(
participantUpdated({
conference: room,
id: from,
avatarURL: data.value
}));
});
room.on(
JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
({ audio, video }) => {
APP.store.dispatch(onStartMutedPolicyChanged(audio, video));
const state = APP.store.getState();
updateTrackMuteState(state, APP.store.dispatch, true);
updateTrackMuteState(state, APP.store.dispatch, false);
APP.store.dispatch(
onStartMutedPolicyChanged(audio, video));
}
);
room.on(JitsiConferenceEvents.STARTED_MUTED, () => {
const audioMuted = room.isStartAudioMuted();
const videoMuted = room.isStartVideoMuted();
const localTracks = getLocalTracks(APP.store.getState()['features/base/tracks']);
const promises = [];
APP.store.dispatch(setAudioMuted(audioMuted));
APP.store.dispatch(setVideoMuted(videoMuted));
// Remove the tracks from the peerconnection.
for (const track of localTracks) {
// Always add the track on Safari because of a known issue where audio playout doesn't happen
// if the user joins audio and video muted, i.e., if there is no local media capture.
if (audioMuted && track.jitsiTrack?.getType() === MEDIA_TYPE.AUDIO && !browser.isWebKitBased()) {
promises.push(this.useAudioStream(null));
}
if (videoMuted && track.jitsiTrack?.getType() === MEDIA_TYPE.VIDEO) {
promises.push(this.useVideoStream(null));
}
}
Promise.allSettled(promises)
.then(() => {
APP.store.dispatch(showNotification({
titleKey: 'notify.mutedTitle',
descriptionKey: 'notify.muted'
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
});
});
room.on(
JitsiConferenceEvents.DATA_CHANNEL_OPENED, () => {
@@ -1719,16 +1856,6 @@ export default {
}, timeout);
}
);
room.on(JitsiConferenceEvents.PERMISSIONS_RECEIVED, p => {
const localParticipant = getLocalParticipant(APP.store.getState());
APP.store.dispatch(participantUpdated({
id: localParticipant.id,
local: true,
features: p
}));
});
},
/**
@@ -1773,11 +1900,7 @@ export default {
return this.useAudioStream(stream);
})
.then(() => {
const state = APP.store.getState();
const localAudio = getLocalJitsiAudioTrack(state);
const settings = getLocalJitsiAudioTrackSettings(state);
APP.store.dispatch(setAudioSettings(settings));
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
if (localAudio && isDefaultMicSelected) {
// workaround for the default device to be shown as selected in the
@@ -1876,6 +1999,7 @@ export default {
APP.UI.initConference();
dispatch(initKeyboardShortcuts());
dispatch(conferenceJoined(room));
const jwt = APP.store.getState()['features/base/jwt'];
@@ -1897,7 +2021,8 @@ export default {
_initDeviceList(setDeviceListChangeHandler = false) {
const { mediaDevices } = JitsiMeetJS;
if (mediaDevices.isDeviceChangeAvailable()) {
if (mediaDevices.isDeviceListAvailable()
&& mediaDevices.isDeviceChangeAvailable()) {
if (setDeviceListChangeHandler) {
this.deviceChangeListener = devices =>
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
@@ -1909,9 +2034,8 @@ export default {
const { dispatch } = APP.store;
return dispatch(getAvailableDevices())
.then(() => {
this.updateAudioIconEnabled();
this.updateVideoIconEnabled();
.then(devices => {
APP.UI.onAvailableDevicesChanged(devices);
});
}
@@ -2076,8 +2200,7 @@ export default {
return Promise.all(promises)
.then(() => {
this.updateAudioIconEnabled();
this.updateVideoIconEnabled();
APP.UI.onAvailableDevicesChanged(filteredDevices);
});
},
@@ -2122,10 +2245,8 @@ export default {
* @param {boolean} [requestFeedback=false] if user feedback should be
* @param {string} [hangupReason] the reason for leaving the meeting
* requested
* @param {boolean} [notifyOnConferenceTermination] whether to notify
* the user on conference termination
*/
hangup(requestFeedback = false, hangupReason, notifyOnConferenceTermination) {
hangup(requestFeedback = false, hangupReason) {
APP.store.dispatch(disableReceiver());
this._stopProxyConnection();
@@ -2144,7 +2265,7 @@ export default {
if (requestFeedback) {
const feedbackDialogClosed = (feedbackResult = {}) => {
if (!feedbackResult.wasDialogShown && hangupReason && notifyOnConferenceTermination) {
if (!feedbackResult.wasDialogShown && hangupReason) {
return APP.store.dispatch(
openLeaveReasonDialog(hangupReason)).then(() => feedbackResult);
}

380
config.js
View File

@@ -49,9 +49,7 @@ var config = {
bosh: 'https://jitsi-meet.example.com/' + subdir + 'http-bind',
// Websocket URL (XMPP)
websocket: 'wss://jitsi-meet.example.com/' + subdir + 'xmpp-websocket',
// websocketKeepAliveUrl: 'https://jitsi-meet.example.com/' + subdir + '_unlock',
// websocket: 'wss://jitsi-meet.example.com/' + subdir + 'xmpp-websocket',
// Whether BOSH should be preferred over WebSocket if both are configured.
// preferBosh: false,
@@ -86,11 +84,9 @@ var config = {
// Allows the setting of a custom bandwidth value from the UI.
// assumeBandwidth: true,
// Enables use of getDisplayMedia in electron
// electronUseGetDisplayMedia: false,
// Enables AV1 codec for FF. Note: By default it is disabled.
// enableAV1ForFF: false,
// Disables the End to End Encryption feature. Useful for debugging
// issues related to insertable streams.
// disableE2EE: false,
// Enables the use of the codec selection API supported by the browsers .
// enableCodecSelectionAPI: false,
@@ -111,17 +107,6 @@ var config = {
// Dump transcripts to a <transcript> element for debugging.
// dumpTranscript: false,
// Log the audio levels.
// debugAudioLevels: true,
// Will replace ice candidates IPs with invalid ones in order to fail ice.
// failICE: true,
// When running on Spot TV, this controls whether to show the recording consent dialog.
// If false (default), Spot instances will not show the recording consent dialog.
// If true, Spot instances will show the recording consent dialog like regular clients.
// showSpotConsentDialog: false,
},
// Disables moderator indicators.
@@ -133,15 +118,9 @@ var config = {
// Disables the reactions moderation feature.
// disableReactionsModeration: false,
// Disables the reactions in chat feature.
// disableReactionsInChat: false,
// Disables polls feature.
// disablePolls: false,
// Disables chat feature entirely including notifications, sounds, and private messages.
// disableChat: false,
// Disables demote button from self-view
// disableSelfDemote: false,
@@ -234,45 +213,14 @@ var config = {
// installation. Specifically, these files are needed:
// - https://meet.example.com/libs/krisp/krisp.mjs
// - https://meet.example.com/libs/krisp/models/model_8.kw
// - https://meet.example.com/libs/krisp/models/model_nc.kw
// - https://meet.example.com/libs/krisp/models/model_bvc.kw
// - https://meet.example.com/libs/krisp/assets/bvc-allowed.txt
// In case when you have known BVC supported devices and you want to extend allowed devices list
// - https://meet.example.com/libs/krisp/assets/bvc-allowed-ext.txt
// In case when you have known BVC supported devices and you want to extend allowed devices list
// - https://meet.example.com/libs/krisp/models/model_inbound_8.kw
// - https://meet.example.com/libs/krisp/models/model_inbound_16.kw
// In case when you want to use inbound noise suppression models
// NOTE: Krisp JS SDK v2.0.0 was tested.
// - https://meet.example.com/libs/krisp/models/model_16.kw
// - https://meet.example.com/libs/krisp/models/model_32.kw
// NOTE: Krisp JS SDK v1.0.9 was tested.
// noiseSuppression: {
// krisp: {
// enabled: false,
// logProcessStats: false,
// debugLogs: false,
// useBVC: false,
// bufferOverflowMS: 1000,
// inboundModels: {
// modelInbound8: 'model_inbound_8.kef',
// modelInbound16: 'model_inbound_16.kef',
// },
// preloadInboundModels: {
// modelInbound8: 'model_inbound_8.kef',
// modelInbound16: 'model_inbound_16.kef',
// },
// preloadModels: {
// modelBVC: 'model_bvc.kef',
// model8: 'model_8.kef',
// modelNC: 'model_nc_mq.kef',
// },
// models: {
// modelBVC: 'model_bvc.kef',
// model8: 'model_8.kef',
// modelNV: 'model_nc_mq.kef',
// },
// bvc: {
// allowedDevices: 'bvc-allowed.txt',
// allowedDevicesExt: 'bvc-allowed-ext.txt',
// }
// },
// },
@@ -284,26 +232,9 @@ var config = {
// Sets the preferred resolution (height) for local video. Defaults to 720.
// resolution: 720,
// DEPRECATED. Please use raisedHands.disableRemoveRaisedHandOnFocus instead.
// Specifies whether the raised hand will hide when someone becomes a dominant speaker or not
// disableRemoveRaisedHandOnFocus: false,
// Specifies which raised hand related config should be set.
// raisedHands: {
// // Specifies whether the raised hand can be lowered by moderator.
// disableLowerHandByModerator: false,
// // Specifies whether there is a notification before hiding the raised hand
// // when someone becomes the dominant speaker.
// disableLowerHandNotification: true,
// // Specifies whether there is a notification when you are the next speaker in line.
// disableNextSpeakerNotification: false,
// // Specifies whether the raised hand will hide when someone becomes a dominant speaker or not.
// disableRemoveRaisedHandOnFocus: false,
// },
// speakerStats: {
// // Specifies whether the speaker stats is enable or not.
// disabled: false,
@@ -366,7 +297,6 @@ var config = {
// Desktop sharing
// Optional desktop sharing frame rate options. Default value: min:5, max:5.
// Setting higher min/max values will affect the resolution, it makes it worse.
// desktopSharingFrameRate: {
// min: 5,
// max: 5,
@@ -388,6 +318,9 @@ var config = {
// Recording
// DEPRECATED. Use recordingService.enabled instead.
// fileRecordingsEnabled: false,
// Enable the dropbox integration.
// dropbox: {
// appKey: '<APP_KEY>', // Specify your app key here.
@@ -408,15 +341,6 @@ var config = {
// // If true, shows a warning label in the prejoin screen to point out the possibility that
// // the call you're joining might be recorded.
// // showPrejoinWarning: true,
// // If true, the notification for recording start will display a link to download the cloud recording.
// // showRecordingLink: true,
// // If true, mutes audio and video when a recording begins and displays a dialog
// // explaining the effect of unmuting.
// // requireConsent: true,
// // If true consent will be skipped for users who are already in the meeting.
// // skipConsentInMeeting: true,
// // Link for the recording consent dialog's "Learn more" link.
// // consentLearnMoreLink: 'https://jitsi.org/meet/consent',
// },
// recordingService: {
@@ -489,7 +413,7 @@ var config = {
// // Translation languages.
// // Available languages can be found in
// // ./lang/translation-languages.json.
// // ./src/react/features/transcribing/translation-languages.json.
// translationLanguages: ['en', 'es', 'fr', 'ro'],
// // Important languages to show on the top of the language list.
@@ -510,19 +434,6 @@ var config = {
// // Enables automatic turning on transcribing when recording is started
// autoTranscribeOnRecord: false,
// // Enables automatic request of subtitles when transcriber is present in the meeting, uses the default
// // language that is set
// autoCaptionOnTranscribe: false,
//
// // Disables everything related to closed captions - the tab in the chat area, the button in the menu,
// // subtitles on stage and the "Show subtitles on stage" checkbox in the settings.
// // Note: Starting transcriptions from the recording dialog will still work.
// disableClosedCaptions: false,
// // Whether to invite jigasi when backend transcriptions are enabled (asyncTranscription is true in metadata).
// // By default, we invite it.
// inviteJigasiOnBackendTranscribing: true,
// },
// Misc
@@ -548,17 +459,12 @@ var config = {
// videoQuality: {
//
// // Provides a way to set the codec preference on desktop based endpoints.
// codecPreferenceOrder: [ 'AV1', 'VP9', 'VP8', 'H264' ],
// codecPreferenceOrder: [ 'VP9', 'VP8', 'H264' ],
//
// // Provides a way to set the codec for screenshare.
// screenshareCodec: 'AV1',
// mobileScreenshareCodec: 'VP8',
//
// // Enables the adaptive mode in the client that will make runtime adjustments to selected codecs and received
// // videos for a better user experience. This mode will kick in only when CPU overuse is reported in the
// // WebRTC statistics for the outbound video streams.
// enableAdaptiveMode: false,
//
// // Codec specific settings for scalability modes and max bitrates.
// av1: {
// maxBitratesVideo: {
@@ -609,6 +515,30 @@ var config = {
// useKSVC: true
// },
//
// DEPRECATED! Use `codec specific settings` instead.
// // Provides a way to configure the maximum bitrates that will be enforced on the simulcast streams for
// // video tracks. The keys in the object represent the type of the stream (LD, SD or HD) and the values
// // are the max.bitrates to be set on that particular type of stream. The actual send may vary based on
// // the available bandwidth calculated by the browser, but it will be capped by the values specified here.
// // This is currently not implemented on app based clients on mobile.
// maxBitratesVideo: {
// H264: {
// low: 200000,
// standard: 500000,
// high: 1500000,
// },
// VP8 : {
// low: 200000,
// standard: 500000,
// high: 1500000,
// },
// VP9: {
// low: 100000,
// standard: 300000,
// high: 1200000,
// },
// },
//
// // The options can be used to override default thresholds of video thumbnail heights corresponding to
// // the video quality levels used in the application. At the time of this writing the allowed levels are:
// // 'low' - for the low quality level (180p at the time of this writing)
@@ -625,7 +555,23 @@ var config = {
// },
//
// // Provides a way to set the codec preference on mobile devices, both on RN and mobile browser based endpoint
// mobileCodecPreferenceOrder: [ 'VP8', 'VP9', 'H264', 'AV1' ],
// mobileCodecPreferenceOrder: [ 'VP8', 'VP9', 'H264' ],
//
// // DEPRECATED! Use `codecPreferenceOrder/mobileCodecPreferenceOrder` instead.
// // Provides a way to prevent a video codec from being negotiated on the JVB connection. The codec specified
// // here will be removed from the list of codecs present in the SDP answer generated by the client. If the
// // same codec is specified for both the disabled and preferred option, the disable settings will prevail.
// // Note that 'VP8' cannot be disabled since it's a mandatory codec, the setting will be ignored in this case.
// disabledCodec: 'H264',
//
// // DEPRECATED! Use `codecPreferenceOrder/mobileCodecPreferenceOrder` instead.
// // Provides a way to set a preferred video codec for the JVB connection. If 'H264' is specified here,
// // simulcast will be automatically disabled since JVB doesn't support H264 simulcast yet. This will only
// // rearrange the the preference order of the codecs in the SDP answer generated by the browser only if the
// // preferred codec specified here is present. Please ensure that the JVB offers the specified codec for this
// // to take effect.
// preferredCodec: 'VP8',
//
// },
// Notification timeouts
@@ -633,8 +579,6 @@ var config = {
// short: 2500,
// medium: 5000,
// long: 10000,
// extraLong: 60000,
// sticky: 0,
// },
// // Options for the recording limit notification.
@@ -664,6 +608,14 @@ var config = {
// Disables or enables REMB support in this client (default: enabled).
// enableRemb: true,
// Enables ICE restart logic in LJM and displays the page reload overlay on
// ICE failure. Current disabled by default because it's causing issues with
// signaling when Octo is enabled. Also when we do an "ICE restart"(which is
// not a real ICE restart), the client maintains the TCC sequence number
// counter, but the bridge resets it. The bridge sends media packets with
// TCC sequence numbers starting from 0.
// enableIceRestart: false,
// Enables forced reload of the client when the call is migrated as a result of
// the bridge going down.
// enableForcedReload: true,
@@ -726,8 +678,6 @@ var config = {
// autoKnock: false,
// // Enables the lobby chat. Replaces `enableLobbyChat`.
// enableChat: true,
// // Shows the hangup button in the lobby screen.
// showHangUp: true,
// },
// Configs for the security related UI elements.
@@ -767,7 +717,7 @@ var config = {
// hideDominantSpeakerBadge: false,
// Default language for the user interface. Cannot be overwritten.
// For iframe integrations, use the `lang` option directly instead.
// DEPRECATED! Use the `lang` iframe option directly instead.
// defaultLanguage: 'en',
// Disables profile and the edit of all fields from the profile settings (display name and email)
@@ -788,15 +738,10 @@ var config = {
// and microsoftApiApplicationClientID
// enableCalendarIntegration: false,
// Whether to notify when the conference is terminated because it was destroyed.
// notifyOnConferenceDestruction: true,
// The client id for the google APIs used for the calendar integration, youtube livestreaming, etc.
// googleApiApplicationClientID: '<client_id>',
// Configs for prejoin page.
// prejoinConfig: {
// // When 'true', it shows an intermediate page before joining, where the user can configure their devices.
// // This replaces `prejoinPageEnabled`. Defaults to true.
// enabled: true,
// // Hides the participant name editing field in the prejoin screen.
// // If requireDisplayName is also set as true, a name should still be provided through
@@ -804,13 +749,6 @@ var config = {
// hideDisplayName: false,
// // List of buttons to hide from the extra join options dropdown.
// hideExtraJoinButtons: ['no-audio', 'by-phone'],
// // Configuration for pre-call test
// // By setting preCallTestEnabled, you enable the pre-call test in the prejoin page.
// // ICE server credentials need to be provided over the preCallTestICEUrl
// preCallTestEnabled: false,
// preCallTestICEUrl: '',
// // Shows the hangup button in the lobby screen.
// showHangUp: true,
// },
// When 'true', the user cannot edit the display name.
@@ -829,6 +767,10 @@ var config = {
// set or the lobby is not enabled.
// enableInsecureRoomNameWarning: false,
// Whether to automatically copy invitation URL after creating a room.
// Document should be focused for this option to work
// enableAutomaticUrlCopy: false,
// Array with avatar URL prefixes that need to use CORS.
// corsAvatarURLs: [ 'https://www.gravatar.com/avatar/' ],
@@ -855,7 +797,8 @@ var config = {
// some other values in config.js to be enabled. Also, the "profile" button will
// not display for users with a JWT.
// Notes:
// - it's possible to reorder the buttons in the maintoolbar by changing the order of the mainToolbarButtons
// - it's impossible to choose which buttons go in the "More actions" menu
// - it's impossible to control the placement of buttons
// - 'desktop' controls the "Share your screen" button
// - if `toolbarButtons` is undefined, we fallback to enabling all buttons on the UI
// toolbarButtons: [
@@ -912,7 +855,7 @@ var config = {
// Overrides the buttons displayed in the main toolbar. Depending on the screen size the number of displayed
// buttons varies from 2 buttons to 8 buttons. Every array in the mainToolbarButtons array will replace the
// corresponding default buttons configuration matched by the number of buttons specified in the array. Arrays with
// more than 8 buttons or less then 2 buttons will be ignored. When there there isn't an override for a certain
// more than 8 buttons or less then 2 buttons will be ignored. When there there isn't an override for a cerain
// configuration (for example when 3 buttons are displayed) the default jitsi-meet configuration will be used.
// The order of the buttons in the array is preserved.
// mainToolbarButtons: [
@@ -1106,10 +1049,10 @@ var config = {
// Provides a way to set the codec preference on mobile devices, both on RN and mobile browser based
// endpoints.
// mobileCodecPreferenceOrder: [ 'H264', 'VP8', 'VP9', 'AV1' ],
// mobileCodecPreferenceOrder: [ 'H264', 'VP8', 'VP9' ],
//
// Provides a way to set the codec preference on desktop based endpoints.
// codecPreferenceOrder: [ 'AV1', 'VP9', 'VP8', 'H264 ],
// codecPreferenceOrder: [ 'VP9', 'VP8', 'H264 ],
// Provides a way to set the codec for screenshare.
// screenshareCodec: 'AV1',
@@ -1125,12 +1068,24 @@ var config = {
// { urls: 'stun:jitsi-meet.example.com:3478' },
{ urls: 'stun:meet-jit-si-turnrelay.jitsi.net:443' },
],
// DEPRECATED! Use `codecPreferenceOrder/mobileCodecPreferenceOrder` instead.
// Provides a way to set the video codec preference on the p2p connection. Acceptable
// codec values are 'VP8', 'VP9' and 'H264'.
// preferredCodec: 'H264',
// DEPRECATED! Use `codecPreferenceOrder/mobileCodecPreferenceOrder` instead.
// Provides a way to prevent a video codec from being negotiated on the p2p connection.
// disabledCodec: '',
},
analytics: {
// True if the analytics should be disabled
// disabled: false,
// The Google Analytics Tracking ID:
// googleAnalyticsTrackingId: 'your-tracking-id-UA-123456-1',
// Matomo configuration:
// matomoEndpoint: 'https://your-matomo-endpoint/',
// matomoSiteID: '42',
@@ -1138,6 +1093,10 @@ var config = {
// The Amplitude APP Key:
// amplitudeAPPKey: '<APP_KEY>',
// Enables Amplitude UTM tracking:
// Default value is false.
// amplitudeIncludeUTM: false,
// Obfuscates room name sent to analytics (amplitude, rtcstats)
// Default value is false.
// obfuscateRoomName: false,
@@ -1164,6 +1123,7 @@ var config = {
// Array of script URLs to load as lib-jitsi-meet "analytics handlers".
// scriptURLs: [
// "libs/analytics-ga.min.js", // google-analytics
// "https://example.com/my-custom-analytics.js",
// ],
@@ -1249,7 +1209,6 @@ var config = {
// warning: '',
// },
// externallyManagedKey: false,
// disabled: false,
// },
// Options related to end-to-end (participant to participant) ping.
@@ -1281,6 +1240,9 @@ var config = {
// disableDeepLinking: false,
// The deeplinking config.
// For information about the properties of
// deeplinking.[ios/android].dynamicLink check:
// https://firebase.google.com/docs/dynamic-links/create-manually
// deeplinking: {
//
// // The desktop deeplinking config, disabled by default.
@@ -1309,6 +1271,13 @@ var config = {
// appScheme: 'org.jitsi.meet',
// // Custom URL for downloading ios mobile app.
// downloadLink: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',
// dynamicLink: {
// apn: 'org.jitsi.meet',
// appCode: 'w2atb',
// customDomain: undefined,
// ibi: 'com.atlassian.JitsiMeet.ios',
// isi: '1165103905'
// }
// },
// // The android deeplinking config.
@@ -1321,6 +1290,13 @@ var config = {
// // Android app package name.
// appPackage: 'org.jitsi.meet',
// fDroidUrl: 'https://f-droid.org/en/packages/org.jitsi.meet/',
// dynamicLink: {
// apn: 'org.jitsi.meet',
// appCode: 'w2atb',
// customDomain: undefined,
// ibi: 'com.atlassian.JitsiMeet.ios',
// isi: '1165103905'
// }
// }
// },
@@ -1368,13 +1344,18 @@ var config = {
// disableKick: true,
// // If set to true the 'Grant moderator' button will be disabled.
// disableGrantModerator: true,
// // If set to 'all' the 'Private chat' button will be disabled for all participants.
// // If set to 'allow-moderator-chat' the 'Private chat' button will be available for chats with moderators.
// // If set to 'disable-visitor-chat' the 'Private chat' button will be disabled for visitor-main participant
// // conversations.
// disablePrivateChat: 'all' | 'allow-moderator-chat' | 'disable-visitor-chat',
// // If set to true the 'Send private message' button will be disabled.
// disablePrivateChat: true,
// },
// Endpoint that enables support for salesforce integration with in-meeting resource linking
// This is required for:
// listing the most recent records - salesforceUrl/records/recents
// searching records - salesforceUrl/records?text=${text}
// retrieving record details - salesforceUrl/records/${id}?type=${type}
// and linking the meeting - salesforceUrl/sessions/${sessionId}/records/${id}
//
// salesforceUrl: 'https://api.example.com/',
// If set to true all muting operations of remote participants will be disabled.
// disableRemoteMute: true,
@@ -1385,12 +1366,8 @@ var config = {
The config file should be in JSON.
None of the fields are mandatory and the response must have the shape:
{
// Whether participant can only send group chat message if `send-groupchat` feature is enabled in jwt.
groupChatRequiresPermission: false,
// Whether participant can only create polls if `create-polls` feature is enabled in jwt.
pollCreationRequiresPermission: false,
// The domain url to apply (will replace the domain in the sharing conference link/embed section)
inviteDomain: 'example-company.org',
inviteDomain: 'example-company.org,
// The hex value for the colour used as background
backgroundColor: '#fff',
// The url for the image used as background
@@ -1399,13 +1376,6 @@ var config = {
logoClickUrl: 'https://example-company.org',
// The url used for the image used as logo
logoImageUrl: 'https://example.com/logo-img.png',
// Endpoint that enables support for salesforce integration with in-meeting resource linking
// This is required for:
// listing the most recent records - salesforceUrl/records/recents
// searching records - salesforceUrl/records?text=${text}
// retrieving record details - salesforceUrl/records/${id}?type=${type}
// and linking the meeting - salesforceUrl/sessions/${sessionId}/records/${id}
// salesforceUrl: 'https://api.example.com/',
// Overwrite for pool of background images for avatars
avatarBackgrounds: ['url(https://example.com/avatar-background-1.png)', '#FFF'],
// The lobby/prejoin screen background
@@ -1456,13 +1426,6 @@ var config = {
*/
// dynamicBrandingUrl: '',
// A list of allowed URL domains for shared video.
//
// NOTE:
// '*' is allowed value and it will allow any URL to be used for shared video. We do not recommend using '*',
// use it at your own risk!
// sharedVideoAllowedURLDomains: [ ],
// Options related to the participants pane.
// participantsPane: {
// // Enables feature
@@ -1585,71 +1548,26 @@ var config = {
// You can enable tokenAuthUrlAutoRedirect which will detect that you have logged in successfully before
// and will automatically redirect to the token service to get the token for the meeting.
// tokenAuthUrlAutoRedirect: false
// An option to respect the context.tenant jwt field compared to the current tenant from the url
// tokenRespectTenant: false,
// An option to get for user info (name, picture, email) in the token outside the user context.
// Can be used with Firebase tokens.
// tokenGetUserInfoOutOfContext: false,
// You can put an array of values to target different entity types in the invite dialog.
// Valid values are "phone", "room", "sip", "user", "videosipgw" and "email"
// peopleSearchQueryTypes: ["user", "email"],
// Directory endpoint which is called for invite dialog autocomplete
// peopleSearchUrl: "https://myservice.com/api/people",
// Endpoint which is called to send invitation requests
// inviteServiceUrl: "https://myservice.com/api/invite",
// For external entities (e. g. email), the localStorage key holding the token value for directory authentication
// peopleSearchTokenLocation: "mytoken",
// Options related to visitors.
// visitors: {
// // Starts audio/video when the participant is promoted from visitor.
// enableMediaOnPromote: {
// audio: true,
// video: true
// },
// // Hides the visitor count for visitors.
// // hideVisitorCountForVisitors: false,
// // Whether to show the join meeting dialog when joining as a visitor.
// // showJoinMeetingDialog: true,
// },
// The default type of desktop sharing sources that will be used in the electron app.
// desktopSharingSources: ['screen', 'window'],
// Disables the echo cancelation for local audio tracks.
// disableAEC: true,
// Disables the auto gain control for local audio tracks.
// disableAGC: true,
// Disables the audio processing (echo cancelation, auto gain control and noise suppression) for local audio tracks.
// disableAP: true,
// Disables the anoise suppression for local audio tracks.
// disableNS: true,
// Replaces the display name with the JID of the participants.
// displayJids: true,
// Enables disables talk while muted detection.
// enableTalkWhileMuted: true,
// Sets the peer connection ICE transport policy to "relay".
// forceTurnRelay: true,
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
debug
debugAudioLevels
deploymentInfo
dialOutAuthUrl
dialOutCodesUrl
dialOutRegionUrl
disableRemoteControl
displayJids
firefox_fake_device
googleApiApplicationClientID
iAmRecorder
iAmSipGateway
microsoftApiApplicationClientID
peopleSearchQueryTypes
peopleSearchUrl
requireDisplayName
*/
/**
@@ -1665,7 +1583,14 @@ var config = {
_peerConnStatusRtcMuteTimeout
avgRtpStatsN
desktopSharingSources
disableAEC
disableAGC
disableAP
disableHPF
disableLocalStats
disableNS
enableTalkWhileMuted
forceTurnRelay
hiddenDomain
hiddenFromRecorderFeatureEnabled
ignoreStartMuted
@@ -1742,7 +1667,7 @@ var config = {
// 'notify.participantsWantToJoin', // shown when lobby is enabled and participants request to join meeting
// 'notify.passwordRemovedRemotely', // shown when a password has been removed remotely
// 'notify.passwordSetRemotely', // shown when a password has been set remotely
// 'notify.raisedHand', // shown when a participant used raise hand,
// 'notify.raisedHand', // shown when a partcipant used raise hand,
// 'notify.screenShareNoAudio', // shown when the audio could not be shared for the selected screen
// 'notify.screenSharingAudioOnlyTitle', // shown when the best performance has been affected by screen sharing
// 'notify.selfViewTitle', // show "You can always un-hide the self-view from settings"
@@ -1763,7 +1688,7 @@ var config = {
// 'toolbar.noAudioSignalTitle', // shown when a broken mic is detected
// 'toolbar.noisyAudioInputTitle', // shown when noise is detected for the current microphone
// 'toolbar.talkWhileMutedPopup', // shown when user tries to speak while muted
// 'transcribing.failed', // shown when transcribing fails
// 'transcribing.failedToStart', // shown when transcribing fails to start
// ],
// List of notifications to be disabled. Works in tandem with the above setting.
@@ -1773,7 +1698,7 @@ var config = {
// disableFilmstripAutohiding: false,
// filmstrip: {
// // Disable the vertical/horizontal filmstrip.
// // Disable the vertical/horizonal filmstrip.
// disabled: false,
// // Disables user resizable filmstrip. Also, allows configuration of the filmstrip
// // (width, tiles aspect ratios) through the interfaceConfig options.
@@ -1793,13 +1718,6 @@ var config = {
// // The minimum number of participants that must be in the call for
// // the top panel layout to be used.
// minParticipantCountForTopPanel: 50,
// // The width of the filmstrip on joining meeting. Can be resized afterwards.
// initialWidth: 400,
// // Whether the draggable resize bar of the filmstrip is always visible. Setting this to true will make
// // the filmstrip always visible in case `disableResizable` is false.
// alwaysShowResizeBar: true,
// },
// Tile view related config options.
@@ -1829,6 +1747,8 @@ var config = {
// tileTime: 5000,
// // Limit results by rating: g, pg, pg-13, r. Default value: g.
// rating: 'pg',
// // The proxy server url for giphy requests in the web app.
// proxyUrl: 'https://giphy-proxy.example.com',
// },
// Logging
@@ -1839,10 +1759,9 @@ var config = {
// //disableLogCollector: true,
// // Individual loggers are customizable.
// loggers: {
// // The following are too verbose in their logging with the default level.
// 'modules/RTC/TraceablePeerConnection.js': 'info',
// 'modules/xmpp/strophe.util.js': 'log',
// },
// // The following are too verbose in their logging with the default level.
// 'modules/RTC/TraceablePeerConnection.js': 'info',
// 'modules/xmpp/strophe.util.js': 'log',
// },
// Application logo url
@@ -1898,21 +1817,14 @@ var config = {
// Hide login button on auth dialog, you may want to enable this if you are using JWT tokens to authenticate users
// hideLoginButton: true,
// If true remove the tint foreground on focused user camera in filmstrip
// disableCameraTintForeground: false,
// File sharign service.
// fileSharing: {
// // The URL of the file sharing service API. See resources/file-sharing.yaml for more details.
// apiUrl: 'https://example.com',
// // Whether the file sharing service is enabled or not.
// enabled: true,
// // Maximum file size limit (-1 value disables any file size limit check)
// maxFileSize: 50,
// },
};
// Temporary backwards compatibility with old mobile clients.
config.flags = config.flags || {};
config.flags.sourceNameSignaling = true;
config.flags.sendMultipleVideoStreams = true;
config.flags.receiveMultipleVideoStreams = true;
// Set the default values for JaaS customers
if (enableJaaS) {
config.dialInNumbersUrl = 'https://conference-mapper.jitsi.net/v1/access/dids';

View File

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

View File

@@ -28,7 +28,7 @@ body {
margin: 0px;
width: 100%;
height: 100%;
font-size: 0.75rem;
font-size: 12px;
font-weight: 400;
overflow: hidden;
color: #F1F1F1;
@@ -111,7 +111,7 @@ form {
height: $watermarkHeight;
background-size: contain;
background-repeat: no-repeat;
z-index: $watermarkZ;
z-index: $zindex2;
}
.leftwatermark {
@@ -139,10 +139,10 @@ form {
position: absolute;
left: 25;
bottom: 7;
font-size: 0.875rem;
font-size: 11pt;
color: rgba(255,255,255,.50);
text-decoration: none;
z-index: $watermarkZ;
z-index: 100;
}
/**

View File

@@ -1,8 +1,5 @@
@use './variables';
#chat-conversation-container {
// extract message input height
box-sizing: border-box;
height: calc(100% - 64px);
overflow: hidden;
position: relative;
@@ -11,9 +8,9 @@
#chatconversation {
box-sizing: border-box;
flex: 1;
font-size: 0.75rem;
height: calc(100% - 10px);
line-height: 1.25rem;
font-size: 10pt;
height: 100%;
line-height: 20px;
overflow: auto;
padding: 16px;
text-align: left;
@@ -22,13 +19,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;
}
@@ -72,7 +62,7 @@
#nickname {
text-align: center;
color: #9d9d9d;
font-size: 1rem;
font-size: 16px;
margin: auto 0;
padding: 0 16px;
@@ -86,7 +76,7 @@
}
label {
line-height: 1.5rem;
line-height: 24px;
}
}
@@ -98,7 +88,7 @@
}
.chatmessage .usermessage {
font-size: 1rem;
font-size: 16px;
}
}
@@ -124,7 +114,7 @@
}
#smileys {
font-size: 1.625rem;
font-size: 20pt;
margin: auto;
cursor: pointer;
}
@@ -141,8 +131,34 @@
left: 0;
}
.smileys-panel {
bottom: 100%;
box-sizing: border-box;
background-color: rgba(0, 0, 0, .6) !important;
height: auto;
display: flex;
overflow: hidden;
position: absolute;
width: calc(#{$sidebarWidth} - 32px);
margin-bottom: 5px;
margin-left: -5px;
/**
* CSS transitions do not apply for auto dimensions. So to produce the css
* accordion effect for showing and hiding the smiley-panel, while allowing
* for variable panel, height, use a very large max-height and animate off
* of that.
*/
transition: max-height 0.3s;
#smileysContainer {
background-color: $chatBackgroundColor;
border-top: 1px solid #A4B8D1;
}
}
#smileysContainer .smiley {
font-size: 1.625rem;
font-size: 20pt;
}
.smileyContainer {
@@ -193,8 +209,8 @@
box-sizing: border-box;
color: #fff;
font-weight: 600;
font-size: 1.5rem;
line-height: 2rem;
font-size: 24px;
line-height: 32px;
.jitsi-icon {
cursor: pointer;

View File

@@ -34,8 +34,8 @@
}
&__checkbox-label {
font-size: 0.875rem;
line-height: 1.125rem;
font-size: 14px;
line-height: 18px;
display: flex;
align-items: center;
letter-spacing: -0.006em;
@@ -51,8 +51,8 @@
}
&__text-container {
font-size: 0.875rem;
line-height: 1.125rem;
font-size: 14px;
line-height: 18px;
display: flex;
align-items: center;
letter-spacing: -0.006em;
@@ -84,8 +84,8 @@
&__button-text {
font-weight: 600;
font-size: 0.875rem;
line-height: 2.5rem;
font-size: 14px;
line-height: 40px;
text-align: center;
letter-spacing: -0.006em;
color: #FFFFFF;

View File

@@ -10,7 +10,7 @@
margin: 17px 0;
padding-bottom: 17px;
color: #ffffff;
font-size: 1.25rem;
font-size: 21px;
letter-spacing: 0.3px;
border-bottom: 1px solid lighten(#FFFFFF, 10%);
}
@@ -19,12 +19,12 @@
color: #ffffff;
display: block;
margin-top: 22px;
font-size: 1rem;
font-size: 16px;
}
&__icon {
margin: 0 10px;
font-size: 3.125rem;
font-size: 50px;
}
}

View File

@@ -1,14 +1,14 @@
.meetings-list {
font-size: 0.875rem;
font-size: 14px;
color: #253858;
line-height: 1.25rem;
line-height: 20px;
text-align: left;
text-overflow: ellipsis;
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: 0.875rem;
line-height: 1.125rem;
margin-bottom: 16px;
max-width: 436px;
color: #2f3237;
font-size: 14px;
line-height: 18px;
margin-bottom: 16px;
max-width: 436px;
}
}
@@ -37,8 +37,8 @@
color: #0163FF;
cursor: pointer;
display: flex;
font-size: 0.875rem;
line-height: 1.125rem;
font-size: 14px;
line-height: 18px;
margin: 24px 0 32px 0;
}
@@ -101,17 +101,17 @@
}
.title {
font-size: 0.75rem;
font-size: 12px;
font-weight: 600;
line-height: 1rem;
line-height: 16px;
margin-bottom: 4px;
}
.subtitle {
color: #5E6D7A;
font-weight: normal;
font-size: 0.75rem;
line-height: 1rem;
font-size: 12px;
line-height: 16px;
}
@@ -127,8 +127,7 @@
cursor: pointer;
}
&.with-click-handler:hover,
&.with-click-handler:focus {
&.with-click-handler:hover {
background-color: #c7ddff;
}
@@ -156,30 +155,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;
}
}
}
}
}

View File

@@ -1,7 +1,7 @@
%navigate-section-list-text {
width: 100%;
font-size: 0.875rem;
line-height: 1.25rem;
font-size: 14px;
line-height: 20px;
color: $welcomePageTitleColor;
text-align: left;
font-family: 'open_sanslight', Helvetica, sans-serif;
@@ -52,7 +52,7 @@
.navigate-section-tile-body {
@extend %navigate-section-list-tile-text;
font-weight: normal;
line-height: 1.5rem;
line-height: 24px;
}
.navigate-section-list-tile-info {
flex: 1;
@@ -61,7 +61,7 @@
.navigate-section-tile-title {
@extend %navigate-section-list-tile-text;
font-weight: bold;
line-height: 1.5rem;
line-height: 24px;
}
.navigate-section-section-header {
@extend %navigate-section-list-text;

View File

@@ -4,3 +4,9 @@
border-radius: 3px;
}
}
.mobile-browser.shift-right {
.participants_pane {
z-index: -1;
}
}

View File

@@ -8,8 +8,8 @@
&__text {
text-align: center;
font-size: 0.875rem;
line-height: 1.25rem;
font-size: 14px;
line-height: 21px;
font-weight: 300;
}
}

View File

@@ -1,14 +1,14 @@
@use 'sass:math';
.reactions-menu {
width: 330px;
width: 280px;
background: #242528;
box-shadow: 0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25);
border-radius: 6px;
padding: 16px;
&.with-gif {
width: 380px;
width: 328px;
.reactions-row .toolbox-button:last-of-type {
top: 3px;
@@ -55,7 +55,7 @@
span.emoji {
width: 40px;
height: 40px;
font-size: 1.375rem;
font-size: 22px;
display: flex;
align-items: center;
justify-content: center;
@@ -63,7 +63,7 @@
@for $i from 1 through 12 {
&.increase-#{$i}{
font-size: calc(1.25rem + #{$i}px);
font-size: calc(20px + #{$i}px);
}
}
}
@@ -96,8 +96,8 @@
span.text {
font-style: normal;
font-weight: 600;
font-size: 0.875rem;
line-height: 1.5rem;
font-size: 14px;
line-height: 24px;
margin-left: 8px;
}
}
@@ -132,8 +132,8 @@ $reactionCount: 20;
.reaction-emoji {
position: absolute;
font-size: 1.5rem;
line-height: 2rem;
font-size: 24px;
line-height: 32px;
width: 32px;
height: 32px;
top: 0;

View File

@@ -12,7 +12,7 @@
.recording-title {
display: inline-flex;
align-items: center;
font-size: 0.875rem;
font-size: 14px;
margin-left: 16px;
max-width: 70%;
@@ -35,8 +35,8 @@
.local-recording-warning {
margin-top: 8px;
display: block;
font-size: 0.875rem;
line-height: 1.25rem;
font-size: 14px;
line-height: 20px;
padding: 8px 16px;
&.text {
@@ -126,7 +126,7 @@
.recording-info-title {
display: inline-flex;
font-size: 0.875rem;
font-size: 14px;
width: 290px
}
@@ -150,7 +150,7 @@
/**
* Set font-size to be consistent with Atlaskit FieldText.
*/
font-size: 0.875rem;
font-size: 14px;
.broadcast-dropdown {
text-align: left;
@@ -194,6 +194,6 @@
.warning-text {
color:#FFD740;
font-size: 0.75rem;
font-size: 12px;
}
}

View File

@@ -2,7 +2,7 @@
width: 30%;
margin: 20% auto;
text-align: center;
font-size: 1.5rem;
font-size: 24px;
.thanks-msg {
border-bottom: 1px solid #FFFFFF;
@@ -10,16 +10,16 @@
padding-right: 30px;
p {
margin: 30px auto;
font-size: 1.5rem;
line-height: 1.5rem;
font-size: 24px;
line-height: 24px;
}
}
.hint-msg {
p {
margin: 26px auto;
font-weight: 600;
font-size: 1rem;
line-height: 1.125rem;
font-size: 16px;
line-height: 18px;
.hint-msg__holder{
font-weight: 200;
}
@@ -33,7 +33,7 @@
}
.forbidden-msg {
p {
font-size: 1rem;
font-size: 16px;
margin-top: 15px;
}
}

View File

@@ -70,7 +70,7 @@ input[type="reset"] {
body {
color: #333;
font-family: Arial, sans-serif;
font-size: 0.875rem;
font-size: 14px;
line-height: 1.42857142857143;
}
/* International Font Stacks*/
@@ -113,7 +113,7 @@ pre:first-child {
/* Headings: desired line height in px / font size = unitless line height */
h1 {
color: #333;
font-size: 2rem;
font-size: 32px;
font-weight: normal;
line-height: 1.25;
text-transform: none;
@@ -121,7 +121,7 @@ h1 {
}
h2 {
color: #333;
font-size: 1.5rem;
font-size: 24px;
font-weight: normal;
line-height: 1.25;
text-transform: none;
@@ -129,14 +129,14 @@ h2 {
}
h3 {
color: #333;
font-size: 1.25rem;
font-size: 20px;
font-weight: normal;
line-height: 1.5;
text-transform: none;
margin: 30px 0 0 0;
}
h4 {
font-size: 1rem;
font-size: 16px;
font-weight: bold;
line-height: 1.25;
text-transform: none;
@@ -144,7 +144,7 @@ h4 {
}
h5 {
color: #333;
font-size: 0.875rem;
font-size: 14px;
font-weight: bold;
line-height: 1.42857143;
text-transform: none;
@@ -152,7 +152,7 @@ h5 {
}
h6 {
color: #707070;
font-size: 0.75rem;
font-size: 12px;
font-weight: bold;
line-height: 1.66666667;
text-transform: uppercase;
@@ -179,7 +179,7 @@ h5 + h6 {
/* Other typographical elements */
small {
color: #707070;
font-size: 0.75rem;
font-size: 12px;
line-height: 1.33333333333333;
}
code,

View File

@@ -4,7 +4,7 @@
#enter_room {
.welcome-page-button {
font-size: 1rem;
font-size: 16px;
left: 0;
text-align: center;
width: 100%;
@@ -53,10 +53,25 @@
}
.welcome-footer-row-block {
display: flex;
flex-direction: column;
gap:12px;
align-items: center;
display: block;
}
}
}
.desktop-browser {
&.shift-right {
@media only screen and (max-width: $verySmallScreen + $sidebarWidth) {
#videoResolutionLabel {
display: none;
}
.vertical-filmstrip .filmstrip {
display: none;
}
.chrome-extension-banner {
display: none;
}
}
}
}

View File

@@ -9,9 +9,9 @@
// Do not inherit the font-family from the toolbar button, because it's an
// icon style.
font-family: $baseFontFamily;
font-size: 0.5rem;
font-size: 9px;
font-weight: 700;
line-height: 0.75rem;
line-height: 13px;
min-width: 13px;
overflow: hidden;
text-align: center;
@@ -69,8 +69,8 @@
.badge-round {
bottom: -5px;
font-size: 0.75rem;
line-height: 1.25rem;
font-size: 12px;
line-height: 20px;
min-width: 20px;
pointer-events: none;
position: absolute;

View File

@@ -0,0 +1,26 @@
.transcription-subtitles {
bottom: $newToolbarSize + 40px;
font-size: 16px;
font-weight: 1000;
left: 50%;
max-width: 50vw;
opacity: 0.80;
overflow-wrap: break-word;
pointer-events: none;
position: absolute;
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
0px 1px 1px rgba(0,0,0,0.3),
1px 0px 1px rgba(0,0,0,0.3),
0px 0px 1px rgba(0,0,0,0.3);
transform: translateX(-50%);
z-index: 7;
&.lifted {
// Lift subtitle above toolbar+dominant speaker box.
bottom: $newToolbarSize + 36px + 40px;
}
span {
background: black;
}
}

View File

@@ -22,6 +22,7 @@ $newToolbarSizeWithPadding: calc(#{$newToolbarSize} + 24px);
* Chat
*/
$chatBackgroundColor: #131519;
$sidebarWidth: 315px;
/**
* Misc.
@@ -37,8 +38,6 @@ $zindex1: 1;
$zindex2: 2;
$zindex3: 3;
$toolbarZ: 250;
$watermarkZ: 253;
// Place filmstrip videos over toolbar in order
// to make connection info visible.
$filmstripVideosZ: $toolbarZ + 1;
@@ -49,11 +48,11 @@ $filmstripVideosZ: $toolbarZ + 1;
$primaryUnsupportedBrowserButtonBgColor: #0052CC;
$unsupportedBrowserButtonBgColor: rgba(9, 30, 66, 0.04);
$unsupportedBrowserTextColor: #4a4a4a;
$unsupportedBrowserTextSmallFontSize: 1rem;
$unsupportedBrowserTextSmallFontSize: 17px;
$unsupportedBrowserTitleColor: #fff;
$unsupportedBrowserTitleFontSize: 1.5rem;
$unsupportedBrowserTitleFontSize: 24px;
$unsupportedDesktopBrowserTextColor: rgba(255, 255, 255, 0.7);
$unsupportedDesktopBrowserTextFontSize: 1.25rem;
$unsupportedDesktopBrowserTextFontSize: 21px;
/**
* The size of the default watermark.
@@ -76,21 +75,18 @@ $welcomePageHeaderBackground: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0,
$welcomePageHeaderBackgroundPosition: center;
$welcomePageHeaderBackgroundRepeat: none;
$welcomePageHeaderBackgroundSize: cover;
$welcomePageHeaderPadding: 1rem;
$welcomePageHeaderPaddingBottom: 15px;
$welcomePageHeaderTitleMaxWidth: initial;
$welcomePageHeaderTextAlign: center;
$welcomePageButtonBg: #0074E0;
$welcomePageButtonHoverBg: #4687ED;
$welcomePageButtonFocusOutline: #00225A;
$welcomePageHeaderContainerMarginTop: 104px;
$welcomePageHeaderContainerDisplay: flex;
$welcomePageHeaderContainerMargin: $welcomePageHeaderContainerMarginTop auto 0;
$welcomePageHeaderTextTitleMarginBottom: 0;
$welcomePageHeaderTextTitleFontSize: 2.625rem;
$welcomePageHeaderTextTitleFontSize: 42px;
$welcomePageHeaderTextTitleFontWeight: normal;
$welcomePageHeaderTextTitleLineHeight: 3.125rem;
$welcomePageHeaderTextTitleLineHeight: 50px;
$welcomePageHeaderTextTitleOpacity: 1;
$welcomePageEnterRoomDisplay: flex;

View File

@@ -160,7 +160,7 @@
}
#alwaysOnTop .displayname {
font-size: 0.875rem;
font-size: 15px;
position: inherit;
width: 100%;
left: 0px;
@@ -294,7 +294,7 @@
width: auto;
z-index: $zindex2;
font-weight: 600;
font-size: 0.875rem;
font-size: 14px;
text-align: center;
color: #FFF;
left: 50%;
@@ -340,7 +340,7 @@
.presence-label {
color: #fff;
font-size: 0.75rem;
font-size: 12px;
font-weight: 100;
left: 0;
margin: 0 auto;

View File

@@ -18,7 +18,7 @@ body.welcome-page {
background-position: $welcomePageHeaderBackgroundPosition;
background-repeat: $welcomePageHeaderBackgroundRepeat;
background-size: $welcomePageHeaderBackgroundSize;
padding: $welcomePageHeaderPadding;
padding-bottom: $welcomePageHeaderPaddingBottom;
background-color: #131519;
overflow: hidden;
position: relative;
@@ -53,9 +53,9 @@ body.welcome-page {
.header-text-subtitle {
color: #fff;
font-size: 1.25rem;
font-size: 20px;
font-weight: 600;
line-height: 1.625rem;
line-height: 26px;
margin: 16px 0 32px 0;
text-align: $welcomePageHeaderTextAlign;
@@ -64,7 +64,7 @@ body.welcome-page {
.not-allow-title-character-div {
color: #f03e3e;
background-color: #fff;
font-size: 0.75rem;
font-size: 12px;
font-weight: 600;
margin: 10px 0px 5px 0px;
text-align: $welcomePageHeaderTextAlign;
@@ -147,7 +147,7 @@ body.welcome-page {
display: inline-block;
height: 50px;
width: 100%;
font-size: 0.875rem;
font-size: 14px;
padding-left: 10px;
&.focus-visible {
@@ -172,7 +172,7 @@ body.welcome-page {
}
.tab-container {
font-size: 1rem;
font-size: 16px;
position: relative;
text-align: left;
display: $welcomePageTabContainerDisplay;
@@ -191,8 +191,8 @@ body.welcome-page {
background-color: #c7ddff;
border-radius: 6px;
color: #0163FF;
font-size: 0.875rem;
line-height: 1.125rem;
font-size: 14px;
line-height: 18px;
margin: 4px;
display: $welcomePageTabButtonsDisplay;
@@ -218,19 +218,15 @@ body.welcome-page {
.welcome-page-button {
border: 0;
font-size: 0.875rem;
background: $welcomePageButtonBg;
font-size: 14px;
background: #0074E0;
border-radius: 3px;
color: #FFFFFF;
cursor: pointer;
padding: 16px 20px;
transition: all 0.2s;
&:focus-within {
outline: auto 2px $welcomePageButtonFocusOutline;
}
&:hover {
background-color: $welcomePageButtonHoverBg;
&:focus-within {
outline: auto 2px #022e61;
}
}
@@ -246,7 +242,7 @@ body.welcome-page {
* {
cursor: pointer;
font-size: 2rem;
font-size: 32px;
}
.toolbox-icon {
@@ -268,7 +264,8 @@ body.welcome-page {
&.without-content {
.welcome-card {
max-width: 100dvw;
min-width: 500px;
max-width: 580px;
}
}
@@ -349,6 +346,6 @@ body.welcome-page {
.welcome-footer-row-1-text {
max-width: 200px;
text-align: center;
margin-right: 16px;
}
}

View File

@@ -91,3 +91,15 @@
}
}
}
.shift-right .remote-videos > div {
/**
* Max-width corresponding to the ASPECT_RATIO_BREAKPOINT from features/filmstrip/constants,
* from which we subtract the chat size.
*/
@media only screen and (max-width: calc(500px + #{$sidebarWidth})) {
video {
object-fit: cover;
}
}
}

View File

@@ -18,7 +18,8 @@
*/
#dominantSpeaker,
#largeVideoElementsContainer,
#sharedVideo {
#sharedVideo,
.stage-participant-label {
display: none;
}

View File

@@ -61,6 +61,7 @@ $flagsImagePath: "../images/";
@import 'filmstrip/vertical_filmstrip_overrides';
@import 'unsupported-browser/main';
@import 'deep-linking/main';
@import 'transcription-subtitles';
@import '_meetings_list.scss';
@import 'navigate_section_list';
@import 'third-party-branding/google';

View File

@@ -20,7 +20,7 @@
width: 28px;
i {
line-height: 1.75rem;
line-height: 28px;
margin: auto;
}
}
@@ -35,7 +35,7 @@
padding-left: 10px;
i {
line-height: 1.25rem;
line-height: 20px;
margin: auto;
}
}

View File

@@ -1,7 +1,7 @@
.info-dialog {
cursor: default;
display: flex;
font-size: 0.875rem;
font-size: 14px;
.info-dialog-column {
margin-right: 10px;
@@ -53,8 +53,8 @@
max-width: 334px;
width: 100%;
margin-top: 20px;
font-size: 0.75rem;
line-height: 1.5rem;
font-size: 12px;
line-height: 24px;
border-collapse: collapse;
* {
@@ -107,7 +107,7 @@
box-sizing: border-box;
display: flex;
flex-direction: column;
font-size: 0.75rem;
font-size: 12px;
max-height: 100%;
overflow: auto;
padding: 15pt;

View File

@@ -1,8 +1,8 @@
.invite-more {
&-dialog {
color: #fff;
font-size: 0.875rem;
line-height: 1.5rem;
font-size: 15px;
line-height: 24px;
&.separator {
margin: 24px 0 24px -20px;

View File

@@ -1,5 +1,5 @@
.share-screen-warn-dialog {
font-size: 0.875rem;
font-size: 14px;
.separator-line {
margin: 24px 0 24px -20px;

View File

@@ -1,15 +1,15 @@
.security {
&-dialog {
color: #fff;
font-size: 0.875rem;
line-height: 1.5rem;
font-size: 15px;
line-height: 24px;
&.password-section {
display: flex;
flex-direction: column;
.description {
font-size: 0.75rem;
font-size: 13px;
}
.password {
@@ -24,7 +24,7 @@
button {
cursor: pointer;
text-decoration: none;
font-size: 0.875rem;
font-size: 14px;
color: #6FB1EA;
}

View File

@@ -1,7 +1,7 @@
.lobby-screen {
font-size: 1rem;
font-size: 16px;
font-weight: 400;
line-height: 1.625rem;
line-height: 26px;
&-content {
align-items: center;
@@ -43,7 +43,7 @@
flex-direction: column;
.description {
font-size: 0.75rem;
font-size: 13px;
}
.control-row {
@@ -53,7 +53,7 @@
margin-top: 15px;
label {
font-size: 0.875rem;
font-size: 14px;
font-weight: bold;
}
}
@@ -191,9 +191,9 @@
.title {
flex: 1;
color: #fff;
font-size: 1.25rem;
font-size: 20px;
font-weight: 600;
line-height: 1.75rem;
line-height: 28px;
letter-spacing: -1.2%;
}
}
@@ -214,8 +214,8 @@
border-radius: 6px;
box-sizing: border-box;
color: white;
font-size: 0.75rem;
line-height: 1rem;
font-size: 12px;
line-height: 16px;
margin-bottom: 16px;
margin-top: -8px;
padding: 4px;

View File

@@ -5,9 +5,9 @@
color: #fff;
cursor: pointer;
display: inline-block;
font-size: 0.875rem;
font-size: 14px;
font-weight: 600;
line-height: 1.5rem;
line-height: 24px;
margin-bottom: 16px;
padding: 7px 16px;
position: relative;
@@ -26,7 +26,7 @@
&.text {
width: auto;
font-size: 0.75rem;
font-size: 13px;
margin: 0;
padding: 0;
}
@@ -98,7 +98,7 @@
}
.action-btn {
font-size: 1rem;
font-size: 16px;
margin-bottom: 8px;
padding: 11px 16px;
}

View File

@@ -1,13 +1,13 @@
.reload_overlay_title {
display: block;
font-size: 1rem;
line-height: 1.25rem;
font-size: 16px;
line-height: 20px;
}
.reload_overlay_text {
display: block;
font-size: 0.75rem;
line-height: 1.875rem;
font-size: 12px;
line-height: 30px;
}
#reloadProgressBar {

View File

@@ -34,12 +34,12 @@
&__status{
margin-top: 15px;
font-size: 0.875rem;
line-height: 1.25rem;
font-size: 14px;
line-height: 20px;
}
&__name {
font-size: 1.5rem;
line-height: 2rem;
font-size: 24px;
line-height: 32px;
}
}

View File

@@ -8,7 +8,7 @@
cursor: pointer;
display: inline-flex;
font-family: Roboto, arial, sans-serif;
font-size: 0.875rem;
font-size: 14px;
padding: 1px;
.google-cta {
@@ -17,7 +17,7 @@
/**
* Hack the line height for vertical centering of text.
*/
line-height: 2rem;
line-height: 32px;
margin: 0 15px;
}

View File

@@ -17,8 +17,8 @@
.microsoft-cta {
display: inline-block;
color: #5E5E5E;
font-size: 0.875rem;
line-height: 2.5rem;
font-size: 15px;
line-height: 41px;
}
.microsoft-logo {

7
debian/control vendored
View File

@@ -20,8 +20,7 @@ Description: WebRTC JavaScript video conferences
Package: jitsi-meet-web-config
Architecture: all
Pre-Depends: nginx | nginx-full | nginx-extras | openresty | apache2
Depends: openssl, curl
Depends: openssl, nginx | nginx-full | nginx-extras | openresty | apache2, curl
Description: Configuration for web serving of Jitsi Meet
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
Videobridge to provide high quality, scalable video conferences.
@@ -34,7 +33,7 @@ Description: Configuration for web serving of Jitsi Meet
Package: jitsi-meet-prosody
Architecture: all
Depends: openssl, prosody (>= 0.12.0) | prosody-trunk | prosody-0.12 | prosody-13.0, lua-sec, lua-basexx, lua-luaossl, lua-cjson, lua-inspect
Depends: openssl, prosody (>= 0.11.7) | prosody-trunk | prosody-0.12 | prosody-0.11, lua-sec, lua-basexx, lua-luaossl, lua-cjson, lua-inspect
Replaces: jitsi-meet-tokens
Description: Prosody configuration for Jitsi Meet
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
@@ -48,7 +47,7 @@ Description: Prosody configuration for Jitsi Meet
Package: jitsi-meet-tokens
Architecture: all
Depends: ${misc:Depends}, prosody-trunk | prosody-0.12 | prosody-13.0 | prosody (>= 0.12.0), jitsi-meet-prosody
Depends: ${misc:Depends}, prosody-trunk | prosody-0.11 | prosody-0.12 | prosody (>= 0.11.7), jitsi-meet-prosody
Description: Prosody token authentication plugin for Jitsi Meet
Package: jitsi-meet-turnserver

View File

@@ -131,6 +131,16 @@ case "$1" in
fi
fi
if [ "$PROSODY_CREATE_JICOFO_USER" = "true" ]; then
# create 'focus@auth.domain' prosody user
prosodyctl register $JICOFO_AUTH_USER $JICOFO_AUTH_DOMAIN $JICOFO_AUTH_PASSWORD
# trigger a restart
PROSODY_CONFIG_PRESENT="false"
fi
# creates the user if it does not exist
echo -e "$JVB_SECRET\n$JVB_SECRET" | prosodyctl adduser jvb@$JICOFO_AUTH_DOMAIN > /dev/null || true
# Check whether prosody config has the internal muc, if not add it,
# as we are migrating configs
if [ -f $PROSODY_HOST_CONFIG ] && ! grep -q "internal.$JICOFO_AUTH_DOMAIN" $PROSODY_HOST_CONFIG; then
@@ -154,16 +164,6 @@ case "$1" in
PROSODY_CONFIG_PRESENT="false"
fi
# Start using the polls component
if ! grep -q "Component \"polls.$JVB_HOSTNAME\"" $PROSODY_HOST_CONFIG ;then
echo -e "\nComponent \"polls.$JVB_HOSTNAME\" \"polls_component\"" >> $PROSODY_HOST_CONFIG
PROSODY_CONFIG_PRESENT="false"
fi
if ! grep -q -- '--"polls";' $PROSODY_HOST_CONFIG ;then
sed -i "s/\"polls\";/--\"polls\";/g" $PROSODY_HOST_CONFIG
PROSODY_CONFIG_PRESENT="false"
fi
# Old versions of jitsi-meet-prosody come with the extra plugin path commented out (https://github.com/jitsi/jitsi-meet/commit/e11d4d3101e5228bf956a69a9e8da73d0aee7949)
# Make sure it is uncommented, as it contains required modules.
if grep -q -- '--plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }' $PROSODY_HOST_CONFIG ;then
@@ -184,12 +184,6 @@ case "$1" in
PROSODY_CONFIG_PRESENT="false"
fi
# Since prosody 13 admins are not automatically room owners and we expect that for jicofo
if ! grep -q -- 'component_admins_as_room_owners = ' $PROSODY_HOST_CONFIG ;then
sed -i "1s/^/component_admins_as_room_owners = true\n/" $PROSODY_HOST_CONFIG
PROSODY_CONFIG_PRESENT="false"
fi
JAAS_HOST_CONFIG="/etc/prosody/conf.avail/jaas.cfg.lua"
if [ "${JAAS_INPUT}" = "true" ] && [ ! -f $JAAS_HOST_CONFIG ]; then
sed -i "s/enabled = false -- Jitsi meet components/enabled = true -- Jitsi meet components/g" $PROSODY_HOST_CONFIG
@@ -213,6 +207,9 @@ case "$1" in
fi
fi
# Make sure the focus@auth user's roster includes the proxy component (this is idempotent)
prosodyctl mod_roster_command subscribe focus.$JVB_HOSTNAME $JICOFO_AUTH_USER@$JICOFO_AUTH_DOMAIN
if [ ! -f /var/lib/prosody/$JVB_HOSTNAME.crt ]; then
# prosodyctl takes care for the permissions
# echo for using all default values
@@ -255,33 +252,9 @@ case "$1" in
if [ "$PROSODY_CONFIG_PRESENT" = "false" ]; then
invoke-rc.d prosody restart || true
# give it some time to warm up
sleep 10
if [ "$PROSODY_CREATE_JICOFO_USER" = "true" ]; then
# create 'focus@auth.domain' prosody user
echo -e "$JICOFO_AUTH_PASSWORD\n$JICOFO_AUTH_PASSWORD" | prosodyctl adduser $JICOFO_AUTH_USER@$JICOFO_AUTH_DOMAIN > /dev/null || true
# trigger a restart
PROSODY_CONFIG_PRESENT="false"
fi
# creates the user if it does not exist
echo -e "$JVB_SECRET\n$JVB_SECRET" | prosodyctl adduser jvb@$JICOFO_AUTH_DOMAIN > /dev/null || true
# Make sure the focus@auth user's roster includes the proxy component (this is idempotent)
prosodyctl mod_roster_command subscribe focus.$JVB_HOSTNAME $JICOFO_AUTH_USER@$JICOFO_AUTH_DOMAIN
# To make sure the roster command is loaded
# Once we have https://issues.prosody.im/1908 we can start using prosodyctl shell roster subscribe
# and drop the wait and the prosody restart
sleep 1
invoke-rc.d prosody restart || true
# In case we had updated the certificates and restarted prosody, let's restart and the bridge and jicofo if possible
# In case we had updated the certificates and restarted prosody, let's restart and the bridge if possible
if [ -d /run/systemd/system ] && [ "$CERT_ADDED_TO_TRUST" = "true" ]; then
systemctl restart jitsi-videobridge2.service >/dev/null || true
systemctl restart jicofo.service >/dev/null || true
fi
fi
;;

View File

@@ -175,6 +175,22 @@ case "$1" in
fi
fi
# Fixes multi-stream flags to workaround problem with mobile joining a multi-stream call with multi-stream disabled
FIX_MSG="// Temporary backwards compatibility with old mobile clients."
if ! grep -q "^${FIX_MSG}" $JITSI_MEET_CONFIG; then
echo $FIX_MSG >> $JITSI_MEET_CONFIG
echo "config.flags = config.flags || {};" >> $JITSI_MEET_CONFIG
fi
if ! grep -q "^config.flags.sourceNameSignaling*" $JITSI_MEET_CONFIG; then
echo "config.flags.sourceNameSignaling = true;" >> $JITSI_MEET_CONFIG
fi
if ! grep -q "^config.flags.sendMultipleVideoStreams*" $JITSI_MEET_CONFIG; then
echo "config.flags.sendMultipleVideoStreams = true;" >> $JITSI_MEET_CONFIG
fi
if ! grep -q "^config.flags.receiveMultipleVideoStreams*" $JITSI_MEET_CONFIG; then
echo "config.flags.receiveMultipleVideoStreams = true;" >> $JITSI_MEET_CONFIG
fi
if [[ "$FORCE_OPENRESTY" = "true" ]]; then
NGX_COMMON_CONF_PATH="/usr/local/openresty/nginx/conf/$JVB_HOSTNAME.conf"
NGX_SVC_NAME=openresty

View File

@@ -1,6 +1,3 @@
-- We need this for prosody 13.0
component_admins_as_room_owners = true
plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }
-- domain mapper options, must at least have domain base set to use the mapper
@@ -15,7 +12,6 @@ external_services = {
cross_domain_bosh = false;
consider_bosh_secure = true;
consider_websocket_secure = true;
-- https_ports = { }; -- Remove this line to prevent listening on port 5284
-- by default prosody 0.12 sends cors headers, if you want to disable it uncomment the following (the config is available on 0.12.1)
@@ -39,11 +35,6 @@ unlimited_jids = {
"jvb@auth.jitmeet.example.com"
}
-- https://prosody.im/doc/modules/mod_smacks
smacks_max_unacked_stanzas = 5;
smacks_hibernation_time = 60;
smacks_max_old_sessions = 1;
VirtualHost "jitmeet.example.com"
authentication = "jitsi-anonymous" -- do not delete me
-- Properties below are modified by jitsi-meet-tokens package config
@@ -58,21 +49,26 @@ VirtualHost "jitmeet.example.com"
key = "/etc/prosody/certs/jitmeet.example.com.key";
certificate = "/etc/prosody/certs/jitmeet.example.com.crt";
}
av_moderation_component = "avmoderation.jitmeet.example.com"
speakerstats_component = "speakerstats.jitmeet.example.com"
end_conference_component = "endconference.jitmeet.example.com"
-- we need bosh
modules_enabled = {
"bosh";
"websocket";
"smacks";
"ping"; -- Enable mod_ping
"speakerstats";
"external_services";
"features_identity";
"conference_duration";
"end_conference";
"muc_lobby_rooms";
"muc_breakout_rooms";
"av_moderation";
"room_metadata";
}
c2s_require_encryption = false
lobby_muc = "lobby.jitmeet.example.com"
breakout_rooms_muc = "breakout.jitmeet.example.com"
room_metadata_component = "metadata.jitmeet.example.com"
main_muc = "conference.jitmeet.example.com"
-- muc_lobby_whitelist = { "recorder.jitmeet.example.com" } -- Here we can whitelist jibri to enter lobby enabled rooms
@@ -83,6 +79,7 @@ Component "conference.jitmeet.example.com" "muc"
"muc_hide_all";
"muc_meeting_id";
"muc_domain_mapper";
"polls";
--"token_verification";
"muc_rate_limit";
"muc_password_whitelist";
@@ -122,17 +119,8 @@ Component "internal.auth.jitmeet.example.com" "muc"
VirtualHost "auth.jitmeet.example.com"
modules_enabled = {
"limits_exception";
"smacks";
}
authentication = "internal_hashed"
smacks_hibernation_time = 15;
VirtualHost "recorder.jitmeet.example.com"
modules_enabled = {
"smacks";
}
authentication = "internal_hashed"
smacks_max_old_sessions = 2000;
-- Proxy to jicofo's user JID, so that it doesn't have to register as a component.
Component "focus.jitmeet.example.com" "client_proxy"
@@ -147,9 +135,6 @@ Component "endconference.jitmeet.example.com" "end_conference"
Component "avmoderation.jitmeet.example.com" "av_moderation_component"
muc_component = "conference.jitmeet.example.com"
Component "filesharing.jitmeet.example.com" "filesharing_component"
muc_component = "conference.jitmeet.example.com"
Component "lobby.jitmeet.example.com" "muc"
storage = "memory"
restrict_room_creation = true
@@ -158,10 +143,9 @@ Component "lobby.jitmeet.example.com" "muc"
modules_enabled = {
"muc_hide_all";
"muc_rate_limit";
"polls";
}
Component "metadata.jitmeet.example.com" "room_metadata_component"
muc_component = "conference.jitmeet.example.com"
breakout_rooms_component = "breakout.jitmeet.example.com"
Component "polls.jitmeet.example.com" "polls_component"

View File

@@ -150,18 +150,10 @@ server {
# alias /usr/share/jitsi-meet/load-test/libs/$1;
#}
location = /_unlock {
add_header 'Access-Control-Allow-Origin' '*';
add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
add_header "Cache-Control" "no-cache, no-store";
}
location ~ ^/conference-request/v1(\/.*)?$ {
proxy_pass http://127.0.0.1:8888/conference-request/v1$1;
add_header "Cache-Control" "no-cache, no-store";
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Content-Type';
}
location ~ ^/([^/?&:'"]+)/conference-request/v1(\/.*)?$ {
rewrite ^/([^/?&:'"]+)/conference-request/v1(\/.*)?$ /conference-request/v1$2;

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