Compare commits

..

1 Commits

Author SHA1 Message Date
Aaron van Meerten
a49b6140e0 feature: empty token verification allow list 2022-12-15 09:02:48 -06:00
1857 changed files with 41137 additions and 54383 deletions

View File

@@ -9,8 +9,5 @@ indent_style = space
max_line_length = 80
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

View File

@@ -1,8 +1,6 @@
# The build artifacts of the jitsi-meet project.
build/*
doc/*
# Third-party source code which we (1) do not want to modify or (2) try to
# modify as little as possible.
libs/*

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

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

View File

@@ -1,25 +0,0 @@
name: Lua CI
on: [pull_request]
jobs:
luacheck:
name: Luacheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install luarocks
run: sudo apt-get --install-recommends -y install luarocks
- name: Install luacheck
run: sudo luarocks install luacheck
- name: Check lua codes
run: |
set -o pipefail && luacheck . | awk -F: '
{
print $0
printf "::warning file=%s,line=%s,col=%s::%s\n", $1, $2, $3, $4
}
'

View File

@@ -7,34 +7,25 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v35
- name: Get changed lang files
id: lang-files
run: echo "all=$(echo "${{ steps.changed-files.outputs.all_changed_files }}" | grep -oE 'lang\/\S+' | tr '\n' ' ')" >> "$GITHUB_OUTPUT"
- run: npm install
- name: Check git status
run: git status
- name: Normalize lang files to ensure sorted
if: steps.lang-files.outputs.all
run: npm run lang-sort
- name: Check lang files are formatted correctly
if: steps.lang-files.outputs.all
run: npm run lint:lang
- name: Check if the git repository is clean
run: $(exit $(git status --porcelain --untracked-files=no | head -255 | wc -l)) || (echo "Dirty git tree"; git diff; exit 1)
- run: npm run lint:ci && npm run tsc:ci
- run: npm run lint:ci
- run: for file in lang/*.json; do npx --yes jsonlint -q $file || exit 1; done
linux-build:
name: Build Frontend (Linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16
@@ -45,32 +36,10 @@ jobs:
name: Build Frontend (macOS)
runs-on: macOS-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
- run: npm install
- run: make
android-build:
name: Build mobile bundle (Android)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
- run: npm install
- run: npx react-native bundle --entry-file react/index.native.js --platform android --bundle-output /tmp/android.bundle --reset-cache
ios-build:
name: Build mobile bundle (iOS)
runs-on: macOS-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
- run: npm install
- run: npx react-native bundle --entry-file react/index.native.js --platform ios --bundle-output /tmp/ios.bundle --reset-cache

View File

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

17
.gitignore vendored
View File

@@ -61,9 +61,8 @@ buck-out/
# fastlane
#
**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/test_output
*/fastlane/report.xml
*/fastlane/Preview.html
# Build artifacts
*.jsbundle
@@ -94,15 +93,3 @@ twa/*.aab
twa/assetlinks.json
tsconfig.json
# React Native SDK
#
react-native-sdk/android/src
react-native-sdk/images
react-native-sdk/ios
react-native-sdk/lang
react-native-sdk/modules
react-native-sdk/node_modules
react-native-sdk/react
react-native-sdk/service
react-native-sdk/sounds

View File

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

View File

@@ -44,8 +44,12 @@ deploy-appbundle:
cp \
$(BUILD_DIR)/app.bundle.min.js \
$(BUILD_DIR)/app.bundle.min.js.map \
$(BUILD_DIR)/do_external_connect.min.js \
$(BUILD_DIR)/do_external_connect.min.js.map \
$(BUILD_DIR)/external_api.min.js \
$(BUILD_DIR)/external_api.min.js.map \
$(BUILD_DIR)/dial_in_info_bundle.min.js \
$(BUILD_DIR)/dial_in_info_bundle.min.js.map \
$(BUILD_DIR)/alwaysontop.min.js \
$(BUILD_DIR)/alwaysontop.min.js.map \
$(OUTPUT_DIR)/analytics-ga.js \
@@ -63,7 +67,11 @@ deploy-appbundle:
deploy-lib-jitsi-meet:
cp \
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.* \
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.min.js \
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.min.map \
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.e2ee-worker.js \
$(LIBJITSIMEET_DIR)/connection_optimization/external_connect.js \
$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
$(DEPLOY_DIR)
deploy-olm:
@@ -123,7 +131,7 @@ dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-mode
source-package:
mkdir -p source_package/jitsi-meet/css && \
cp -r *.js *.html resources/*.txt favicon.ico fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
cp -r *.js *.html resources/*.txt connection_optimization favicon.ico fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
cp css/all.css source_package/jitsi-meet/css && \
(cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \
rm -rf source_package

View File

@@ -7,6 +7,7 @@
android:extractNativeLibs="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".MainApplication"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme">
<meta-data
@@ -16,8 +17,7 @@
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleInstance"
android:taskAffinity=""
android:launchMode="singleTask"
android:name=".MainActivity"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"

View File

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

View File

@@ -1,70 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="262.91376dp"
android:height="262.91376dp"
android:viewportWidth="262.91376"
android:viewportHeight="262.91376">
<group android:scaleX="0.75" android:scaleY="0.75" android:translateX="35" android:translateY="35">
<clip-path
android:pathData="m0,0 l262.914,-0L262.914,262.914 0,262.914 0,0Z"/>
<path
android:pathData="m142.646,105.099c0.117,0.026 0.255,0.036 0.406,0.036 3.186,-0 10.297,-4.615 11.617,-6.721l0.1,-0.17 0.153,-0.135c0.451,-0.441 1.746,-2.773 2.374,-4.17 -6.751,-2.023 -7.49,-5.677 -8.153,-8.919 -0.069,-0.376 -0.138,-0.717 -0.204,-1.019 -0.074,-0.397 -0.153,-0.8 -0.226,-1.112C138.668,86.221 135.593,88.094 133.921,89.483 133.056,90.201 132.542,92.251 135.042,97.926 136.323,100.816 140.727,104.733 142.646,105.099"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"/>
<path
android:pathData="m115.413,146.042c5.934,-0 18.464,-3.543 26.748,-5.887 1.21,-0.336 2.33,-0.66 3.351,-0.944 0.166,-0.046 0.321,-0.091 0.472,-0.124 -0.463,-0.461 -1.239,-1.159 -2.497,-2.216 -5.521,-3.741 -10.736,-5.484 -16.403,-5.484 -1.237,-0 -2.522,0.071 -3.923,0.231 -4.801,0.55 -8.8,1.69 -10.722,2.237 -0.967,0.284 -1.263,0.366 -1.567,0.366 -0.58,-0 -1.079,-0.341 -1.273,-0.878 -0.194,-0.534 -0.027,-1.121 0.425,-1.507l0.024,-0.011c3.316,-2.784 9.489,-7.951 21.198,-10.256 2.027,-0.401 4.202,-0.605 6.454,-0.605 5.242,-0 10.67,1.086 16.125,3.219 7.436,2.899 12.521,6.625 16.602,9.62 2.199,1.609 4.105,3.007 5.755,3.771 0.421,0.2 0.637,0.255 0.746,0.265 0.074,-0.095 0.23,-0.365 0.474,-1.069 0.066,-0.185 0.529,-2.161 -2.806,-13.374 -1.931,-6.51 -4.264,-13.156 -5.479,-16.104 -2.356,-5.711 -1.778,-9.76 -1.051,-12.125 -1.999,0.735 -4.033,1.87 -6.174,3.446L161.758,98.711C160.694,99.506 159.599,100.404 158.426,101.454 151.517,107.64 146.344,110.864 143.035,111.04l-0.093,0.004 -0.093,-0.009c-2.912,-0.245 -7.324,-4.489 -9.133,-6.634 -0.373,-0.251 -0.8,-0.366 -1.366,-0.366 -0.564,-0 -1.202,0.116 -1.82,0.235C130.086,104.354 129.623,104.441 129.167,104.489 127.708,104.632 125.668,105.106 123.694,105.561 122.746,105.777 121.762,106.005 120.864,106.189 120.851,106.19 120.463,106.272 119.774,106.454 114.903,107.891 111.228,109.55 109.432,111.111 109.414,111.127 109.352,111.174 109.266,111.242 108.048,112.105 105.124,114.567 104.248,118.762L104.237,118.795C102.398,126.516 105.187,136.087 108.892,141.554 110.636,144.125 112.513,145.727 114.048,145.959 114.437,146.015 114.891,146.042 115.413,146.042"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"/>
<path
android:pathData="m90.093,173.175c-1.252,-1.472 -1.783,-3.324 -1.574,-5.521 0.884,-10.642 -0.329,-13.215 -0.891,-13.829 -0.131,-0.144 -0.207,-0.144 -0.265,-0.144 -0.022,-0 -0.041,0.003 -0.064,0.003 -1.044,0.248 -8.066,5.002 -9.615,19.171 -0.749,6.845 0.561,15.63 1.679,20.974 0.897,-3.155 2.314,-6.624 5.057,-10.204 2.556,-3.326 5.345,-5.955 8.801,-8.253C92.143,174.93 90.991,174.235 90.093,173.175"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"/>
<path
android:pathData="m94.906,156.389c-0.03,2.229 -0.326,4.36 -0.61,6.445 -0.151,1.119 -0.314,2.286 -0.434,3.46 -0.161,2.341 0.346,3.166 0.571,3.406 0.127,0.136 0.326,0.287 0.76,0.287 0.339,-0 0.741,-0.091 1.161,-0.268 4.202,-1.756 8.195,-4.815 10.115,-6.515C103.522,161.892 98.995,159.058 94.906,156.389"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"/>
<path
android:pathData="m154.002,81.595c-0.031,0.074 -0.065,0.148 -0.101,0.216 -0.821,2.403 0.306,5.664 2.419,6.898 0.561,0.327 1.106,0.526 1.624,0.596 0.072,0.006 0.148,0.009 0.219,0.009 1.645,-0 2.971,-1.199 3.961,-3.561C162.752,83.959 162.836,81.827 162.37,79.904 162.003,78.409 161.057,76.627 160.453,75.738 159.332,76.509 157.111,78.207 155.585,79.553 154.518,80.582 154.136,81.229 154.002,81.595"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"/>
<path
android:pathData="M148.97,77.699C153.957,73.194 156.988,65.754 158.253,61.334 153.915,65.513 148.633,67.758 145.25,69.198 144.084,69.695 143.08,70.124 142.477,70.476 142.224,70.623 141.965,70.77 141.708,70.919 139.654,72.109 136.55,73.905 136.1,75.011l-0.012,0.036 -0.012,0.034c-1.406,2.956 -2.199,7.401 -2.457,9.95 3.266,-1.99 6.625,-3.322 9.416,-4.42C145.628,79.585 147.863,78.703 148.97,77.699"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"/>
<path
android:pathData="m164.464,51.921c-0.84,5.539 -2.205,10.799 -4.751,16.347 2.781,-3.144 4.396,-6.568 4.941,-10.401C164.886,56.275 165.097,54.756 164.464,51.921"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"/>
<path
android:pathData="M148.749,142.639C148.718,142.598 148.684,142.56 148.658,142.519 148.523,142.539 148.307,142.584 147.972,142.683l-0.14,0.04c-1.726,0.644 -4.899,1.708 -8.556,2.946 -4.396,1.479 -9.365,3.154 -13.526,4.649 -5.297,1.975 -7.021,2.755 -7.557,3.024 -0.098,0.266 -0.203,0.599 -0.327,0.965 -1.254,3.816 -4.125,12.541 -18.276,18.653 2.928,2.956 9.289,8.27 21.809,8.27 1.082,-0 2.21,-0.036 3.341,-0.12 9.451,-0.666 18.342,-4.855 25.026,-11.78 6.087,-6.291 9.538,-14.136 9.585,-21.7C157.876,147.509 155.367,147.135 153.043,146.033 153.014,146.02 150.361,144.745 148.749,142.639"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"/>
<path
android:pathData="m189.478,117.853c-0.523,9.749 -2.122,18.424 -4.744,25.8 -2.128,5.988 -4.94,11.134 -8.356,15.316 -5.676,6.931 -11.555,9.256 -12.804,9.304 -0.866,-0 -1.313,-0.309 -3.046,-1.528 -0.17,-0.114 -0.37,-0.252 -0.581,-0.4 -3.313,5.953 -8.505,11.097 -15.065,14.959 -7.079,4.144 -15.297,6.423 -23.157,6.423 -9.078,-0 -17.13,-2.924 -23.341,-8.456 -7.467,4.799 -12.31,9.074 -16.267,27.005l-1.363,6.17 -2.971,-5.564c-0.424,-0.786 -1.929,-3.731 -3.332,-8.887 -1.934,-7.104 -2.86,-15.181 -2.758,-24.01 0.117,-10.049 3.154,-16.526 5.68,-20.186 2.98,-4.314 6.837,-6.994 10.076,-6.994 0.216,-0 0.428,0.006 0.616,0.035 5.159,0.575 8.435,2.75 14.396,6.686l1.899,1.252c2.059,1.344 4.481,2.7 5.259,2.989 0.54,-0.284 1.749,-2.3 2.155,-5.271l0.069,-0.451c0.005,-0.045 0.009,-0.091 0.014,-0.131 -0.036,-0.02 -0.065,-0.029 -0.094,-0.041 -4.008,-1.375 -9.539,-7.7 -12.364,-17.134 -2.684,-9.382 -2.129,-17.185 1.644,-23.193 6.12,-9.736 19.198,-11.974 23.466,-12.702 1.331,-0.266 2.716,-0.511 4.041,-0.717 0.255,-0.061 0.469,-0.121 0.642,-0.168 -0.031,-0.126 -0.071,-0.265 -0.114,-0.43 -0.108,-0.417 -0.23,-0.891 -0.354,-1.447 -1.345,-6.035 -0.664,-11.069 0.181,-15.193 0.928,-4.546 1.489,-7.287 3.747,-9.936 3.029,-4.165 8.319,-5.936 11.479,-6.991 0.746,-0.249 1.511,-0.509 1.894,-0.689 8.988,-4.31 11.82,-8.739 12.615,-11.694 0.656,-2.451 1.699,-8.884 1.251,-13.335 -0.085,-0.805 0.129,-1.521 0.621,-2.065 0.45,-0.505 1.101,-0.794 1.778,-0.794 1.515,-0 2.82,-0 7.511,14.598 2.481,7.698 0.645,14.903 -5.45,21.424l-0.226,0.231c0.024,0.044 0.049,0.09 0.08,0.144 2.57,4.236 3.963,9.54 3.553,13.51 -0.099,0.906 -0.265,1.775 -0.419,2.549 -0.003,0.01 -0.003,0.016 -0.004,0.029 0.516,-0.032 1.119,-0.055 1.775,-0.055 3.052,-0 7.435,0.474 10.989,2.735 2.135,1.352 4.845,3.439 6.835,7.615C189.223,102.942 190.076,109.575 189.478,117.853m4.77,-23.191c-2.916,-6.1 -6.989,-9.177 -9.793,-10.96 -2.355,-1.494 -5.064,-2.584 -8.077,-3.24l-0.676,-0.146 -0.111,-0.689c-0.339,-2.119 -0.918,-4.275 -1.715,-6.406l-0.185,-0.49 0.292,-0.434c5.095,-7.594 6.323,-16.17 3.54,-24.802 -2.191,-6.824 -3.895,-11.211 -5.341,-13.799 -2.954,-5.305 -7.006,-6.417 -9.891,-6.417 -2.964,-0 -5.8,1.261 -7.789,3.457 -2.043,2.254 -2.993,5.207 -2.678,8.31 0.316,3.134 -0.494,8.516 -1.014,10.439 -0.04,0.117 -0.975,2.929 -8.201,6.428 -0.162,0.056 -0.512,0.179 -1.053,0.359 -3.729,1.246 -10.666,3.571 -15.258,9.64 -3.465,4.205 -4.332,8.441 -5.338,13.346 -0.586,2.865 -1.236,6.744 -1.079,11.344l0.026,0.841 -0.824,0.188c-11.646,2.585 -20.025,7.835 -24.909,15.605 -5.054,8.04 -5.919,18.055 -2.543,29.853 0.063,0.204 0.126,0.407 0.189,0.615l0.527,1.608 -1.665,-0.286c-0.561,-0.101 -1.135,-0.18 -1.729,-0.241 -0.493,-0.06 -1.001,-0.082 -1.509,-0.082 -5.633,-0 -11.663,3.585 -16.128,9.592 -3.451,4.641 -7.588,12.849 -7.735,25.601 -0.114,9.573 0.906,18.401 3.038,26.228 1.581,5.795 3.326,9.329 4.004,10.577l13.306,24.94 6.096,-27.619c2.454,-11.09 4.864,-15.262 7.725,-18.111l0.561,-0.563 0.679,0.411c6.605,3.977 14.466,6.084 22.73,6.084 9.286,-0 18.965,-2.682 27.259,-7.551 5.38,-3.16 9.974,-7.036 13.649,-11.531l0.45,-0.369 0.85,-0.02c2.156,-0.068 5.16,-1.164 8.222,-3.004 2.6,-1.555 6.543,-4.428 10.501,-9.262 3.997,-4.884 7.274,-10.854 9.716,-17.734 2.876,-8.073 4.625,-17.489 5.204,-28.004 0.689,-9.668 -0.434,-17.641 -3.327,-23.704"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"/>
<path
android:pathData="m180.026,98.414c-1.67,-2.596 -3.771,-4.206 -5.475,-4.206 -0.313,-0 -0.613,0.051 -0.895,0.161 -0.911,0.361 -2.356,4.532 -1.714,7.566 0.434,2.066 2.938,9.04 4.151,12.394 0.456,1.281 0.68,1.91 0.754,2.142 0.064,0.183 0.145,0.448 0.256,0.774 0.97,2.971 3.467,10.586 4.206,16.761 1.549,-6.579 2.424,-14.512 2.085,-23.997C183.235,105.662 182.04,101.538 180.026,98.414"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"/>
<path
android:pathData="M168.088,142.604C169.896,142.111 171.33,141.705 172.398,141.395 170.213,139.874 167.689,137.979 164.247,135.304c-8.418,-6.546 -17.449,-9.87 -26.839,-9.87 -5.135,-0 -9.611,0.991 -13.156,2.186 0.882,-0.05 1.779,-0.079 2.7,-0.079 1.1,-0 2.247,0.04 3.411,0.119 3.652,0.246 13.061,1.901 21.565,12.047 1.714,2.039 3.559,3.73 8.794,3.73 1.873,-0 4.051,-0.207 6.662,-0.645C167.544,142.751 167.793,142.678 168.088,142.604"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"/>
<path
android:pathData="m164.3,147.583c-0.122,1.563 -0.376,4.509 -0.782,6.76 -0.495,2.719 -1.31,5.02 -1.791,6.226 0.85,0.786 1.694,1.553 2.247,2.043 2.214,-1.447 9.47,-6.96 14.483,-19.474C176.847,144.229 174.59,145.178 171.671,146.018 168.701,146.861 165.82,147.357 164.3,147.583"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"/>
</group>
</vector>

View File

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

View File

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

View File

@@ -10,17 +10,16 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.1'
classpath 'com.android.tools.build:gradle:7.0.4'
classpath 'com.google.gms:google-services:4.3.14'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
}
}
ext {
kotlinVersion = "1.7.0"
buildToolsVersion = "31.0.0"
compileSdkVersion = 32
minSdkVersion = 24
minSdkVersion = 23
targetSdkVersion = 32
supportLibVersion = "28.0.0"

View File

@@ -26,5 +26,5 @@ android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
appVersion=23.2.0
sdkVersion=8.2.0
appVersion=99.0.0
sdkVersion=99.0.0

View File

@@ -33,7 +33,7 @@ if [[ $MVN_HTTP == 1 ]]; then
deploy:deploy-file \
-Durl=${MVN_REPO} \
-DrepositoryId=${MVN_REPO_ID} \
-Dfile=react-native-${RN_VERSION}-release.aar \
-Dfile=react-native-${RN_VERSION}.aar \
-Dpackaging=aar \
-DgeneratePom=false \
-DpomFile=react-native-${RN_VERSION}.pom || true
@@ -58,7 +58,7 @@ else
mvn \
deploy:deploy-file \
-Durl=${MVN_REPO} \
-Dfile=react-native-${RN_VERSION}-release.aar \
-Dfile=react-native-${RN_VERSION}.aar \
-Dpackaging=aar \
-DgeneratePom=false \
-DpomFile=react-native-${RN_VERSION}.pom

View File

@@ -76,6 +76,7 @@ dependencies {
implementation project(':react-native-get-random-values')
implementation project(':react-native-immersive')
implementation project(':react-native-keep-awake')
implementation project(':react-native-masked-view_masked-view')
implementation project(':react-native-orientation-locker')
implementation project(':react-native-pager-view')
implementation project(':react-native-performance')

View File

@@ -25,7 +25,6 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.module.annotations.ReactModule;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
@@ -33,10 +32,7 @@ import java.util.Map;
class AppInfoModule
extends ReactContextBaseJavaModule {
private static final String BUILD_CONFIG = "org.jitsi.meet.sdk.BuildConfig";
public static final String NAME = "AppInfo";
public static final boolean GOOGLE_SERVICES_ENABLED = getGoogleServicesEnabled();
public static final boolean LIBRE_BUILD = getLibreBuild();
public AppInfoModule(ReactApplicationContext reactContext) {
super(reactContext);
@@ -79,8 +75,8 @@ class AppInfoModule
constants.put(
"version",
packageInfo == null ? "" : packageInfo.versionName);
constants.put("LIBRE_BUILD", LIBRE_BUILD);
constants.put("GOOGLE_SERVICES_ENABLED", GOOGLE_SERVICES_ENABLED);
constants.put("LIBRE_BUILD", BuildConfig.LIBRE_BUILD);
constants.put("GOOGLE_SERVICES_ENABLED", BuildConfig.GOOGLE_SERVICES_ENABLED);
return constants;
}
@@ -89,47 +85,4 @@ class AppInfoModule
public String getName() {
return NAME;
}
/**
* Checks if libre google services object is null based on build configuration.
*/
private static boolean getGoogleServicesEnabled() {
Object googleServicesEnabled = getBuildConfigValue("GOOGLE_SERVICES_ENABLED");
if (googleServicesEnabled !=null) {
return (Boolean) googleServicesEnabled;
}
return false;
}
/**
* Checks if libre build field is null based on build configuration.
*/
private static boolean getLibreBuild() {
Object libreBuild = getBuildConfigValue("LIBRE_BUILD");
if (libreBuild !=null) {
return (Boolean) libreBuild;
}
return false;
}
/**
* Gets build config value of a certain field.
*
* @param fieldName Field from build config.
*/
private static Object getBuildConfigValue(String fieldName) {
try {
Class<?> c = Class.forName(BUILD_CONFIG);
Field f = c.getDeclaredField(fieldName);
f.setAccessible(true);
return f.get(null);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

View File

@@ -45,12 +45,6 @@ class AudioDeviceHandlerGeneric implements
*/
private AudioModeModule module;
/**
* Constant defining a Hearing Aid. Only available on API level >= 28.
* The value of: AudioDeviceInfo.TYPE_HEARING_AID
*/
private static final int TYPE_HEARING_AID = 23;
/**
* Constant defining a USB headset. Only available on API level >= 26.
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
@@ -91,7 +85,6 @@ class AudioDeviceHandlerGeneric implements
break;
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
case TYPE_HEARING_AID:
case TYPE_USB_HEADSET:
devices.add(AudioModeModule.DEVICE_HEADPHONES);
break;

View File

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

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
package org.jitsi.meet.sdk;
import org.webrtc.VideoCodecInfo;
import java.util.Map;
import java.util.HashMap;
/** Container for static helper functions related to dealing with H264 codecs. */
class H264Utils {
public static final String H264_FMTP_PROFILE_LEVEL_ID = "profile-level-id";
public static final String H264_FMTP_LEVEL_ASYMMETRY_ALLOWED = "level-asymmetry-allowed";
public static final String H264_FMTP_PACKETIZATION_MODE = "packetization-mode";
public static final String H264_PROFILE_CONSTRAINED_BASELINE = "42e0";
public static final String H264_PROFILE_CONSTRAINED_HIGH = "640c";
public static final String H264_LEVEL_3_1 = "1f"; // 31 in hex.
public static final String H264_CONSTRAINED_HIGH_3_1 =
H264_PROFILE_CONSTRAINED_HIGH + H264_LEVEL_3_1;
public static final String H264_CONSTRAINED_BASELINE_3_1 =
H264_PROFILE_CONSTRAINED_BASELINE + H264_LEVEL_3_1;
public static Map<String, String> getDefaultH264Params(boolean isHighProfile) {
final Map<String, String> params = new HashMap<>();
params.put(VideoCodecInfo.H264_FMTP_LEVEL_ASYMMETRY_ALLOWED, "1");
params.put(VideoCodecInfo.H264_FMTP_PACKETIZATION_MODE, "1");
params.put(VideoCodecInfo.H264_FMTP_PROFILE_LEVEL_ID,
isHighProfile ? VideoCodecInfo.H264_CONSTRAINED_HIGH_3_1
: VideoCodecInfo.H264_CONSTRAINED_BASELINE_3_1);
return params;
}
public static VideoCodecInfo DEFAULT_H264_BASELINE_PROFILE_CODEC =
new VideoCodecInfo("H264", getDefaultH264Params(/* isHighProfile= */ false));
public static VideoCodecInfo DEFAULT_H264_HIGH_PROFILE_CODEC =
new VideoCodecInfo("H264", getDefaultH264Params(/* isHighProfile= */ true));
}

View File

@@ -16,7 +16,6 @@
package org.jitsi.meet.sdk;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -178,11 +177,8 @@ public class JitsiMeetActivity extends AppCompatActivity
}
protected void leave() {
if (this.jitsiView != null) {
this.jitsiView.abort();
} else {
JitsiMeetLogger.w("Cannot leave, view is null");
}
Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
}
private @Nullable
@@ -299,7 +295,6 @@ public class JitsiMeetActivity extends AppCompatActivity
JitsiMeetActivityDelegate.requestPermissions(this, permissions, requestCode, listener);
}
@SuppressLint("MissingSuperCall")
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);

View File

@@ -81,8 +81,6 @@ public class JitsiMeetView extends FrameLayout {
result.putBoolean(key, (Boolean)bValue);
} else if (valueType.contentEquals("String")) {
result.putString(key, (String)bValue);
} else if (valueType.contentEquals("Integer")) {
result.putInt(key, (int)bValue);
} else if (valueType.contentEquals("Bundle")) {
result.putBundle(key, mergeProps((Bundle)aValue, (Bundle)bValue));
} else {
@@ -157,14 +155,6 @@ public class JitsiMeetView extends FrameLayout {
setProps(options != null ? options.asProps() : new Bundle());
}
/**
* Internal method which aborts running RN by passing empty props.
* This is only meant to be used from the enclosing Activity's onDestroy.
*/
public void abort() {
setProps(new Bundle());
}
/**
* Creates the {@code ReactRootView} for the given app name with the given
* props. Once created it's set as the view of this {@code FrameLayout}.

View File

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

View File

@@ -10,19 +10,14 @@ import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
@@ -40,7 +35,6 @@ class RNConnectionService extends ReactContextBaseJavaModule {
private static final String TAG = ConnectionService.TAG;
private static RNConnectionService sRNConnectionServiceInstance;
/**
* Handler for dealing with call state changes. We are acting as a proxy between ConnectionService
* and other modules such as {@link AudioModeModule}.
@@ -63,11 +57,6 @@ class RNConnectionService extends ReactContextBaseJavaModule {
RNConnectionService(ReactApplicationContext reactContext) {
super(reactContext);
sRNConnectionServiceInstance = this;
}
static RNConnectionService getInstance() {
return sRNConnectionServiceInstance;
}
@ReactMethod
@@ -237,22 +226,4 @@ class RNConnectionService extends ReactContextBaseJavaModule {
interface CallAudioStateListener {
void onCallAudioStateChange(android.telecom.CallAudioState callAudioState);
}
/**
* Helper function to send an event to JavaScript.
*
* @param eventName {@code String} containing the event name.
* @param data {@code Object} optional ancillary data for the event.
*/
void emitEvent(
String eventName,
@Nullable Object data) {
ReactContext reactContext = getReactApplicationContext();
if (reactContext != null) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, data);
}
}
}

View File

@@ -17,6 +17,7 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.Application;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -31,17 +32,17 @@ 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 com.oney.WebRTCModule.RTCVideoViewManager;
import com.oney.WebRTCModule.WebRTCModule;
import org.devio.rn.splashscreen.SplashScreenModule;
import org.webrtc.EglBase;
import org.webrtc.audio.AudioDeviceModule;
import org.webrtc.audio.JavaAudioDeviceModule;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
class ReactInstanceManagerHolder {
@@ -72,17 +73,37 @@ class ReactInstanceManagerHolder {
new SplashScreenModule(reactContext),
new PictureInPictureModule(reactContext),
new ProximityModule(reactContext),
new WiFiStatsModule(reactContext),
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)));
if (AudioModeModule.useConnectionService()) {
nativeModules.add(new RNConnectionService(reactContext));
}
// Initialize the WebRTC module by hand, since we want to override some
// initialization options.
WebRTCModule.Options options = new WebRTCModule.Options();
AudioDeviceModule adm = JavaAudioDeviceModule.builder(reactContext)
.setEnableVolumeLogger(false)
.createAudioDeviceModule();
options.setAudioDeviceModule(adm);
EglBase.Context eglContext = EglUtils.getRootEglBaseContext();
options.setVideoDecoderFactory(new WebRTCVideoDecoderFactory(eglContext));
options.setVideoEncoderFactory(new WebRTCVideoEncoderFactory(eglContext));
nativeModules.add(new WebRTCModule(reactContext, options));
return nativeModules;
}
private static List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
return Arrays.<ViewManager>asList(
// WebRTC, see createNativeModules for details.
new RTCVideoViewManager()
);
}
static List<ReactPackage> getReactNativePackages() {
@@ -99,11 +120,11 @@ class ReactInstanceManagerHolder {
new com.oblador.performance.PerformancePackage(),
new com.reactnativecommunity.slider.ReactSliderPackage(),
new com.brentvatne.react.ReactVideoPackage(),
new org.reactnative.maskedview.RNCMaskedViewPackage(),
new com.reactnativecommunity.webview.RNCWebViewPackage(),
new com.kevinresol.react_native_default_preference.RNDefaultPreferencePackage(),
new com.learnium.RNDeviceInfo.RNDeviceInfo(),
new com.oney.WebRTCModule.WebRTCModulePackage(),
new com.swmansion.gesturehandler.RNGestureHandlerPackage(),
new com.swmansion.gesturehandler.react.RNGestureHandlerPackage(),
new org.linusu.RNGetRandomValuesPackage(),
new com.rnimmersive.RNImmersivePackage(),
new com.swmansion.rnscreens.RNScreensPackage(),
@@ -220,6 +241,35 @@ class ReactInstanceManagerHolder {
return reactInstanceManager;
}
/**
* Internal method to initialize the React Native instance manager. We
* create a single instance in order to load the JavaScript bundle a single
* time. All {@code ReactRootView} instances will be tied to the one and
* only {@code ReactInstanceManager}.
*
* This method is only meant to be called when integrating with {@code JitsiReactNativeHost}.
*
* @param app {@code Application} current running Application.
*/
static void initReactInstanceManager(Application app) {
if (reactInstanceManager != null) {
return;
}
Log.d(TAG, "initializing RN with Application");
reactInstanceManager
= ReactInstanceManager.builder()
.setApplication(app)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index.android")
.setJavaScriptExecutorFactory(getReactNativeJSFactory())
.addPackages(getReactNativePackages())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
.build();
}
/**
* Internal method to initialize the React Native instance manager. We
* create a single instance in order to load the JavaScript bundle a single
@@ -233,15 +283,7 @@ class ReactInstanceManagerHolder {
return;
}
// Initialize the WebRTC module options.
WebRTCModuleOptions options = WebRTCModuleOptions.getInstance();
EglBase.Context eglContext = EglUtils.getRootEglBaseContext();
options.videoDecoderFactory = new H264AndSoftwareVideoDecoderFactory(eglContext);
options.videoEncoderFactory = new H264AndSoftwareVideoEncoderFactory(eglContext);
Log.d(TAG, "initializing RN with Activity");
Log.d(ReactInstanceManagerHolder.class.getCanonicalName(), "initializing RN with Activity");
reactInstanceManager
= ReactInstanceManager.builder()

View File

@@ -0,0 +1,19 @@
package org.jitsi.meet.sdk;
/** Enumeration of supported video codec types. */
public enum VideoCodecMimeType {
VP8("video/x-vnd.on2.vp8"),
VP9("video/x-vnd.on2.vp9"),
H264("video/avc"),
AV1("video/av01");
private final String mimeType;
private VideoCodecMimeType(String mimeType) {
this.mimeType = mimeType;
}
String mimeType() {
return mimeType;
}
}

View File

@@ -0,0 +1,52 @@
package org.jitsi.meet.sdk;
import androidx.annotation.Nullable;
import org.webrtc.EglBase;
import org.webrtc.HardwareVideoDecoderFactory;
import org.webrtc.SoftwareVideoDecoderFactory;
import org.webrtc.VideoCodecInfo;
import org.webrtc.VideoDecoder;
import org.webrtc.VideoDecoderFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* This is a custom video decoder factory for WebRTC which behaves similarly
* to the default one in iOS. It supports the following codecs:
*
* - In hardware: H.264 (baseline)
* - In software: VP8, VP9, AV1
*/
public class WebRTCVideoDecoderFactory implements VideoDecoderFactory {
private final VideoDecoderFactory hardwareVideoDecoderFactory;
private final VideoDecoderFactory softwareVideoDecoderFactory = new SoftwareVideoDecoderFactory();
public WebRTCVideoDecoderFactory(@Nullable EglBase.Context eglContext) {
this.hardwareVideoDecoderFactory = new HardwareVideoDecoderFactory(eglContext);
}
@Nullable
@Override
public VideoDecoder createDecoder(VideoCodecInfo codecInfo) {
if (codecInfo.name.equalsIgnoreCase(VideoCodecMimeType.H264.name())) {
return this.hardwareVideoDecoderFactory.createDecoder(codecInfo);
}
return this.softwareVideoDecoderFactory.createDecoder(codecInfo);
}
@Override
public VideoCodecInfo[] getSupportedCodecs() {
List<VideoCodecInfo> codecs = new ArrayList<>();
codecs.add(H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC);
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP8.name(), new HashMap<>()));
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP9.name(), new HashMap<>()));
codecs.add(new VideoCodecInfo(VideoCodecMimeType.AV1.name(), new HashMap<>()));
return codecs.toArray(new VideoCodecInfo[codecs.size()]);
}
}

View File

@@ -0,0 +1,53 @@
package org.jitsi.meet.sdk;
import androidx.annotation.Nullable;
import org.webrtc.EglBase;
import org.webrtc.HardwareVideoEncoderFactory;
import org.webrtc.SoftwareVideoEncoderFactory;
import org.webrtc.VideoCodecInfo;
import org.webrtc.VideoEncoder;
import org.webrtc.VideoEncoderFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* This is a custom video encoder factory for WebRTC which behaves similarly
* to the default one in iOS. It supports the following codecs:
*
* - In hardware: H.264 (baseline)
* - In software: VP8, VP9, AV1
*/
public class WebRTCVideoEncoderFactory implements VideoEncoderFactory {
private final VideoEncoderFactory hardwareVideoEncoderFactory;
private final VideoEncoderFactory softwareVideoEncoderFactory = new SoftwareVideoEncoderFactory();
public WebRTCVideoEncoderFactory(@Nullable EglBase.Context eglContext) {
this.hardwareVideoEncoderFactory =
new HardwareVideoEncoderFactory(eglContext, false, false);
}
@Nullable
@Override
public VideoEncoder createEncoder(VideoCodecInfo codecInfo) {
if (codecInfo.name.equalsIgnoreCase(VideoCodecMimeType.H264.name())) {
return this.hardwareVideoEncoderFactory.createEncoder(codecInfo);
}
return this.softwareVideoEncoderFactory.createEncoder(codecInfo);
}
@Override
public VideoCodecInfo[] getSupportedCodecs() {
List<VideoCodecInfo> codecs = new ArrayList<>();
codecs.add(H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC);
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP8.name(), new HashMap<>()));
codecs.add(new VideoCodecInfo(VideoCodecMimeType.VP9.name(), new HashMap<>()));
codecs.add(new VideoCodecInfo(VideoCodecMimeType.AV1.name(), new HashMap<>()));
return codecs.toArray(new VideoCodecInfo[codecs.size()]);
}
}

View File

@@ -0,0 +1,203 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Module exposing WiFi statistics.
*
* Gathers rssi, signal in percentage, timestamp and the addresses of the wifi
* device.
*/
@ReactModule(name = WiFiStatsModule.NAME)
class WiFiStatsModule
extends ReactContextBaseJavaModule {
public static final String NAME = "WiFiStats";
/**
* The {@code Log} tag {@code WiFiStatsModule} is to log messages with.
*/
static final String TAG = NAME;
/**
* The scale used for the signal value. A level of the signal, given in the
* range of 0 to SIGNAL_LEVEL_SCALE-1 (both inclusive).
*/
public final static int SIGNAL_LEVEL_SCALE = 101;
/**
* {@link ExecutorService} for running all operations on a dedicated thread.
*/
private static final ExecutorService executor
= Executors.newSingleThreadExecutor();
/**
* Initializes a new module instance. There shall be a single instance of
* this module throughout the lifetime of the application.
*
* @param reactContext the {@link ReactApplicationContext} where this module
* is created.
*/
public WiFiStatsModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Gets the name for this module to be used in the React Native bridge.
*
* @return a string with the module name.
*/
@Override
public String getName() {
return NAME;
}
/**
* Returns the {@link InetAddress} represented by this int.
*
* @param value the int representation of the ip address.
* @return the {@link InetAddress}.
* @throws UnknownHostException - if IP address is of illegal length.
*/
public static InetAddress toInetAddress(int value)
throws UnknownHostException {
return InetAddress.getByAddress(
new byte[] {
(byte) value,
(byte) (value >> 8),
(byte) (value >> 16),
(byte) (value >> 24)
});
}
/**
* Public method to retrieve WiFi stats.
*
* @param promise a {@link Promise} which will be resolved if WiFi stats are
* retrieved successfully, and it will be rejected otherwise.
*/
@ReactMethod
public void getWiFiStats(final Promise promise) {
Runnable r = new Runnable() {
@Override
public void run() {
try {
Context context
= getReactApplicationContext().getApplicationContext();
WifiManager wifiManager
= (WifiManager) context
.getSystemService(Context.WIFI_SERVICE);
if (!wifiManager.isWifiEnabled()) {
promise.reject(new Exception("Wifi not enabled"));
return;
}
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (wifiInfo.getNetworkId() == -1) {
promise.reject(new Exception("Wifi not connected"));
return;
}
int rssi = wifiInfo.getRssi();
int signalLevel
= WifiManager.calculateSignalLevel(
rssi, SIGNAL_LEVEL_SCALE);
JSONObject result = new JSONObject();
result.put("rssi", rssi)
.put("signal", signalLevel)
.put("timestamp", System.currentTimeMillis());
JSONArray addresses = new JSONArray();
InetAddress wifiAddress
= toInetAddress(wifiInfo.getIpAddress());
try {
Enumeration<NetworkInterface> e
= NetworkInterface.getNetworkInterfaces();
while (e.hasMoreElements()) {
NetworkInterface networkInterface = e.nextElement();
boolean found = false;
// first check whether this is the desired interface
Enumeration<InetAddress> as
= networkInterface.getInetAddresses();
while (as.hasMoreElements()) {
InetAddress a = as.nextElement();
if(a.equals(wifiAddress)) {
found = true;
break;
}
}
if (found) {
// interface found let's put addresses
// to the result object
as = networkInterface.getInetAddresses();
while (as.hasMoreElements()) {
InetAddress a = as.nextElement();
if (a.isLinkLocalAddress())
continue;
addresses.put(a.getHostAddress());
}
}
}
} catch (SocketException e) {
JitsiMeetLogger.e(e, TAG + " Unable to NetworkInterface.getNetworkInterfaces()");
}
result.put("addresses", addresses);
promise.resolve(result.toString());
JitsiMeetLogger.d(TAG + " WiFi stats: " + result.toString());
} catch (Throwable e) {
JitsiMeetLogger.e(e, TAG + " Failed to obtain wifi stats");
promise.reject(
new Exception("Failed to obtain wifi stats"));
}
}
};
executor.execute(r);
}
}

View File

@@ -31,6 +31,8 @@ include ':react-native-immersive'
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
include ':react-native-keep-awake'
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
include ':react-native-masked-view_masked-view'
project(':react-native-masked-view_masked-view').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-masked-view/masked-view/android')
include ':react-native-orientation-locker'
project(':react-native-orientation-locker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-orientation-locker/android')
include ':react-native-pager-view'

13
app.js
View File

@@ -18,6 +18,7 @@ import './react/features/base/jitsi-local-storage/setup';
import conference from './conference';
import API from './modules/API';
import UI from './modules/UI/UI';
import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
import translation from './modules/translation/translation';
// Initialize Olm as early as possible.
@@ -32,11 +33,23 @@ window.APP = {
API,
conference,
// Used by do_external_connect.js if we receive the attach data after
// connect was already executed. status property can be 'initialized',
// 'ready', or 'connecting'. We are interested in 'ready' status only which
// means that connect was executed but we have to wait for the attach data.
// In status 'ready' handler property will be set to a function that will
// finish the connect process when the attach data or error is received.
connect: {
handler: null,
status: 'initialized'
},
// Used for automated performance tests.
connectionTimes: {
'index.loaded': window.indexLoadedTime
},
keyboardshortcut,
translation,
UI
};

View File

@@ -17,9 +17,9 @@ import {
createDeviceChangedEvent,
createScreenSharingEvent,
createStartSilentEvent,
createTrackMutedEvent
} from './react/features/analytics/AnalyticsEvents';
import { sendAnalytics } from './react/features/analytics/functions';
createTrackMutedEvent,
sendAnalytics
} from './react/features/analytics';
import {
maybeRedirectToWelcomePage,
redirectToStaticPage,
@@ -28,8 +28,13 @@ import {
import { showModeratedNotification } from './react/features/av-moderation/actions';
import { shouldShowModeratedNotification } from './react/features/av-moderation/functions';
import {
AVATAR_URL_COMMAND,
CONFERENCE_LEAVE_REASONS,
EMAIL_COMMAND,
_conferenceWillJoin,
authStatusChanged,
commonUserJoinedHandling,
commonUserLeftHandling,
conferenceFailed,
conferenceJoinInProgress,
conferenceJoined,
@@ -42,26 +47,15 @@ import {
dataChannelClosed,
dataChannelOpened,
e2eRttChanged,
generateVisitorConfig,
getConferenceOptions,
kickedOut,
lockStateChanged,
nonParticipantMessageReceived,
onStartMutedPolicyChanged,
p2pStatusChanged
} from './react/features/base/conference/actions';
import {
AVATAR_URL_COMMAND,
CONFERENCE_LEAVE_REASONS,
EMAIL_COMMAND
} from './react/features/base/conference/constants';
import {
commonUserJoinedHandling,
commonUserLeftHandling,
getConferenceOptions,
getVisitorOptions,
restoreConferenceOptions,
p2pStatusChanged,
sendLocalParticipant
} from './react/features/base/conference/functions';
import { overwriteConfig } from './react/features/base/config/actions';
} from './react/features/base/conference';
import { getReplaceParticipant } from './react/features/base/config/functions';
import {
checkAndNotifyForNewDevice,
@@ -87,85 +81,77 @@ import {
} from './react/features/base/lib-jitsi-meet';
import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-meet/functions';
import {
gumPending,
MEDIA_TYPE,
getStartWithAudioMuted,
getStartWithVideoMuted,
isVideoMutedByUser,
setAudioAvailable,
setAudioMuted,
setAudioUnmutePermissions,
setVideoAvailable,
setVideoMuted,
setVideoUnmutePermissions
} from './react/features/base/media/actions';
import { MEDIA_TYPE } from './react/features/base/media/constants';
import {
getStartWithAudioMuted,
getStartWithVideoMuted,
isVideoMutedByUser
} from './react/features/base/media/functions';
import { IGUMPendingState } from './react/features/base/media/types';
} from './react/features/base/media';
import {
dominantSpeakerChanged,
getLocalParticipant,
getNormalizedDisplayName,
getVirtualScreenshareParticipantByOwnerId,
localParticipantAudioLevelChanged,
localParticipantRoleChanged,
participantKicked,
participantMutedUs,
participantPresenceChanged,
participantRoleChanged,
participantSourcesUpdated,
participantUpdated,
screenshareParticipantDisplayNameChanged,
updateRemoteParticipantFeatures
} from './react/features/base/participants/actions';
import {
getLocalParticipant,
getNormalizedDisplayName,
getVirtualScreenshareParticipantByOwnerId
} from './react/features/base/participants/functions';
import { updateSettings } from './react/features/base/settings/actions';
} from './react/features/base/participants';
import { updateSettings } from './react/features/base/settings';
import {
addLocalTrack,
destroyLocalTracks,
replaceLocalTrack,
toggleScreensharing as toggleScreensharingA,
trackAdded,
trackRemoved
} from './react/features/base/tracks/actions';
import {
createLocalTracksF,
destroyLocalTracks,
getLocalJitsiAudioTrack,
getLocalJitsiVideoTrack,
getLocalTracks,
getLocalVideoTrack,
isLocalTrackMuted,
isUserInteractionRequiredForUnmute
} from './react/features/base/tracks/functions';
isUserInteractionRequiredForUnmute,
replaceLocalTrack,
toggleScreensharing as toggleScreensharingA,
trackAdded,
trackRemoved
} from './react/features/base/tracks';
import { downloadJSON } from './react/features/base/util/downloadJSON';
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 { showDesktopPicker } from './react/features/desktop-picker';
import { appendSuffix } from './react/features/display-name';
import {
maybeOpenFeedbackDialog,
submitFeedback
} from './react/features/feedback';
import { maybeSetLobbyChatMessageListener } from './react/features/lobby/actions.any';
import { setNoiseSuppressionEnabled } from './react/features/noise-suppression/actions';
import { hideNotification, showNotification, showWarningNotification } from './react/features/notifications/actions';
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';
NOTIFICATION_TIMEOUT_TYPE,
hideNotification,
isModerationNotificationDisplayed,
showNotification,
showWarningNotification
} from './react/features/notifications';
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
import { suspendDetected } from './react/features/power-monitor';
import { initPrejoin, makePrecallTest, setJoiningInProgress } from './react/features/prejoin/actions';
import { 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 { disableReceiver, stopReceiver } from './react/features/remote-control';
import { isScreenAudioShared, setScreenAudioShareState } from './react/features/screen-share/';
import { toggleScreenshotCaptureSummary } from './react/features/screenshot-capture';
import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect';
import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
import { endpointMessageReceived } from './react/features/subtitles/actions.any';
import { endpointMessageReceived } from './react/features/subtitles';
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
import { muteLocal } from './react/features/video-menu/actions.any';
import { setIAmVisitor } from './react/features/visitors/actions';
import { iAmVisitor } from './react/features/visitors/functions';
import UIEvents from './service/UI/UIEvents';
const logger = Logger.getLogger(__filename);
@@ -353,30 +339,21 @@ class ConferenceConnector {
}
case JitsiConferenceErrors.REDIRECTED: {
const newConfig = getVisitorOptions(APP.store.getState(), params);
generateVisitorConfig(APP.store.getState(), params);
if (!newConfig) {
logger.warn('Not redirected missing params');
break;
}
connection.disconnect().then(() => {
connect(this._conference.roomName).then(con => {
const localTracks = getLocalTracks(APP.store.getState()['features/base/tracks']);
const [ vnode ] = params;
const jitsiTracks = localTracks.map(t => t.jitsiTrack);
APP.store.dispatch(overwriteConfig(newConfig))
.then(() => this._conference.leaveRoom())
.then(() => APP.store.dispatch(setIAmVisitor(Boolean(vnode))))
// visitors connect muted
jitsiTracks.forEach(t => t.mute());
// we do not clear local tracks on error, so we need to manually clear them
.then(() => APP.store.dispatch(destroyLocalTracks()))
.then(() => {
// Reset VideoLayout. It's destroyed in features/video-layout/middleware.web.js so re-initialize it.
VideoLayout.initLargeVideo();
VideoLayout.resizeVideoArea();
connect(this._conference.roomName).then(con => {
this._conference.startConference(con, []);
});
// TODO disable option to unmute audio or video
this._conference.startConference(con, jitsiTracks);
});
});
break;
}
@@ -411,28 +388,10 @@ class ConferenceConnector {
room.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).then(() => connection.disconnect());
break;
case JitsiConferenceErrors.CONFERENCE_MAX_USERS: {
case JitsiConferenceErrors.CONFERENCE_MAX_USERS:
APP.UI.notifyMaxUsersLimitReached();
// in case of max users(it can be from a visitor node), let's restore
// oldConfig if any as we will be back to the main prosody
const newConfig = restoreConferenceOptions(APP.store.getState());
if (newConfig) {
APP.store.dispatch(overwriteConfig(newConfig))
.then(() => this._conference.leaveRoom())
.then(() => {
_connectionPromise = connect(this._conference.roomName);
return _connectionPromise;
})
.then(con => {
APP.connection = connection = con;
});
}
break;
}
case JitsiConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS:
APP.store.dispatch(reloadWithStoredParams());
break;
@@ -495,21 +454,6 @@ function disconnect() {
return connection.disconnect().then(onDisconnected, onDisconnected);
}
/**
* Sets the GUM pending state for the tracks that have failed.
*
* NOTE: Some of the track that we will be setting to GUM pending state NONE may not have failed but they may have
* been requested. This won't be a problem because their current GUM pending state will be NONE anyway.
* @param {JitsiLocalTrack} tracks - The tracks that have been created.
* @returns {void}
*/
function setGUMPendingStateOnFailedTracks(tracks) {
const tracksTypes = tracks.map(track => track.getType());
const nonPendingTracks = [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ].filter(type => !tracksTypes.includes(type));
APP.store.dispatch(gumPending(nonPendingTracks, IGUMPendingState.NONE));
}
/**
* Handles CONNECTION_FAILED events from lib-jitsi-meet.
*
@@ -576,7 +520,7 @@ export default {
);
}
let tryCreateLocalTracks = Promise.resolve([]);
let tryCreateLocalTracks;
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
// spend much time displaying the overlay screen. If GUM is not resolved within 15 seconds it will
@@ -617,66 +561,76 @@ export default {
return [];
});
} else if (requestedAudio || requestedVideo) {
APP.store.dispatch(gumPending(initialDevices, IGUMPendingState.PENDING_UNMUTE));
} else if (!requestedAudio && !requestedVideo) {
// Resolve with no tracks
tryCreateLocalTracks = Promise.resolve([]);
} else {
tryCreateLocalTracks = createLocalTracksF({
devices: initialDevices,
timeout,
firePermissionPromptIsShownEvent: true
})
.catch(async error => {
if (error.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
errors.audioAndVideoError = error;
.catch(err => {
if (requestedAudio && requestedVideo) {
// Try audio only...
errors.audioAndVideoError = err;
if (err.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
// In this case we expect that the permission prompt is still visible. There is no point of
// executing GUM with different source. Also at the time of writing the following
// inconsistency have been noticed in some browsers - if the permissions prompt is visible
// and another GUM is executed the prompt does not change its content but if the user
// clicks allow the user action isassociated with the latest GUM call.
errors.audioOnlyError = err;
errors.videoOnlyError = err;
return [];
}
return createLocalTracksF(audioOptions);
} else if (requestedAudio && !requestedVideo) {
errors.audioOnlyError = err;
return [];
} else if (requestedVideo && !requestedAudio) {
errors.videoOnlyError = err;
return [];
}
logger.error('Should never happen');
})
.catch(err => {
// Log this just in case...
if (!requestedAudio) {
logger.error('The impossible just happened', err);
}
errors.audioOnlyError = err;
// Try video only...
return requestedVideo
? createLocalTracksF({
devices: [ MEDIA_TYPE.VIDEO ],
firePermissionPromptIsShownEvent: true
})
: [];
})
.catch(err => {
// Log this just in case...
if (!requestedVideo) {
logger.error('The impossible just happened', err);
}
errors.videoOnlyError = err;
return [];
}
// Retry with separate gUM calls.
const gUMPromises = [];
const tracks = [];
if (requestedAudio) {
gUMPromises.push(createLocalTracksF(audioOptions));
}
if (requestedVideo) {
gUMPromises.push(createLocalTracksF({
devices: [ MEDIA_TYPE.VIDEO ],
timeout,
firePermissionPromptIsShownEvent: true
}));
}
const results = await Promise.allSettled(gUMPromises);
let errorMsg;
results.forEach((result, idx) => {
if (result.status === 'fulfilled') {
tracks.push(result.value[0]);
} else {
errorMsg = result.reason;
const isAudio = idx === 0;
logger.error(`${isAudio ? 'Audio' : 'Video'} track creation failed with error ${errorMsg}`);
if (isAudio) {
errors.audioOnlyError = errorMsg;
} else {
errors.videoOnlyError = errorMsg;
}
}
});
if (errors.audioOnlyError && errors.videoOnlyError) {
errors.audioAndVideoError = errorMsg;
}
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.
// Hide the permissions prompt/overlay as soon as the tracks are
// created. Don't wait for the connection to be made, since in some
// cases, when auth is required, for instance, that won't happen until
// the user inputs their credentials, but the dialog would be
// overshadowed by the overlay.
tryCreateLocalTracks.then(tracks => {
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
@@ -813,55 +767,46 @@ export default {
* @returns {Promise}
*/
async init({ roomName }) {
const state = APP.store.getState();
const initialOptions = {
startAudioOnly: config.startAudioOnly,
startScreenSharing: config.startScreenSharing,
startWithAudioMuted: getStartWithAudioMuted(state) || isUserInteractionRequiredForUnmute(state),
startWithVideoMuted: getStartWithVideoMuted(state) || isUserInteractionRequiredForUnmute(state)
startWithAudioMuted: getStartWithAudioMuted(APP.store.getState())
|| isUserInteractionRequiredForUnmute(APP.store.getState()),
startWithVideoMuted: getStartWithVideoMuted(APP.store.getState())
|| isUserInteractionRequiredForUnmute(APP.store.getState())
};
this.roomName = roomName;
try {
// Initialize the device list first. This way, when creating tracks based on preferred devices, loose label
// matching can be done in cases where the exact ID match is no longer available, such as -
// 1. When the camera device has switched USB ports.
// 2. When in startSilent mode we want to start with audio muted
// Initialize the device list first. This way, when creating tracks
// based on preferred devices, loose label matching can be done in
// cases where the exact ID match is no longer available, such as
// when the camera device has switched USB ports.
// when in startSilent mode we want to start with audio muted
await this._initDeviceList();
} catch (error) {
logger.warn('initial device list initialization failed', error);
}
// Filter out the local tracks based on various config options, i.e., when user joins muted or is muted by
// focus. However, audio track will always be created even though it is not added to the conference since we
// want audio related features (noisy mic, talk while muted, etc.) to work even if the mic is muted.
const handleInitialTracks = (options, tracks) => {
let localTracks = tracks;
// No local tracks are added when user joins as a visitor.
if (iAmVisitor(state)) {
return [];
}
if (options.startWithAudioMuted || room?.isStartAudioMuted()) {
const handleStartAudioMuted = (options, tracks) => {
if (options.startWithAudioMuted) {
// Always add the track on Safari because of a known issue where audio playout doesn't happen
// if the user joins audio and video muted, i.e., if there is no local media capture.
if (browser.isWebKitBased()) {
this.muteAudio(true, true);
} else {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
return tracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
}
}
if (room?.isStartVideoMuted()) {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
}
return localTracks;
return tracks;
};
if (isPrejoinPageVisible(state)) {
if (isPrejoinPageVisible(APP.store.getState())) {
_connectionPromise = connect(roomName).then(c => {
// We want to initialize it early, in case of errors to be able to gather logs.
// we want to initialize it early, in case of errors to be able
// to gather logs
APP.connection = c;
return c;
@@ -874,38 +819,43 @@ export default {
APP.store.dispatch(makePrecallTest(this._getConferenceOptions()));
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
const localTracks = await tryCreateLocalTracks;
const tracks = await tryCreateLocalTracks;
// Initialize device list a second time to ensure device labels get populated in case of an initial gUM
// acceptance; otherwise they may remain as empty strings.
// Initialize device list a second time to ensure device labels
// get populated in case of an initial gUM acceptance; otherwise
// they may remain as empty strings.
this._initDeviceList(true);
if (isPrejoinPageVisible(state)) {
APP.store.dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
return APP.store.dispatch(initPrejoin(localTracks, errors));
if (isPrejoinPageVisible(APP.store.getState())) {
return APP.store.dispatch(initPrejoin(tracks, errors));
}
logger.debug('Prejoin screen no longer displayed at the time when tracks were created');
this._displayErrorsForCreateInitialLocalTracks(errors);
const tracks = handleInitialTracks(initialOptions, localTracks);
let localTracks = handleStartAudioMuted(initialOptions, tracks);
setGUMPendingStateOnFailedTracks(tracks);
// in case where gum is slow and resolves after the startAudio/VideoMuted coming from jicofo, we can be
// join unmuted even though jicofo had instruct us to mute, so let's respect that before passing the tracks
if (!browser.isWebKitBased()) {
if (room?.isStartAudioMuted()) {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
}
}
return this._setLocalAudioVideoStreams(tracks);
if (room?.isStartVideoMuted()) {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
}
return this._setLocalAudioVideoStreams(localTracks);
}
const [ tracks, con ] = await this.createInitialLocalTracksAndConnect(roomName, initialOptions);
this._initDeviceList(true);
const filteredTracks = handleInitialTracks(initialOptions, tracks);
setGUMPendingStateOnFailedTracks(filteredTracks);
return this.startConference(con, filteredTracks);
return this.startConference(con, handleStartAudioMuted(initialOptions, tracks));
},
/**
@@ -1028,7 +978,6 @@ export default {
showUI && APP.store.dispatch(notifyMicError(error));
};
APP.store.dispatch(gumPending([ MEDIA_TYPE.AUDIO ], IGUMPendingState.PENDING_UNMUTE));
createLocalTracksF({ devices: [ 'audio' ] })
.then(([ audioTrack ]) => audioTrack)
.catch(error => {
@@ -1040,10 +989,7 @@ export default {
.then(async audioTrack => {
await this._maybeApplyAudioMixerEffect(audioTrack);
return this.useAudioStream(audioTrack);
})
.finally(() => {
APP.store.dispatch(gumPending([ MEDIA_TYPE.AUDIO ], IGUMPendingState.NONE));
this.useAudioStream(audioTrack);
});
} else {
muteLocalAudio(mute);
@@ -1082,25 +1028,18 @@ export default {
*/
muteVideo(mute, showUI = true) {
if (this.videoSwitchInProgress) {
logger.warn('muteVideo - unable to perform operations while video switch is in progress');
console.warn('muteVideo - unable to perform operations while video switch is in progress');
return;
}
const state = APP.store.getState();
if (!mute
&& isUserInteractionRequiredForUnmute(state)) {
&& isUserInteractionRequiredForUnmute(APP.store.getState())) {
logger.error('Unmuting video requires user interaction');
return;
}
// 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
@@ -1114,7 +1053,7 @@ export default {
return;
}
const localVideo = getLocalJitsiVideoTrack(state);
const localVideo = getLocalJitsiVideoTrack(APP.store.getState());
if (!localVideo && !mute && !this.isCreatingLocalTrack) {
const maybeShowErrorDialog = error => {
@@ -1123,8 +1062,6 @@ export default {
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
@@ -1149,7 +1086,6 @@ export default {
})
.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)
@@ -1462,16 +1398,11 @@ export default {
* @private
*/
_setLocalAudioVideoStreams(tracks = []) {
const { dispatch } = APP.store;
const pendingGUMDevicesToRemove = [];
const promises = tracks.map(track => {
if (track.isAudioTrack()) {
pendingGUMDevicesToRemove.push(MEDIA_TYPE.AUDIO);
return this.useAudioStream(track);
} else if (track.isVideoTrack()) {
logger.debug(`_setLocalAudioVideoStreams is calling useVideoStream with track: ${track}`);
pendingGUMDevicesToRemove.push(MEDIA_TYPE.VIDEO);
return this.useVideoStream(track);
}
@@ -1483,10 +1414,6 @@ export default {
});
return Promise.allSettled(promises).then(() => {
if (pendingGUMDevicesToRemove.length > 0) {
dispatch(gumPending(pendingGUMDevicesToRemove, IGUMPendingState.NONE));
}
this._localTracksInitialized = true;
logger.log(`Initialized with ${tracks.length} local tracks`);
});
@@ -1517,7 +1444,7 @@ export default {
logger.debug(`useVideoStream: Replacing ${oldTrack} with ${newTrack}`);
if (oldTrack === newTrack || (!oldTrack && !newTrack)) {
if (oldTrack === newTrack) {
resolve();
onFinish();
@@ -2029,10 +1956,7 @@ export default {
room.on(
JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
(...args) => {
APP.store.dispatch(nonParticipantMessageReceived(...args));
APP.API.notifyNonParticipantMessageReceived(...args);
});
(...args) => APP.store.dispatch(nonParticipantMessageReceived(...args)));
room.on(
JitsiConferenceEvents.LOCK_STATE_CHANGED,
@@ -2063,11 +1987,6 @@ export default {
APP.store.dispatch(participantKicked(kicker, kicked));
});
room.on(JitsiConferenceEvents.PARTICIPANT_SOURCE_UPDATED,
jitsiParticipant => {
APP.store.dispatch(participantSourcesUpdated(jitsiParticipant));
});
room.on(JitsiConferenceEvents.SUSPEND_DETECTED, () => {
APP.store.dispatch(suspendDetected());
});
@@ -2203,11 +2122,6 @@ export default {
UIEvents.VIDEO_DEVICE_CHANGED,
cameraDeviceId => {
const videoWasMuted = this.isLocalVideoMuted();
const localVideoTrack = getLocalJitsiVideoTrack(APP.store.getState());
if (localVideoTrack?.getDeviceId() === cameraDeviceId) {
return;
}
sendAnalytics(createDeviceChangedEvent('video', 'input'));
@@ -2338,7 +2252,10 @@ export default {
APP.UI.initConference();
dispatch(initKeyboardShortcuts());
if (!config.disableShortcuts) {
APP.keyboardshortcut.init();
}
dispatch(conferenceJoined(room));
const jwt = APP.store.getState()['features/base/jwt'];
@@ -2425,7 +2342,7 @@ export default {
* @param {MediaDeviceInfo[]} devices
* @returns {Promise}
*/
async _onDeviceListChanged(devices) {
_onDeviceListChanged(devices) {
const oldDevices = APP.store.getState()['features/base/devices'].availableDevices;
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
const localVideo = getLocalJitsiVideoTrack(APP.store.getState());
@@ -2439,10 +2356,13 @@ export default {
const newDevices
= mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged(
devices,
this.isSharingScreen,
localVideo,
localAudio,
newLabelsOnly);
const promises = [];
const audioWasMuted = this.isLocalAudioMuted();
const videoWasMuted = this.isLocalVideoMuted();
const requestedInput = {
audio: Boolean(newDevices.audioinput),
video: Boolean(newDevices.videoinput)
@@ -2452,9 +2372,8 @@ export default {
const { dispatch } = APP.store;
const setAudioOutputPromise
= setAudioOutputDeviceId(newDevices.audiooutput, dispatch)
.catch(err => {
logger.error(`Failed to set the audio output device to ${newDevices.audiooutput} - ${err}`);
});
.catch(); // Just ignore any errors in catch block.
promises.push(setAudioOutputPromise);
}
@@ -2472,7 +2391,8 @@ export default {
}
// Let's handle unknown/non-preferred devices
const newAvailDevices = APP.store.getState()['features/base/devices'].availableDevices;
const newAvailDevices
= APP.store.getState()['features/base/devices'].availableDevices;
let newAudioDevices = [];
let oldAudioDevices = [];
@@ -2488,86 +2408,103 @@ export default {
// check for audio
if (newAudioDevices.length > 0) {
APP.store.dispatch(checkAndNotifyForNewDevice(newAudioDevices, oldAudioDevices));
APP.store.dispatch(
checkAndNotifyForNewDevice(newAudioDevices, oldAudioDevices));
}
// check for video
if (requestedInput.video) {
APP.store.dispatch(checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput));
if (!requestedInput.video) {
APP.store.dispatch(
checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput));
}
// When the 'default' mic needs to be selected, we need to pass the real device id to gUM instead of 'default'
// in order to get the correct MediaStreamTrack from chrome because of the following bug.
// When the 'default' mic needs to be selected, we need to
// pass the real device id to gUM instead of 'default' in order
// to get the correct MediaStreamTrack from chrome because of the
// following bug.
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689
const hasDefaultMicChanged = newDevices.audioinput === 'default';
// When the local video is muted and a preferred device is connected, update the settings and remove the track
// from the conference. A new track will be created and replaced when the user unmutes their camera.
// This is the case when the local video is muted and a preferred device is connected.
if (requestedInput.video && this.isLocalVideoMuted()) {
APP.store.dispatch(updateSettings({
// We want to avoid creating a new video track in order to prevent turning on the camera.
requestedInput.video = false;
APP.store.dispatch(updateSettings({ // Update the current selected camera for the device selection dialog.
cameraDeviceId: newDevices.videoinput
}));
requestedInput.video = false;
delete newDevices.videoinput;
// Remove the track from the conference.
if (localVideo) {
await this.useVideoStream(null);
logger.debug('_onDeviceListChanged: Removed the current video track.');
}
// Removing the current video track in order to force the unmute to select the preferred device.
logger.debug('_onDeviceListChanged: Removing the current video track.');
this.useVideoStream(null);
}
// When the local audio is muted and a preferred device is connected, update the settings and remove the track
// from the conference. A new track will be created and replaced when the user unmutes their mic.
if (requestedInput.audio && this.isLocalAudioMuted()) {
APP.store.dispatch(updateSettings({
micDeviceId: newDevices.audioinput
}));
requestedInput.audio = false;
delete newDevices.audioinput;
// Remove the track from the conference.
if (localAudio) {
await this.useAudioStream(null);
logger.debug('_onDeviceListChanged: Removed the current audio track.');
}
}
// Create the tracks and replace them only if the user is unmuted.
if (requestedInput.audio || requestedInput.video) {
let tracks = [];
const realAudioDeviceId = hasDefaultMicChanged
? getDefaultDeviceId(APP.store.getState(), 'audioInput') : newDevices.audioinput;
try {
tracks = await mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
promises.push(
mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
createLocalTracksF,
requestedInput.video ? newDevices.videoinput : null,
requestedInput.audio ? realAudioDeviceId : null
);
} catch (error) {
logger.error(`Track creation failed on device change, ${error}`);
newDevices.videoinput,
hasDefaultMicChanged
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: newDevices.audioinput)
.then(tracks => {
// If audio or video muted before, or we unplugged current
// device and selected new one, then mute new track.
const muteSyncPromises = tracks.map(track => {
if ((track.isVideoTrack() && videoWasMuted)
|| (track.isAudioTrack() && audioWasMuted)) {
return track.mute();
}
return Promise.reject(error);
}
return Promise.resolve();
});
for (const track of tracks) {
if (track.isAudioTrack()) {
promises.push(
this.useAudioStream(track)
.then(() => {
hasDefaultMicChanged && (track._realDeviceId = track.deviceId = 'default');
this._updateAudioDeviceId();
}));
} else {
promises.push(
this.useVideoStream(track)
.then(() => {
this._updateVideoDeviceId();
}));
}
}
}
return Promise.all(muteSyncPromises)
.then(() =>
Promise.all(Object.keys(requestedInput).map(mediaType => {
if (requestedInput[mediaType]) {
const useStream
= mediaType === 'audio'
? this.useAudioStream.bind(this)
: this.useVideoStream.bind(this);
const track = tracks.find(t => t.getType() === mediaType) || null;
// Use the new stream or null if we failed to obtain it.
return useStream(track)
.then(() => {
if (track?.isAudioTrack() && hasDefaultMicChanged) {
// workaround for the default device to be shown as selected in the
// settings even when the real device id was passed to gUM because of
// the above mentioned chrome bug.
track._realDeviceId = track.deviceId = 'default';
}
mediaType === 'audio'
? this._updateAudioDeviceId()
: this._updateVideoDeviceId();
});
}
return Promise.resolve();
})));
})
.then(() => {
// Log and sync known mute state.
if (audioWasMuted) {
sendAnalytics(createTrackMutedEvent(
'audio',
'device list changed'));
logger.log('Audio mute: device list changed');
muteLocalAudio(true);
}
if (!this.isSharingScreen && videoWasMuted) {
sendAnalytics(createTrackMutedEvent(
'video',
'device list changed'));
logger.log('Video mute: device list changed');
muteLocalVideo(true);
}
}));
return Promise.all(promises)
.then(() => {
@@ -2675,22 +2612,17 @@ export default {
async leaveRoom(doDisconnect = true, reason = '') {
APP.store.dispatch(conferenceWillLeave(room));
const maybeDisconnect = () => {
if (doDisconnect) {
return disconnect();
}
};
if (room && room.isJoined()) {
return room.leave(reason).then(() => maybeDisconnect())
.catch(e => {
logger.error(e);
return maybeDisconnect();
return room.leave(reason).finally(() => {
if (doDisconnect) {
return disconnect();
}
});
}
return maybeDisconnect();
if (doDisconnect) {
return disconnect();
}
},
/**

208
config.js
View File

@@ -46,9 +46,9 @@ var config = {
},
// BOSH URL. FIXME: use XEP-0156 to discover it.
bosh: 'https://jitsi-meet.example.com/' + subdir + 'http-bind',
bosh: '//jitsi-meet.example.com/' + subdir + 'http-bind',
// Websocket URL (XMPP)
// Websocket URL
// websocket: 'wss://jitsi-meet.example.com/' + subdir + 'xmpp-websocket',
// The real JID of focus participant - can be overridden here
@@ -56,27 +56,11 @@ var config = {
// https://github.com/jitsi/jitsi-meet/issues/7376
// focusUserJid: 'focus@auth.jitsi-meet.example.com',
// Options related to the bridge (colibri) data channel
bridgeChannel: {
// If the backend advertises multiple colibri websockets, this options allows
// to filter some of them out based on the domain name. We use the first URL
// which does not match ignoreDomain, falling back to the first one that matches
// ignoreDomain. Has no effect if undefined.
// ignoreDomain: 'example.com',
// Prefer SCTP (WebRTC data channels over the media path) over a colibri websocket.
// If SCTP is available in the backend it will be used instead of a WS. Defaults to
// false (SCTP is used only if available and no WS are available).
// preferSctp: false
},
// Testing / experimental features.
//
testing: {
// Allows the setting of a custom bandwidth value from the UI.
// assumeBandwidth: true,
// Disables the End to End Encryption feature. Useful for debugging
// issues related to insertable streams.
// disableE2EE: false,
@@ -95,6 +79,11 @@ var config = {
// This is useful when the client runs on a host with limited resources.
// noAutoPlayVideo: false,
// Whether to use fake constraints (height: 99999, width: 99999) when calling getDisplayMedia on
// Chromium based browsers. This is intended as a workaround for
// https://bugs.chromium.org/p/chromium/issues/detail?id=1056311
// setScreenSharingResolutionConstraints: true,
// Enable callstats only for a percentage of users.
// This takes a value between 0 and 100 which determines the probability for
// the callstats to be enabled.
@@ -142,6 +131,9 @@ var config = {
// Media
//
// Enable unified plan implementation support on Chromium based browsers.
// enableUnifiedOnChrome: false,
// Audio
// Disable measuring of audio levels.
@@ -197,22 +189,6 @@ var config = {
// enableOpusDtx: false,
// },
// Noise suppression configuration. By default rnnoise is used. Optionally Krisp
// can be used by enabling it below, but the Krisp JS SDK files must be supplied in your
// 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_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,
// },
// },
// Video
// Sets the preferred resolution (height) for local video. Defaults to 720.
@@ -273,6 +249,12 @@ var config = {
// Enable / disable simulcast support.
// disableSimulcast: false,
// Enable / disable layer suspension. If enabled, endpoints whose HD layers are not in use will be suspended
// (no longer sent) until they are requested again. This is enabled by default. This must be enabled for screen
// sharing to work as expected on Chrome. Disabling this might result in low resolution screenshare being sent
// by the client.
// enableLayerSuspension: false,
// Every participant after the Nth will start video muted.
// startVideoMuted: 10,
@@ -280,6 +262,17 @@ var config = {
// applied locally. FIXME: having these 2 options is confusing.
// startWithVideoMuted: false,
// If set to true, prefer to use the H.264 video codec (if supported).
// Note that it's not recommended to do this because simulcast is not
// supported when using H.264. For 1-to-1 calls this setting is enabled by
// default and can be toggled in the p2p section.
// This option has been deprecated, use preferredCodec under videoQuality section instead.
// preferH264: true,
// If set to true, disable H.264 video codec by stripping it out of the
// SDP.
// disableH264: false,
// Desktop sharing
// Optional desktop sharing frame rate options. Default value: min:5, max:5.
@@ -425,6 +418,28 @@ var config = {
// value will be used when the quality level is selected using "Manage Video Quality" slider.
// startLastN: 1,
// Provides a way to use different "last N" values based on the number of participants in the conference.
// The keys in an Object represent number of participants and the values are "last N" to be used when number of
// participants gets to or above the number.
//
// For the given example mapping, "last N" will be set to 20 as long as there are at least 5, but less than
// 29 participants in the call and it will be lowered to 15 when the 30th participant joins. The 'channelLastN'
// will be used as default until the first threshold is reached.
//
// lastNLimits: {
// 5: 20,
// 30: 15,
// 50: 10,
// 70: 5,
// 90: 2,
// },
// Provides a way to translate the legacy bridge signaling messages, 'LastNChangedEvent',
// 'SelectedEndpointsChangedEvent' and 'ReceiverVideoConstraint' into the new 'ReceiverVideoConstraints' message
// that invokes the new bandwidth allocation algorithm in the bridge which is described here
// - https://github.com/jitsi/jitsi-videobridge/blob/master/doc/allocation.md.
// useNewBandwidthAllocationStrategy: false,
// Specify the settings for video quality optimizations on the client.
// videoQuality: {
// // Provides a way to prevent a video codec from being negotiated on the JVB connection. The codec specified
@@ -549,15 +564,12 @@ var config = {
// Disables responsive tiles.
// disableResponsiveTiles: false,
// DEPRECATED. Please use `securityUi?.hideLobbyButton` instead.
// Hides lobby button.
// Hides lobby button
// hideLobbyButton: false,
// DEPRECATED. Please use `lobby?.autoKnock` instead.
// If Lobby is enabled starts knocking automatically.
// autoKnockLobby: false,
// DEPRECATED. Please use `lobby?.enableChat` instead.
// Enable lobby chat.
// enableLobbyChat: true,
@@ -568,9 +580,6 @@ var config = {
// Require users to always specify a display name.
// requireDisplayName: true,
// Enables webhid functionality for Audio.
// enableWebHIDFeature: false,
// DEPRECATED! Use 'welcomePage.disabled' instead.
// Whether to use a welcome page or not. In case it's false a random room
// will be joined when no room is specified.
@@ -585,22 +594,6 @@ var config = {
// customUrl: ''
// },
// Configs for the lobby screen.
// lobby {
// // If Lobby is enabled, it starts knocking automatically. Replaces `autoKnockLobby`.
// autoKnock: false,
// // Enables the lobby chat. Replaces `enableLobbyChat`.
// enableChat: true,
// },
// Configs for the security related UI elements.
// securityUi: {
// // Hides the lobby button. Replaces `hideLobbyButton`.
// hideLobbyButton: false,
// // Hides the possibility to set and enter a lobby password.
// disableLobbyPassword: false,
// },
// Disable app shortcuts that are registered upon joining a conference
// disableShortcuts: false,
@@ -828,14 +821,6 @@ var config = {
// 'microphone', 'camera', 'select-background', 'invite', 'settings'
// hiddenPremeetingButtons: [],
// An array with custom option buttons for the participant context menu
// type: Array<{ icon: string; id: string; text: string; }>
// customParticipantMenuButtons: [],
// An array with custom option buttons for the toolbar
// type: Array<{ icon: string; id: string; text: string; }>
// customToolbarButtons: [],
// Stats
//
@@ -927,6 +912,9 @@ var config = {
// connection.
enabled: true,
// Enable unified plan implementation support on Chromium for p2p connection.
// enableUnifiedOnChrome: false,
// Sets the ICE transport policy for the p2p connection. At the time
// of this writing the list of possible values are 'all' and 'relay',
// but that is subject to change in the future. The enum is defined in
@@ -935,10 +923,18 @@ var config = {
// If not set, the effective value is 'all'.
// iceTransportPolicy: 'all',
// If set to true, it will prefer to use H.264 for P2P calls (if H.264
// is supported). This setting is deprecated, use preferredCodec instead.
// preferH264: true,
// Provides a way to set the video codec preference on the p2p connection. Acceptable
// codec values are 'VP8', 'VP9' and 'H264'.
// preferredCodec: 'H264',
// If set to true, disable H.264 video codec by stripping it out of the
// SDP. This setting is deprecated, use disabledCodec instead.
// disableH264: false,
// Provides a way to prevent a video codec from being negotiated on the p2p connection.
// disabledCodec: '',
@@ -1068,12 +1064,7 @@ var config = {
// },
// e2ee: {
// labels: {
// description: '',
// label: '',
// tooltip: '',
// warning: '',
// },
// labels,
// externallyManagedKey: false,
// },
@@ -1100,71 +1091,10 @@ var config = {
// use only.
// _desktopSharingSourceDevice: 'sample-id-or-label',
// DEPRECATED! Use deeplinking.disabled instead.
// If true, any checks to handoff to another application will be prevented
// and instead the app will continue to display in the current browser.
// 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.
// desktop: {
// appName: 'Jitsi Meet'
// },
// // If true, any checks to handoff to another application will be prevented
// // and instead the app will continue to display in the current browser.
// disabled: false,
// // whether to hide the logo on the deep linking pages.
// hideLogo: false,
// // The ios deeplinking config.
// ios: {
// appName: 'Jitsi Meet',
// // Specify mobile app scheme for opening the app from the mobile browser.
// 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.
// android: {
// appName: 'Jitsi Meet',
// // Specify mobile app scheme for opening the app from the mobile browser.
// appScheme: 'org.jitsi.meet',
// // Custom URL for downloading android mobile app.
// downloadLink: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',
// // 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'
// }
// }
// },
// // The terms, privacy and help centre URL's.
// legalUrls: {
// helpCentre: 'https://web-cdn.jitsi.net/faq/meet-faq.html',
// privacy: 'https://jitsi.org/meet/privacy',
// terms: 'https://jitsi.org/meet/terms'
// },
// A property to disable the right click context menu for localVideo
// the menu has option to flip the locally seen video for local presentations
// disableLocalVideoFlip: false,
@@ -1257,6 +1187,7 @@ var config = {
ui03: "violet",
ui04: "magenta",
ui05: "blueviolet",
field02Hover: 'red',
action01: 'green',
action01Hover: 'lightgreen',
disabled01: 'beige',
@@ -1380,9 +1311,10 @@ var config = {
deploymentInfo
dialOutAuthUrl
dialOutCodesUrl
dialOutRegionUrl
disableRemoteControl
displayJids
externalConnectUrl
e2eeLabels
firefox_fake_device
googleApiApplicationClientID
iAmRecorder
@@ -1405,6 +1337,7 @@ var config = {
/**
_peerConnStatusOutOfLastNTimeout
_peerConnStatusRtcMuteTimeout
abTesting
avgRtpStatsN
callStatsConfIDNamespace
callStatsCustomScriptUrl
@@ -1413,7 +1346,6 @@ var config = {
disableAGC
disableAP
disableHPF
disableLocalStats
disableNS
enableTalkWhileMuted
forceJVB121Ratio
@@ -1559,8 +1491,6 @@ 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
@@ -1590,12 +1520,6 @@ var config = {
// },
};
// 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

@@ -12,7 +12,7 @@ import {
constructOptions
} from './react/features/base/connection/actions.web';
import { openDialog } from './react/features/base/dialog/actions';
import { setJWT } from './react/features/base/jwt/actions';
import { setJWT } from './react/features/base/jwt';
import {
JitsiConnectionErrors,
JitsiConnectionEvents
@@ -32,6 +32,54 @@ const logger = Logger.getLogger(__filename);
*/
export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri';
/**
* Checks if we have data to use attach instead of connect. If we have the data
* executes attach otherwise check if we have to wait for the data. If we have
* to wait for the attach data we are setting handler to APP.connect.handler
* which is going to be called when the attach data is received otherwise
* executes connect.
*
* @param {string} [id] user id
* @param {string} [password] password
* @param {string} [roomName] the name of the conference.
*/
function checkForAttachParametersAndConnect(id, password, connection) {
if (window.XMPPAttachInfo) {
APP.connect.status = 'connecting';
// When connection optimization is not deployed or enabled the default
// value will be window.XMPPAttachInfo.status = "error"
// If the connection optimization is deployed and enabled and there is
// a failure the value will be window.XMPPAttachInfo.status = "error"
if (window.XMPPAttachInfo.status === 'error') {
connection.connect({
id,
password
});
return;
}
const attachOptions = window.XMPPAttachInfo.data;
if (attachOptions) {
connection.attach(attachOptions);
delete window.XMPPAttachInfo.data;
} else {
connection.connect({
id,
password
});
}
} else {
APP.connect.status = 'ready';
APP.connect.handler
= checkForAttachParametersAndConnect.bind(
null,
id, password, connection);
}
}
/**
* Try to open connection using provided credentials.
* @param {string} [id]
@@ -134,10 +182,7 @@ export async function connect(id, password) {
APP.store.dispatch(setPrejoinDisplayNameRequired());
}
connection.connect({
id,
password
});
checkForAttachParametersAndConnect(id, password, connection);
});
}
@@ -167,7 +212,7 @@ export function openConnection({ id, password, retry, roomName }) {
password = passwordOverride; // eslint-disable-line no-param-reassign
}
return connect(id, password).catch(err => {
return connect(id, password, roomName).catch(err => {
if (retry) {
const { jwt } = APP.store.getState()['features/base/jwt'];

View File

@@ -0,0 +1,3 @@
module.exports = {
'extends': '../react/.eslintrc.js'
};

View File

@@ -0,0 +1,86 @@
/* global config, createConnectionExternally */
import getRoomName from '../react/features/base/config/getRoomName';
import { parseURLParams } from '../react/features/base/util/parseURLParams';
/**
* Implements external connect using createConnectionExternally function defined
* in external_connect.js for Jitsi Meet. Parses the room name and JSON Web
* Token (JWT) from the URL and executes createConnectionExternally.
*
* NOTE: If you are using lib-jitsi-meet without Jitsi Meet, you should use this
* file as reference only because the implementation is Jitsi Meet-specific.
*
* NOTE: For optimal results this file should be included right after
* external_connect.js.
*/
if (typeof createConnectionExternally === 'function') {
// URL params have higher priority than config params.
// Do not use external connect if websocket is enabled.
let url
= parseURLParams(window.location, true, 'hash')[
'config.externalConnectUrl']
|| config.websocket ? undefined : config.externalConnectUrl;
const isRecorder
= parseURLParams(window.location, true, 'hash')['config.iAmRecorder'];
let roomName;
if (url && (roomName = getRoomName()) && !isRecorder) {
url += `?room=${roomName}`;
const token = parseURLParams(window.location, true, 'search').jwt;
if (token) {
url += `&token=${token}`;
}
createConnectionExternally(
url,
connectionInfo => {
// Sets that global variable to be used later by connect method
// in connection.js.
window.XMPPAttachInfo = {
status: 'success',
data: connectionInfo
};
checkForConnectHandlerAndConnect();
},
errorCallback);
} else {
errorCallback();
}
} else {
errorCallback();
}
/**
* Check if connect from connection.js was executed and executes the handler
* that is going to finish the connect work.
*
* @returns {void}
*/
function checkForConnectHandlerAndConnect() {
window.APP
&& window.APP.connect.status === 'ready'
&& window.APP.connect.handler();
}
/**
* Implements a callback to be invoked if anything goes wrong.
*
* @param {Error} error - The specifics of what went wrong.
* @returns {void}
*/
function errorCallback(error) {
// The value of error is undefined if external connect is disabled.
error && console.warn(error);
// Sets that global variable to be used later by connect method in
// connection.js.
window.XMPPAttachInfo = {
status: 'error'
};
checkForConnectHandlerAndConnect();
}

View File

@@ -0,0 +1,117 @@
/**
* Mixins that mimic the way Atlaskit fills the screen with modals at low screen widths.
*/
@mixin full-size-modal-positioner() {
height: 100%;
left: 0;
position: fixed;
top: 0;
max-width: 100%;
width: 100%;
}
@mixin full-size-modal-dialog() {
height: 100%;
max-height: 100%;
border-radius: 0;
}
/**
* Move the @atlaskit/flag container up a little bit so it does not cover the
* toolbar with the first notification.
*/
.atlaskit-portal > #notifications-container {
bottom: calc(#{$newToolbarSizeWithPadding}) !important;
}
/**
* Keep overflow menu within screen vertical bounds and make it scrollable.
*/
.toolbox-button-wth-dialog > div:nth-child(2) {
background: $menuBG;
max-height: calc(100vh - #{$newToolbarSizeWithPadding} - 46px);
margin-bottom: 4px;
padding: 0;
overflow-y: auto;
}
/**
* Remove background color and box-shadow for the context menu container.
*/
.toolbox-button-wth-dialog.context-menu > div:nth-child(2) {
background: transparent;
box-shadow: none;
overflow-y: initial;
}
.audio-preview > div:nth-child(2),
.video-preview > div:nth-child(2) {
margin-bottom: 4px;
outline: none;
padding: 0;
}
/**
* The following selectors keep the chat modal full-size anywhere between 100px
* and 580px for desktop or 680px for mobile.
*/
@media (min-width: 100px) and (max-width: 320px) {
.smiley-input {
display: none;
}
.shift-right .focus-lock > div > div {
@include full-size-modal-positioner();
}
.shift-right .focus-lock [role="dialog"] {
@include full-size-modal-dialog();
}
}
@media (min-width: 480px) and (max-width: 580px) {
.shift-right .focus-lock > div > div {
@include full-size-modal-positioner();
}
.shift-right .focus-lock [role="dialog"] {
@include full-size-modal-dialog();
}
}
@media (max-width: 580px) {
// Override Atlaskit inline style for the modal background.
// Important is unfortunately needed for that.
.shift-right .focus-lock [role="dialog"][style] {
background-color: $chatBackgroundColor !important;
}
// Remove Atlaskit padding from the chat dialog.
.shift-right .focus-lock [role="dialog"] > div:first-child > div:nth-child(2) {
padding: 0;
}
}
div.Tooltip {
color: #fff;
font-size: 12px;
line-height: 14px;
padding: 8px;
}
// make modal full screen on landscape orientation
@media (max-height: 420px) {
.atlaskit-portal {
.css-1oc7v0j {
height: 100%;
padding: 0;
max-width: 100%;
top: 0;
width: 100%;
&> div {
height: 100%;
}
}
}
}

192
css/_audio-preview.scss Normal file
View File

@@ -0,0 +1,192 @@
.audio-preview {
display: inline-block;
&-content {
background: $menuBG;
border-radius: 3px;
font-size: 14px;
line-height: 24px;
max-height: 456px;
overflow: auto;
width: 300px;
&-ul {
margin:0;
padding:0;
list-style-type: none;
}
}
&-header {
color: #fff;
align-items: center;
display: flex;
margin-top: 8px;
padding: 8px 16px;
&-icon {
display: inline-block;
svg {
fill: #fff;
}
}
&--bordered {
border-bottom: 1px solid #4C4D50;
}
&-text {
margin-left: 12px;
}
}
&-entry {
align-items: center;
color: #fff;
cursor: pointer;
display: flex;
padding: 8px 0;
margin-left: 48px;
&--selected {
background: #131519;
cursor: initial;
margin-left: 0;
padding-left: 18px;
}
&-text {
color: #fff;
display: inline-block;
line-height: 24px;
text-overflow: ellipsis;
max-width: 213px;
overflow: hidden;
white-space: nowrap;
}
}
&-speaker {
position: relative;
&-ul {
margin:0;
padding:0;
list-style-type: none;
}
&:hover, &:focus-within, &:focus {
.audio-preview-entry {
background: #36383C;
margin-left: 0;
padding-left: 48px;
&--selected {
padding-left: 18px;
background: $newToolbarBackgroundColor;
}
}
.audio-preview-test-button {
display: inline-block;
}
.audio-preview-entry-text {
max-width: 178px;
}
}
&:last-child {
padding-bottom: 8px;
}
.audio-preview-entry-text {
max-width: 238px;
}
}
&-microphone {
position: relative;
&:hover {
.audio-preview-entry {
background: #36383C;
margin-left: 0;
padding-left: 48px;
&--selected {
background: $newToolbarBackgroundColor;
padding-left: 18px;
}
}
}
&--nometer {
.audio-preview-entry-text {
max-width: 238px;
}
}
&--withmeter {
.audio-preview-entry-text {
max-width: 178px;
}
}
}
&-icon {
border-radius: 50%;
display: inline-block;
width: 14px;
& svg {
fill: #1C2025;
}
&--check {
background: #31B76A;
margin-right: 16px;
}
&--exclamation {
margin-left: 6px;
& svg {
fill: #E54B4B;
}
}
}
&-hr {
border-top: 1px solid #4C4D50;
border-bottom: 0;
}
&-test-button {
display: none;
background: #FFF;
border: 1px solid #D1DBE8;
border-radius: 3px;
color: #1C2025;
cursor: pointer;
font-weight: 600;
font-size: 0.8rem;
line-height: 24px;
padding: 2px 8px;
position: absolute;
right: 16px;
top: 5px;
}
&-meter-mic {
position: absolute;
right: 16px;
top: 14px;
}
// Override @atlaskit/InlineDialog container which is made with styled components
& > div:nth-child(2) {
outline: none;
padding: 0;
}
}

View File

@@ -126,12 +126,6 @@ form {
background-size: contain;
}
.leftwatermarknomargin {
background-position: center left;
background-repeat: no-repeat;
background-size: contain;
}
.rightwatermark {
right: 32px;
top: 32px;

View File

@@ -32,7 +32,7 @@
#chat-conversation-container {
// extract message input height
height: calc(100% - 64px);
height: calc(100% - 68px);
overflow: hidden;
position: relative;
}
@@ -76,6 +76,32 @@
}
}
#chat-recipient {
align-items: center;
background-color: $chatPrivateMessageBackgroundColor;
display: flex;
flex-direction: row;
font-weight: 100;
padding: 10px;
span {
color: white;
display: flex;
flex: 1;
}
div {
svg {
cursor: pointer;
fill: white;
}
}
&.lobby-chat-recipient {
background-color: $chatLobbyMessageBackgroundColor;
}
}
.chat-header {
height: 70px;
@@ -98,12 +124,13 @@
}
.chat-input-container {
padding: 0 16px 24px;
padding: 0 16px 16px;
}
#chat-input {
display: flex;
align-items: flex-end;
padding: 4px;
position: relative;
}
@@ -236,6 +263,15 @@
-webkit-user-select: text;
user-select: text;
}
.display-name {
font-size: 12px;
font-weight: 600;
margin-bottom: 5px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.sr-only {
@@ -252,11 +288,24 @@
}
.chatmessage {
background-color: $chatRemoteMessageBackgroundColor;
border-radius: 0px 6px 6px 6px;
box-sizing: border-box;
color: white;
margin-top: 3px;
max-width: 100%;
position: relative;
&.localuser {
background-color: $chatLocalMessageBackgroundColor;
border-radius: 6px 0px 6px 6px;
}
.usermessage {
white-space: pre-wrap;
font-size: 14px;
}
&.error {
border-radius: 0px;
@@ -271,12 +320,22 @@
}
}
.privatemessagenotice {
font-size: 11px;
font-weight: 100;
}
.messagecontent {
margin: 8px;
max-width: 100%;
overflow: hidden;
}
}
.timestamp {
color: #757575;
}
#smileys {
font-size: 20pt;
margin: auto;
@@ -350,9 +409,24 @@
}
.chat-message-group {
display: flex;
flex-direction: column;
&.local {
align-items: flex-end;
.chatmessage {
background-color: $chatLocalMessageBackgroundColor;
border-radius: 6px 0px 6px 6px;
&.privatemessage {
background-color: $chatPrivateMessageBackgroundColor;
}
&.lobbymessage {
background-color: $chatLobbyMessageBackgroundColor;
}
}
.display-name {
display: none;
}
@@ -363,10 +437,58 @@
}
&.error {
.chatmessage {
background-color: $defaultWarningColor;
border-radius: 0px;
font-weight: 100;
}
.display-name {
display: none;
}
}
.chatmessage-wrapper {
max-width: 100%;
.replywrapper {
display: flex;
flex-direction: row;
align-items: center;
.messageactions {
align-self: stretch;
border-left: 1px solid $chatActionsSeparatorColor;
display: flex;
flex-direction: column;
justify-content: center;
padding: 5px;
&.lobbychatmessageactions {
border-left-color: $chatLobbyActionsSeparatorColor;
}
.toolbox-icon {
cursor: pointer;
}
}
}
}
.chatmessage {
background-color: $chatRemoteMessageBackgroundColor;
border-radius: 0px 6px 6px 6px;
display: inline-block;
margin-top: 3px;
color: white;
&.privatemessage {
background-color: $chatPrivateMessageBackgroundColor;
}
&.lobbymessage {
background-color: $chatLobbyMessageBackgroundColor;
}
}
}
.chat-dialog {

75
css/_country-picker.scss Normal file
View File

@@ -0,0 +1,75 @@
.cpick {
border: 1px solid #A4B8D1;
color: #fff;
display: flex;
font-size: 15px;
height: 38px;
line-height: 24px;
&-selector {
align-items: center;
background-color: #283447;
border-right: 1px solid #A4B8D1;
cursor: pointer;
display: flex;
padding: 8px 10px;
position: relative;
width: 88px;
}
&-icon {
margin-right: 8px;
position: absolute;
right: 0;
top: 12px;
& > svg {
fill: #fff;
}
}
&-input {
padding: 8px;
background: #1C2025;
border: 0;
margin: 0;
color: #fff;
caret-color: #0376DA;
flex-grow: 1;
}
&-dropdown {
height: 190px;
overflow-y: auto;
width: 343px;
}
&-dropdown-entry {
align-items: center;
cursor: pointer;
display: flex;
height: 40px;
padding: 0 10px;
&:hover {
background-color: #66768b;
}
&-text {
color: #fff;
flex-grow: 1;
font-size: 15px;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
// Override @Atlaskit/inline-dialog styles
.cpick-container > div:nth-child(2) {
outline: none;
padding: 8px 0 0 0;
}

View File

@@ -82,7 +82,6 @@
}
.left-column {
order: -1;
display: flex;
flex-direction: column;
flex-grow: 0;
@@ -93,7 +92,6 @@
.right-column {
display: flex;
flex-direction: column;
align-items: flex-start;
flex-grow: 1;
padding-left: 16px;
padding-top: 13px;
@@ -101,11 +99,11 @@
}
.title {
font-size: 12px;
font-weight: 600;
line-height: 16px;
margin-bottom: 4px;
}
font-size: 12px;
font-weight: 600;
line-height: 16px;
padding-bottom: 4px;
}
.subtitle {
color: #5E6D7A;
@@ -127,7 +125,8 @@
cursor: pointer;
}
&.with-click-handler:hover {
&.with-click-handler:hover,
&.with-click-handler:focus {
background-color: #c7ddff;
}

View File

@@ -3,28 +3,28 @@
display: inline-block;
& > svg {
fill: #525252;
fill: #4E5E6C;
width: 38px;
}
}
&.metr--disabled {
& > svg {
fill: #525252;
fill: #4E5E6C;
}
}
}
.metr-l-0 {
rect:first-child {
fill: #1EC26A;
fill: #31B76A;
}
}
@for $i from 1 through 7 {
.metr-l-#{$i} {
rect:nth-child(-n+#{$i+1}) {
fill: #1EC26A;
fill: #31B76A;
}
}
}

23
css/_notifications.scss Normal file
View File

@@ -0,0 +1,23 @@
.notification-appear, .notification-enter {
opacity: 0;
position: relative;
left: -200px;
transition: all .2s !important; // !important needed to overwrite atlaskit default style
&-active {
opacity: 1;
left: 0;
}
}
.notification-exit {
opacity: 1;
position: relative;
left: 0;
transition: all .2s !important; // !important needed to overwrite atlaskit default style
&-active {
opacity: 0;
left: -200px;
}
}

View File

@@ -1,3 +1,353 @@
.polls-panel {
.poll-dialog {
font-size: 14px;
font-weight: 400;
line-height: 20px;
h1, span, li, strong {
color: #bce;
}
ol {
margin: 0;
}
}
.poll-question-field {
padding: 8px 16px;
padding-bottom: 24px;
border-bottom: 1px solid #525252;
}
.poll-header {
margin-bottom: 8px;
}
.poll-creator {
color: #C2C2C2;
font-weight: 600;
margin: 4px 0 16px 0;
}
.poll-answer-container {
display: flex;
padding: 4px;
background: #3D3D3D;
border-radius: 3px;
margin-bottom: 8px;
@media (max-width: 580px) {
&> span {
padding: 8px 0;
}
svg {
margin-top: 6px;
}
}
}
.poll-answer-field-list, .poll-answer-list, .poll-result-list {
list-style-type: none;
padding: 0;
margin: 0;
}
.poll-answer-field-list {
padding: 0 16px;
}
ol.poll-result-list {
margin-bottom: 1.5em;
}
.poll-result-list > li {
margin-bottom: 16px;
}
.poll-answer-field {
flex-direction: column;
align-items: stretch;
margin-bottom: 16;
}
.poll-answer-field:last-child {
margin-bottom: 0;
}
.poll-create-option-row {
display: flex;
margin-bottom: 4;
}
// Needed to override atlaskit default blue color
.poll-create-container .jsYMHu {
background: #292929;
border-color: #808090;
color: #fff // #808090
}
.poll-add-button {
display: flex;
justify-content: center;
padding: 8px 16px;
}
.poll-remove-option-button {
background: 0 0;
border: none;
color: #E04757;
padding-left: 0;
}
.poll-create-add-option {
border: none;
background-color: #292929;
padding: 3px;
width: 100%;
}
.poll-icon-button, .poll-drag-handle {
.jitsi-icon svg {
fill: #929292;
}
}
.poll-drag-handle {
background-color: transparent;
border: none;
cursor: grab;
padding-left: 8;
padding-top: 8px;
display: flex;
}
.poll-question {
font-size: 16px;
font-weight: 600;
line-height: 26px;
}
.poll-answer-voters {
font-weight: lighter;
list-style-type: none;
border: #616161 solid 1px;
border-radius: 3px;
padding: 2px 6px;
margin: 4px 0px 12px;
background-color: #616161;
}
.poll-answer-header {
display: flex;
justify-content: space-between;
}
.poll-answer-vote-name {
flex-shrink: 1;
overflow-wrap: anywhere
}
.poll-answer-vote-count-container{
display: flex;
}
.poll-answer-vote-count {
margin-left: 10px;
white-space: nowrap;
flex: 1;
text-align: right;
}
.poll-answer-short-results{
display: flex;
min-width: 10em;
justify-content: space-between;
align-items: center;
}
.poll-bar-container, .poll-bar {
border-radius: 3px;
height: 6px;
}
.poll-bar-container {
background-color: #616161;
max-width: 160px;
margin-top: 3px;
flex: 1;
}
.poll-bar {
background-color: #246FE5;
}
.poll-message-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
margin-top: 5px;
}
.poll-notice {
font-weight: 100;
margin-right: 10px;
}
.poll-show-details {
background-color: transparent;
border: none;
&:hover {
text-decoration: underline;
}
}
.poll-result-links {
display: flex;
flex-direction: row;
justify-content: space-between;
a.poll-detail-link, a.poll-change-vote-link {
color: #669AEC;
cursor: pointer;
font-weight: 600;
text-decoration: none;
&:hover {
color: #669AEC;
}
&:visited {
color: #669AEC;
}
}
}
.polls-pane-content {
height: 100%;
position: relative;
}
.pane-content{
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
align-items: center;
width: 100%;
}
.empty-pane-icon {
width: 50%;
padding: 24px;
}
.empty-pane-icon svg {
fill: #3D3D3D;
width: 100%;
height: auto;
}
.empty-pane-message {
color: #fff;
padding: 0 16px;
text-align: center;
}
.poll-results, .poll-answer {
background: #292929;
border-radius: 8px;
border: 1px solid #666666;
margin: 16px;
padding: 16px;
word-break: break-word;
}
.poll-results {
color: #fff;
}
.poll-answer {
h1, strong ,span {
color: #fff;
}
button > span {
color: inherit;
}
}
.poll-create-label {
color: #C2C2C2;
display: flex;
font-weight: 400;
margin-bottom: 4;
}
.expandable-input{
line-height: 18px;
resize: none;
width: 100%;
height: 40px;
box-sizing: border-box;
overflow: hidden;
border: 1px solid #666666;
background-color: #141414;
color: #FFF;
border-radius: 6px;
padding: 10px 16px;
}
#polls-panel {
height: calc(100% - 119px);
}
.poll-container {
font-size: 14px;
font-weight: 600;
height: calc(100% - 88px);
line-height: 20px;
overflow-y: auto;
position: relative;
& > * + *:not(.ignore-child) {
margin-top: 16px;
}
@media (max-width: 580px) {
height: calc(100% - 102px);
}
}
.poll-create-header {
color: #fff;
font-size: 20px;
line-height: 28px;
margin: 20px 16px;
font-weight: 600;
}
.poll-create-container {
padding: 8px 0;
}
.poll-create-footer {
background-color: #141414;
bottom: 0;
position: absolute;
width: calc(100% - 32px);
}
.poll-footer {
display: flex;
justify-content: space-between;
padding: 0 16px 16px 16px;
}
.poll-answer-footer {
padding: 8px 0 0 0;
}

View File

@@ -1,33 +1,49 @@
/**
* Mousemove padding styles are used to add invisible elements to the popover
* to allow mouse movement from the popover trigger to the popover itself
* without triggering a mouseleave event.
*/
%vertical-popover-padding {
height: 100%;
position: absolute;
top: 0;
width: 20px;
padding: 20px 0;
top: -20px;
}
%horizontal-popover-padding {
height: 25px;
position: absolute;
right: 0;
width: 100%;
padding: 0 35px;
left: -35px;
}
.popover-mousemove-padding-left {
@extend %vertical-popover-padding;
left: -35px;
}
.popover-mousemove-padding-right {
@extend %vertical-popover-padding;
right: -35px;
}
.popover-mousemove-padding-bottom {
@extend %horizontal-popover-padding;
bottom: -40px;
}
.popover-mousemove-padding-top {
@extend %horizontal-popover-padding;
top: -40px;
}
.popover {
margin: -16px -24px;
z-index: $popoverZ;
.popover-content {
position: relative;
}
&.hover {
margin: -16px -24px;
.popover-content {
margin: 16px 24px;
&.top {
bottom: 8px;
}
&.bottom {
top: 4px;
}
&.left {
right: 4px;
}
&.right {
left: 4px;
}
}
}
}
.excalidraw .popover {

View File

@@ -5,15 +5,15 @@
.popupmenu__contents {
.popupmenu__volume-slider {
&::-webkit-slider-runnable-track {
background-color: #246FE5;
background-color: $popupSliderColor;
}
&::-moz-range-track {
background-color: #246FE5;
background-color: $popupSliderColor;
}
&::-ms-fill-lower {
background-color: #246FE5;
background-color: $popupSliderColor;
}
}
}

View File

@@ -4,7 +4,7 @@
width: 280px;
background: $menuBG;
box-shadow: 0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25);
border-radius: 6px;
border-radius: 3px;
padding: 16px;
&.with-gif {
@@ -104,6 +104,10 @@
}
}
.reactions-menu-container {
padding-bottom: 6px;
}
.reactions-animations-container {
position: absolute;
width: 20%;

View File

@@ -18,7 +18,6 @@
align-items: center;
font-size: 14px;
margin-left: 16px;
max-width: 70%;
}
&.space-top {
@@ -83,7 +82,7 @@
background-color: #FFFFFF;
border-radius: 4px;
height: 40px;
width: 40px;
width: 56px;
}
.jitsi-content-recording-icon-container-without-switch {
@@ -197,6 +196,14 @@
padding-bottom: 10px;
}
.helper-link {
cursor: pointer;
font-weight: bold;
display: inline-block;
flex-shrink: 0;
margin-left: auto;
}
.warning-text {
color:#FFD740;
font-size: 12px;

View File

@@ -3,37 +3,38 @@
display: block;
#enter_room {
position: relative;
height: 42px;
.welcome-page-button {
font-size: 16px;
left: 0;
text-align: center;
width: 100%;
font-size: 16px;
left: 0;
position: absolute;
top: 68px;
text-align: center;
width: 100%;
}
}
.header {
background-color: #002637;
.insecure-room-name-warning {
width: 100%;
}
#enter_room {
width: 100%;
.join-meeting-container {
padding: 0;
flex-direction: column;
background: transparent;
}
.enter-room-input-container {
padding-right: 0;
margin-bottom: 10px;
}
.warning-without-link,
.warning-with-link {
top: 120px;
}
}
}
.welcome-tabs {
display: none;
}
.header-text-title {
text-align: center;
}
@@ -55,6 +56,13 @@
.welcome-footer-row-block {
display: block;
}
.welcome-badge {
margin-right: 16px;
}
.welcome-footer {
display: none;
}
}
}

View File

@@ -30,24 +30,24 @@
right: -4px;
top: -3px;
&:hover {
&:hover {
background: #F2F3F4;
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
& svg {
fill: #040404;
&> svg {
fill: #000;
}
&.settings-button-small-icon--disabled {
background: #36383C;
& svg {
fill: #929292;
}
&> svg {
fill: #929292;
}
}
}
& svg {
&> svg {
fill: #fff;
}
@@ -55,7 +55,7 @@
background-color: #36383c;
cursor: default;
& svg {
&> svg {
fill: #929292;
}
}

View File

@@ -12,6 +12,7 @@
&#autoHide.with-always-on {
overflow: hidden;
animation: hideSubject forwards .6s ease-out;
margin-left: 4px;
& > .subject-info-container {
justify-content: flex-start;
@@ -42,6 +43,42 @@
height: 28px;
}
.subject-text {
background: rgba(0, 0, 0, 0.6);
border-radius: 3px 0px 0px 3px;
box-sizing: border-box;
font-size: 14px;
line-height: 28px;
padding: 0 16px;
height: 28px;
max-width: 324px;
@media (max-width: 300px) {
display: none;
}
&--content {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.subject-timer {
background: rgba(0, 0, 0, 0.8);
border-radius: 0px 3px 3px 0px;
box-sizing: border-box;
font-size: 12px;
line-height: 28px;
min-width: 34px;
padding: 0 8px;
height: 28px;
@media (max-width: 300px) {
display: none;
}
}
.details-container {
width: 100%;
display: flex;

View File

@@ -32,14 +32,6 @@
pointer-events: none;
z-index: $toolbarZ + 2;
&.shift-up {
bottom: calc(((#{$newToolbarSize} + 30px) * 2) * -1);
.toolbox-content {
margin-bottom: 46px;
}
}
&.visible {
bottom: 0;
}
@@ -128,16 +120,12 @@
margin: 8px 0;
}
div.hangup-button {
background-color: #CB2233;
.hangup-button {
background-color: $hangupColor;
@media (hover: hover) and (pointer: fine) {
&:hover {
background-color: #E04757;
}
&:active {
background-color: #A21B29;
background-color: $hangupHoverColor;
}
}
@@ -146,16 +134,12 @@ div.hangup-button {
}
}
div.hangup-menu-button {
background-color: #CB2233;
.hangup-menu-button {
background-color: $hangupMenuButtonColor;
@media (hover: hover) and (pointer: fine) {
&:hover {
background-color: #E04757;
}
&:active {
background-color: #A21B29;
background-color: $hangupMenuButtonHoverColor;
}
}

View File

@@ -41,36 +41,3 @@
display: -webkit-flex !important;
display: flex !important;
}
/**
* resets default button styles,
* mostly intended to be used on interactive elements that
* differ from their default styles (e.g. <a>) or have custom styles
*/
.invisible-button {
background: none;
border: none;
color: inherit;
cursor: pointer;
padding: 0;
}
/**
* style an element the same as an <a>
* useful on some cases where we visually have a link but it's actually a <button>
*/
.as-link {
@extend .invisible-button;
display: inline;
color: #44A5FF;
text-decoration: none;
font-weight: bold;
&:focus,
&:hover,
&:active {
text-decoration: underline;
}
}

View File

@@ -4,6 +4,10 @@
* Style variables
*/
$baseFontFamily: -apple-system, BlinkMacSystemFont, 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
$hangupColor:#DD3849;
$hangupHoverColor: #F25363;
$hangupMenuButtonColor:#0056E0;;
$hangupMenuButtonHoverColor: #246FE5;
/**
* Size variables.
@@ -75,6 +79,7 @@ $modalTextColor: #333;
$chatActionsSeparatorColor: rgb(173, 105, 112);
$chatBackgroundColor: #131519;
$chatInputSeparatorColor: #A4B8D1;
$chatLobbyMessageBackgroundColor: #6A50D3;
$chatLobbyActionsSeparatorColor: #6A50D3;
$chatLocalMessageBackgroundColor: #484A4F;
$chatPrivateMessageBackgroundColor: rgb(153, 69, 77);
@@ -161,13 +166,12 @@ $welcomePageHeaderBackground: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0,
$welcomePageHeaderBackgroundPosition: center;
$welcomePageHeaderBackgroundRepeat: none;
$welcomePageHeaderBackgroundSize: cover;
$welcomePageHeaderPaddingBottom: 15px;
$welcomePageHeaderPaddingBottom: 0px;
$welcomePageHeaderTitleMaxWidth: initial;
$welcomePageHeaderTextAlign: center;
$welcomePageHeaderContainerMarginTop: 104px;
$welcomePageHeaderContainerDisplay: flex;
$welcomePageHeaderContainerMargin: $welcomePageHeaderContainerMarginTop auto 0;
$welcomePageHeaderContainerMargin: 104px 32px 0 32px;
$welcomePageHeaderTextTitleMarginBottom: 0;
$welcomePageHeaderTextTitleFontSize: 42px;
@@ -201,6 +205,11 @@ $deepLinkingDialInConferenceIdPadding: inherit;
$deepLinkingDialInConferenceIdBackgroundColor: inherit;
$deepLinkingDialInConferenceIdBorderRadius: inherit;
$deepLinkingDialInConferenceNameFontSize: inherit;
$deepLinkingDialInConferenceNameLineHeight: inherit;
$deepLinkingDialInConferenceNameMarginBottom: none;
$deepLinkingDialInConferenceNameFontWeight: inherit;
$deepLinkingDialInConferenceDescriptionFontSize: 0.8em;
$deepLinkingDialInConferenceDescriptionLineHeight: inherit;
$deepLinkingDialInConferenceDescriptionMarginBottom: none;

87
css/_video-preview.scss Normal file
View File

@@ -0,0 +1,87 @@
.video-preview {
background: none;
display: inline-block;
&-container {
max-height: 344px;
background: $menuBG;
border-radius: 3px;
overflow: auto;
padding: 8px;
margin-bottom: 8px;
}
&-entry {
cursor: pointer;
height: 168px;
margin-bottom: 8px;
position: relative;
width: 284px;
&:last-child {
margin-bottom: 0;
}
&--selected {
border: 3px solid #31B76A;
border-radius: 3px;
cursor: default;
height: 162px;
width: 278px;
}
}
&-video {
border-radius: 3px;
height: 100%;
object-fit: cover;
width: 100%;
}
&-overlay {
background: rgba(42, 58, 75, 0.6);
height: 100%;
position: absolute;
width: 100%;
z-index: 1;
}
&-error {
align-items: center;
display: flex;
height: 100%;
justify-content: center;
position: absolute;
width: 100%;
}
&-label {
bottom: 8px;
color: #fff;
position: absolute;
width: 100%;
z-index: 2;
&-container {
margin: 0 16px;
}
&-text {
background-color: #131519;
border-radius: 3px;
padding: 2px 8px;
font-size: 13px;
line-height: 20px;
margin: 0 auto;
max-width: calc(100% - 16px);
overflow: hidden;
text-overflow: ellipsis;
width: fit-content;
white-space: nowrap;
}
}
// Override @atlaskit/InlineDialog container which is made with styled components
& > div:nth-child(2) {
padding: 0;
}
}

View File

@@ -76,42 +76,9 @@
}
}
.animatedFadeIn {
opacity: 0;
animation: fadeInAnimation 0.3s ease forwards;
}
@keyframes fadeInAnimation {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.animatedFadeOut {
opacity: 1;
animation: fadeOutAnimation 0.3s ease forwards;
}
@keyframes fadeOutAnimation {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
#largeVideoContainer {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
margin: 0 !important;
}
#largeVideoWrapper {

View File

@@ -20,6 +20,7 @@ body.welcome-page {
background-size: $welcomePageHeaderBackgroundSize;
padding-bottom: $welcomePageHeaderPaddingBottom;
background-color: #131519;
height: 400px;
overflow: hidden;
position: relative;
@@ -28,16 +29,6 @@ body.welcome-page {
flex-direction: column;
margin: $welcomePageHeaderContainerMargin;
z-index: $zindex2;
align-items: center;
position: relative;
max-width: 688px;
}
.header-watermark-container {
position: absolute;
width: 100%;
height: 100%;
margin-top: calc(20px - #{$welcomePageHeaderContainerMarginTop});
}
.header-text-title {
@@ -61,58 +52,26 @@ body.welcome-page {
}
.insecure-room-name-warning {
align-items: center;
color: $defaultWarningColor;
font-weight: 600;
display: flex;
flex-direction: row;
margin-top: 15px;
max-width: 480px;
width: $welcomePageEnterRoomWidth;
.jitsi-icon {
margin-right: 15px;
svg {
fill: $defaultWarningColor;
& > *:first-child {
fill: none !important;
}
}
}
}
::placeholder {
color: #253858;
}
#enter_room {
display: $welcomePageEnterRoomDisplay;
align-items: center;
max-width: 480px;
width: $welcomePageEnterRoomWidth;
z-index: $zindex2;
height: fit-content;
.join-meeting-container {
margin: $welcomePageEnterRoomMargin;
padding: $welcomePageEnterRoomPadding;
border-radius: 4px;
background-color: #fff;
display: flex;
width: 100%;
text-align: left;
color: #253858;
}
background-color: #fff;
padding: $welcomePageEnterRoomPadding;
border-radius: 4px;
margin: $welcomePageEnterRoomMargin;
.enter-room-input-container {
text-align: left;
color: #253858;
flex-grow: 1;
height: fit-content;
padding-right: 4px;
position: relative;
.enter-room-input {
border-radius: 4px;
border: 0;
background: #fff;
display: inline-block;
@@ -121,23 +80,59 @@ body.welcome-page {
font-size: 14px;
padding-left: 10px;
&.focus-visible {
&:focus {
outline: auto 2px #005fcc;
}
}
.insecure-room-name-warning {
align-items: center;
color: $defaultWarningColor;
display: flex;
flex-direction: row;
margin-top: 15px;
.jitsi-icon {
margin-right: 15px;
svg {
fill: $defaultWarningColor
}
}
}
::placeholder {
color: #253858;
}
}
.warning-without-link {
position: absolute;
top: 44px;
left: -10px;
}
.warning-with-link {
position: absolute;
top: 84px;
}
}
#moderated-meetings {
max-width: calc(100% - 40px);
padding: 16px 0 0;
padding: 16px 0 39px 0;
width: $welcomePageEnterRoomWidth;
text-align: center;
a {
color: inherit;
font-weight: 600;
p {
color: $welcomePageDescriptionColor;
float: left;
text-align: $welcomePageHeaderTextAlign;
a {
color: inherit;
font-weight: 600;
}
}
}
}
@@ -167,7 +162,7 @@ body.welcome-page {
margin: 4px;
display: $welcomePageTabButtonsDisplay;
[role="tab"] {
.tab {
background-color: #c7ddff;
border-radius: 7px;
cursor: pointer;
@@ -176,10 +171,8 @@ body.welcome-page {
margin: 2px;
padding: 7px 0;
text-align: center;
color: inherit;
border: 0;
&[aria-selected="true"] {
&.selected {
background-color: #FFF;
}
}
@@ -207,8 +200,8 @@ body.welcome-page {
color: $welcomePageDescriptionColor;
padding: 4px;
position: absolute;
top: calc(35px - #{$welcomePageHeaderContainerMarginTop});
right: 0;
top: 32px;
right: 32px;
z-index: $zindex2;
* {
@@ -231,11 +224,6 @@ body.welcome-page {
width: $welcomePageWatermarkWidth;
height: $welcomePageWatermarkHeight;
}
.watermark.leftwatermarknomargin {
width: $welcomePageWatermarkWidth;
height: $welcomePageWatermarkHeight;
}
}
&.without-content {
@@ -254,17 +242,10 @@ body.welcome-page {
padding-top: 40px;
}
.welcome-card-column {
.welcome-card-row {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
max-width: 688px;
margin: auto;
> div {
margin-bottom: 16px;
}
padding: 0 32px;
}
.welcome-card-text {
@@ -272,7 +253,7 @@ body.welcome-page {
}
.welcome-card {
width: 100%;
width: 49%;
border-radius: 8px;
&--dark {
@@ -287,6 +268,10 @@ body.welcome-page {
&--grey {
background: #F2F3F4;
}
&--shadow {
box-shadow: 0px 4px 30px rgba(0, 0, 0, 0.15);
}
}
.welcome-footer {

View File

@@ -0,0 +1,101 @@
.button-control {
box-sizing: border-box;
display: inline-block;
border: 1px solid $buttonBorder;
vertical-align: baseline;
height: 30px;
min-width: 60px;
padding: 4px 10px;
margin: 0;
line-height: 1.5em;
outline: none;
background-color: transparent;
float: right;
font-size: 14px;
margin-left: 10px;
color: $buttonColor;
font-weight: $buttonFontWeight;
@include transition(background-color .1s ease-out);
&[disabled] {
color: #666;
cursor: default;
}
&_full-width {
margin: 0;
width: 100%;
}
&:hover {
border: 1px solid $buttonHoverBorder;
background-color: $buttonHoverBackground;
@include transition(background-color .1s ease-in);
}
&:active {
@include box-shadow(0, 0, 1px, $buttonShadowColor, true);
}
&_light {
color: $defaultDarkColor;
background-color: $buttonLightBackground;
border: 1px solid $buttonLightBorder;
&:hover {
border: 1px solid $buttonLightHoverBorder;
background-color: $buttonLightHoverBackground;
}
}
&_link {
color: $buttonLinkColor;
background-color: $buttonLinkBackground;
&:hover {
background-color: $buttonLinkBackground;
}
}
&_overlay {
color: $primaryButtonColor;
background-color: $overlayButtonBg;
border-radius: 2px;
border: none;
&:hover {
background-color: $primaryButtonBackground;
border: none;
}
}
&_primary {
background-color: $primaryButtonBackground;
border: 1px solid $primaryButtonBackground;
color: $primaryButtonColor !important;
font-weight: $primaryButtonFontWeight;
&:hover {
border: 1px solid $primaryButtonHoverBackground;
background-color: $primaryButtonHoverBackground;
}
&[disabled] {
color: $primaryButtonColor;
}
}
&_close {
color: $defaultFontColor;
}
&_submit {
color: $linkFontColor;
&:hover {
color: $linkHoverFontColor;
}
}
&_center {
float: none !important;
}
}

View File

@@ -0,0 +1,49 @@
.form-control {
padding: $formPadding 0;
&:first-child {
padding-top: 0;
}
&:last-child {
padding-bottom: 0;
}
&__text {
margin: 8px 0;
font-size: 1em
}
&__label {
font-size: 1em;
font-weight: $labelFontWeight;
}
&__em {
color: $inputControlEmColor;
}
&__container {
position: relative;
width: 100%;
margin-top: 5px;
margin-bottom: 5px;
@include flex();
.button-control {
margin: 1px 0 1px 10px;
}
}
&__right {
position: absolute;
right: 0;
}
}
/**
* Set a specific color for read only style.
*/
input:read-only {
color: $readOnlyInputColor;
}

View File

@@ -0,0 +1,29 @@
.input-control {
@include transition(all .2s ease-in);
display: inline-block;
width: 100%;
padding: 5px 7px;
border-radius: $borderRadius;
line-height: 32px;
height: 32px;
text-align: left;
margin-bottom: 8px;
&:last-child {
margin-bottom: inherit;
}
&::selection {
background-color: $defaultDarkSelectionColor;
}
&.error {
color: $errorColor;
border-color: $errorColor;
}
}
@include placeholder {
color: $placeHolderColor;
}

20
css/components/_link.scss Normal file
View File

@@ -0,0 +1,20 @@
.link {
cursor: pointer;
color: $linkFontColor;
@include transition(color .1s ease-out);
&:hover {
color: $linkHoverFontColor;
text-decoration: underline;
@include transition(color .1s ease-in);
}
}
/**
* Helper links are links that are meant to open a documentation page or more
* detailed info.
*/
.helper-link {
@extend .link;
font-size: 12px;
}

View File

@@ -67,13 +67,6 @@
font-size: 1em;
}
.dial-in-conference-id {
text-align: center;
min-width: 200px;
margin-top: 40px;
}
.dial-in-conference-id {
margin: $deepLinkingDialInConferenceIdMargin;
padding: $deepLinkingDialInConferenceIdPadding;
@@ -81,12 +74,24 @@
border-radius: $deepLinkingDialInConferenceIdBorderRadius;
}
.dial-in-conference-name {
font-size: $deepLinkingDialInConferenceNameFontSize;
line-height: $deepLinkingDialInConferenceNameLineHeight;
margin-bottom: $deepLinkingDialInConferenceNameMarginBottom;
font-weight: $deepLinkingDialInConferenceNameFontWeight;
}
.dial-in-conference-description {
font-size: $deepLinkingDialInConferenceDescriptionFontSize;
line-height: $deepLinkingDialInConferenceDescriptionLineHeight;
margin-bottom: $deepLinkingDialInConferenceDescriptionMarginBottom;
}
.dial-in-conference-pin {
font-size: $deepLinkingDialInConferencePinFontSize;
line-height: $deepLinkingDialInConferencePinLineHeight;
}
.toll-free-list {
min-width: 80px;
}

View File

@@ -25,6 +25,7 @@ $flagsImagePath: "../images/";
/* Modules BEGIN */
@import 'reset';
@import 'atlaskit_overrides';
@import 'base';
@import 'utils';
@import 'overlay/overlay';
@@ -32,7 +33,10 @@ $flagsImagePath: "../images/";
@import 'reload_overlay/reload_overlay';
@import 'mini_toolbox';
@import 'modals/desktop-picker/desktop-picker';
@import 'modals/device-selection/device-selection';
@import 'modals/dialog';
@import 'modals/embed-meeting/embed-meeting';
@import 'modals/feedback/feedback';
@import 'modals/invite/info';
@import 'modals/screen-share/share-audio';
@import 'modals/screen-share/share-screen-warning';
@@ -49,6 +53,10 @@ $flagsImagePath: "../images/";
@import 'welcome_page_settings_toolbar';
@import 'toolbars';
@import 'redirect_page';
@import 'components/form-control';
@import 'components/link';
@import 'components/button-control';
@import 'components/input-control';
@import 'components/input-slider';
@import '404';
@import 'policy';
@@ -60,6 +68,7 @@ $flagsImagePath: "../images/";
@import 'filmstrip/vertical_filmstrip';
@import 'filmstrip/vertical_filmstrip_overrides';
@import 'unsupported-browser/main';
@import 'modals/invite/add-people';
@import 'deep-linking/main';
@import 'transcription-subtitles';
@import '_meetings_list.scss';
@@ -70,7 +79,10 @@ $flagsImagePath: "../images/";
@import 'chrome-extension-banner';
@import 'settings-button';
@import 'meter';
@import 'audio-preview';
@import 'video-preview';
@import 'premeeting/main';
@import 'country-picker';
@import 'modals/invite/invite_more';
@import 'modals/security/security';
@import 'e2ee';
@@ -80,5 +92,6 @@ $flagsImagePath: "../images/";
@import 'reactions-menu';
@import 'plan-limit';
@import 'polls';
@import 'notifications';
/* Modules END */

View File

@@ -12,6 +12,24 @@
}
}
/**
* Styling inline dialog errors.
*/
.inline-dialog-error {
margin-top: 16px;
&-text {
color: $dialogErrorText;
margin-bottom: 8px;
text-align: center;
}
&-button {
display: block;
margin: 16px auto 0 auto;
}
}
/**
* Styling shared video dialog errors.
*/

View File

@@ -63,8 +63,3 @@
.desktop-source-preview-image-container {
padding: 10px;
}
.desktop-picker-tabs-container {
width: 65%;
margin-top: 3px;
}

View File

@@ -0,0 +1,148 @@
.device-selection {
.device-selectors {
font-size: 14px;
> div {
display: block;
margin-bottom: 4px;
}
.device-selector-icon {
align-self: center;
color: inherit;
font-size: 20px;
margin-left: 3px;
}
.device-selector-label {
margin-bottom: 1px;
}
/* device-selector-trigger stylings attempt to mimic AtlasKit button */
.device-selector-trigger {
background-color: #0E1624;
border: 1px solid #455166;
border-radius: 5px;
display: flex;
height: 2.3em;
justify-content: space-between;
line-height: 2.3em;
overflow: hidden;
padding: 0 8px;
}
.device-selector-trigger-disabled {
.device-selector-trigger {
color: #a5adba;
cursor: default;
}
}
.device-selector-trigger-text {
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
}
.device-selection-column {
box-sizing: border-box;
display: inline-block;
vertical-align: top;
&.column-selectors {
margin-left: 15px;
width: 45%;
}
&.column-video {
width: 50%;
}
}
.device-selection-video-container {
border-radius: 3px;
margin-bottom: 5px;
.video-input-preview {
margin-top: 2px;
position: relative;
> video {
border-radius: 3px;
}
.video-input-preview-error {
color: $participantNameColor;
display: none;
left: 0;
position: absolute;
right: 0;
text-align: center;
top: 50%;
}
&.video-preview-has-error {
background: black;
.video-input-preview-error {
display: block;
}
}
.video-input-preview-display {
height: auto;
overflow: hidden;
width: 100%;
}
}
}
.audio-output-preview {
font-size: 14px;
a {
color: #6FB1EA;
cursor: pointer;
text-decoration: none;
}
a:hover {
color: #B3D4FF;
}
}
.audio-input-preview {
background: #1B2638;
border-radius: 5px;
height: 8px;
.audio-input-preview-level {
background: #75B1FF;
border-radius: 5px;
height: 100%;
-webkit-transition: width .1s ease-in-out;
-moz-transition: width .1s ease-in-out;
-o-transition: width .1s ease-in-out;
transition: width .1s ease-in-out;
}
}
}
.device-selection.video-hidden {
display: flex;
flex-direction: column;
width: 100%;
.column-selectors {
width: 100%;
margin-left: 0;
}
.column-video {
order: 1;
width: 100%;
margin-top: 8px;
}
}

View File

@@ -0,0 +1,42 @@
.embed-meeting {
&-dialog {
display: flex;
flex-direction: column;
}
&-copy {
color: white;
font-size: 15px;
margin-left: auto;
margin-top: 16px;
}
&-code {
background: transparent;
border: 1px solid #A4B8D1;
color: white;
font-size: 15px;
height: 165px;
line-height: 24px;
padding: 8px;
width: 100%;
resize: vertical;
}
&-trigger {
display: flex;
align-items: center;
padding: 8px 8px 8px 16px;
margin-top: 24px;
width: calc(100% - 24px);
height: 24px;
background: #2A3A4B;
border: 1px solid #5E6D7A;
border-radius: 4px;
cursor: pointer;
.jitsi-icon {
margin-right: 20px;
}
}
}

View File

@@ -0,0 +1,104 @@
@-webkit-keyframes shake-rotate {
0% {
-webkit-transform:scale(1) rotate(0deg);
transform:scale(1) rotate(0deg)
}
50% {
-webkit-transform:scale(.8) rotate(-5deg);
transform:scale(.8) rotate(-5deg)
}
to {
-webkit-transform:scale(1) rotate(3deg);
transform:scale(1) rotate(3deg)
}
}
@keyframes shake-rotate {
0% {
-webkit-transform:scale(1) rotate(0deg);
transform:scale(1) rotate(0deg)
}
50% {
-webkit-transform:scale(.8) rotate(-5deg);
transform:scale(.8) rotate(-5deg)
}
to {
-webkit-transform:scale(1) rotate(3deg);
transform:scale(1) rotate(3deg)
}
}
.shake-rotate {
display: inline-block;
-webkit-animation-duration: .4s;
animation-duration: .4s;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
-webkit-animation-name: shake-rotate;
animation-name: shake-rotate;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out
}
.feedback-dialog {
margin-bottom: 5px;
.details {
textarea {
min-height: 100px;
}
}
.input-control {
background-color: $feedbackInputBg;
color: $feedbackInputTextColor;
&::-webkit-input-placeholder {
color: $feedbackInputPlaceholderColor;
}
&::-moz-placeholder { /* Firefox 19+ */
color: $feedbackInputPlaceholderColor;
}
&:-ms-input-placeholder {
color: $feedbackInputPlaceholderColor;
}
}
.rating {
line-height: 1.2;
margin-top: 10px;
text-align: center;
.star-label {
font-size: 14px;
height: 16px;
}
.star-btn {
color: inherit;
cursor: pointer;
display: inline-block;
font-size: 34px;
outline: none;
position: relative;
text-decoration: none;
@include transition(all .2s ease);
&.active,
&:hover,
&.starHover {
color: #36B37E;
};
}
.star-btn:focus,
.star-btn:active {
outline: 1px solid #B8C7E0;
}
}
}

View File

@@ -41,3 +41,10 @@
}
}
}
/**
* Styles errors in the MultiSelectAutocomplete.
*/
.autocomplete-error {
min-width: 260px;
}

View File

@@ -50,21 +50,19 @@
}
.dial-in-numbers-list {
max-width: 334px;
width: 100%;
margin-top: 20px;
font-size: 12px;
line-height: 24px;
border-collapse: collapse;
* {
user-select: text;
}
thead {
text-align: left;
}
tr {
border-bottom: 1px solid #d1dbe8;
}
.flag-cell {
vertical-align: top;
width: 30px;
@@ -93,7 +91,6 @@
font-weight: bold;
list-style: none;
vertical-align: top;
text-align: right;
}
li.toll-free:empty:before {
@@ -122,6 +119,11 @@
margin-top: 40px;
}
.dial-in-conference-name,
.dial-in-conference-pin {
font-size: 18px;
}
.dial-in-conference-description {
margin: 12px;
}

View File

@@ -1,4 +1,57 @@
.invite-more {
&-container {
margin-bottom: 8px;
transition: margin-bottom 0.3s;
&.elevated {
margin-bottom: 36px;
}
}
&-content {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
background: rgba(0, 0, 0, 0.7);
border-radius: 8px;
color: #fff;
font-size: 14px;
line-height: 24px;
font-weight: 600;
}
&-header {
max-width: 100%;
margin-bottom: 16px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
&-button {
display: flex;
max-width: 100%;
height: 40px;
box-sizing: border-box;
padding: 8px 16px;
background: #0376DA;
border-radius: 3px;
cursor: pointer;
@media (hover: hover) and (pointer: fine) {
&:hover {
background: #278ADF;
}
}
&-text {
margin-left: 8px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
&-dialog {
color: #fff;
font-size: 15px;
@@ -12,6 +65,59 @@
background: #5E6D7A;
}
&.email-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 8px 8px 16px;
margin-top: 24px;
width: calc(100% - 26px);
height: 22px;
background: #2A3A4B;
border: 1px solid #5E6D7A;
border-radius: 3px;
cursor: pointer;
&.active {
border-radius: 3px 3px 0 0;
}
}
&.invite-buttons {
width: 100%;
text-align: right;
margin-top: 8px;
& > a {
display: inline-block;
height: 24px;
min-width: 48px;
border-radius: 3px;
text-align: center;
text-decoration: none;
cursor: pointer;
}
&-cancel {
margin-right: 16px;
padding: 7px 15px;
background: #2A3A4B;
border: 1px solid #5E6D7A;
}
&-add {
padding: 8px 16px;
background: #0376DA;
}
&.disabled {
& > a {
pointer-events: none;
}
}
}
&.stream {
display: flex;
justify-content: space-between;
@@ -52,3 +158,14 @@
}
}
}
.mobile-browser {
.invite-more-content {
font-size: 16px;
}
.invite-more-button {
height: 48px;
padding: 12px 16px;
}
}

View File

@@ -1,3 +1,7 @@
.share-audio-dialog-container {
max-height: none;
}
.share-audio-dialog {
.share-audio-animation {
width: 100%;

View File

@@ -21,7 +21,7 @@
&-actions {
margin-top: 10px;
button {
a {
cursor: pointer;
text-decoration: none;
font-size: 14px;

View File

@@ -208,26 +208,3 @@
.lobby-button-margin {
margin-bottom: 16px;
}
.lobby-prejoin-error {
background-color: #E04757;
border-radius: 6px;
box-sizing: border-box;
color: white;
font-size: 12px;
line-height: 16px;
margin-bottom: 16px;
margin-top: -8px;
padding: 4px;
text-align: center;
width: 100%;
}
.lobby-prejoin-input {
margin-bottom: 16px;
width: 100%;
& input {
text-align: center;
}
}

View File

@@ -1,3 +1,4 @@
@import 'lobby';
@import 'premeeting-screens';
@import 'prejoin';
@import 'prejoin-third-party';

View File

@@ -3,7 +3,6 @@ $sidePanelWidth: 300px;
.prejoin-third-party {
flex-direction: column-reverse;
z-index: auto;
align-items: center;
.content {
height: auto;

View File

@@ -0,0 +1,60 @@
.prejoin {
&-input-area {
width: 100%;
}
&-avatar {
margin: 8px auto 16px;
&-name {
color: white;
font-size: 16px;
font-weight: 600;
line-height: 26px;
margin-bottom: 32px;
text-align: center;
}
&-container {
align-items: center;
display: flex;
flex-direction: column;
}
}
&-error {
background-color: #E04757;
border-radius: 6px;
box-sizing: border-box;
color: white;
font-size: 12px;
line-height: 16px;
margin-bottom: 16px;
margin-top: -8px;
padding: 4px;
text-align: center;
width: 100%;
}
}
.prejoin-preview {
&-dropdown-btns {
padding: 8px 0;
}
&-dropdown-container {
position: relative;
width: 100%;
/**
* Override default InlineDialog behaviour, since it does not play nicely with relative widths
*/
& > div:nth-child(2) {
background: #fff;
padding: 0;
position: absolute !important;
width: 100%;
}
}
}

View File

@@ -1,4 +1,14 @@
.premeeting-screen {
.premeeting-screen {
background: #292929;
bottom: 0;
display: flex;
font-size: 1.3em;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: $toolbarZ + 2;
.action-btn {
border-radius: 6px;
box-sizing: border-box;
@@ -65,44 +75,139 @@
}
}
#new-toolbox {
bottom: 0;
.content {
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
flex-shrink: 0;
height: 100%;
margin: 0 30px;
padding: 24px 0 16px;
position: relative;
transition: none;
width: $prejoinDefaultContentWidth;
z-index: $toolbarZ + 2;
.toolbox-content {
margin-bottom: 4px;
}
.toolbox-content-items {
@include ltr;
background: transparent;
box-shadow: none;
&-controls {
align-items: center;
display: flex;
justify-content: space-between;
padding: 8px 0;
}
flex-direction: column;
margin: auto;
width: 100%;
.toolbox-content,
.toolbox-content-wrapper,
.toolbox-content-items {
box-sizing: border-box;
width: auto;
.title {
color: #fff;
font-size: 28px;
font-weight: 600;
letter-spacing: -0.015;
line-height: 36px;
margin-bottom: 16px;
text-align: center;
}
input.field {
background-color: white;
border: none;
outline: none;
border-radius: 6px;
font-size: 14px;
line-height: 20px;
margin-bottom: 16px;
color: #1C2025;
padding: 10px 16px;
text-align: center;
width: 100%;
&.error {
border: 1px solid #E04757;
}
&.focused {
box-shadow: 0px 0px 1px 1.5px black, 0px 0px 1.3px 4px white;
}
}
#new-toolbox {
bottom: 0;
position: relative;
transition: none;
.toolbox-content {
margin-bottom: 4px;
}
.toolbox-content-items {
@include ltr;
background: transparent;
box-shadow: none;
display: flex;
justify-content: space-evenly;
padding: 8px 0;
}
.toolbox-content,
.toolbox-content-wrapper,
.toolbox-content-items {
box-sizing: border-box;
width: 100%;
}
}
}
}
@media (max-width: 720px) {
flex-direction: column-reverse;
.content {
height: auto;
margin: 0 auto;
}
}
// mobile phone landscape
@media (max-height: 420px) {
div.content {
padding: 16px 16px 0 16px;
}
}
@media (max-width: 400px) {
.content {
padding: 16px;
width: 100%;
&-controls {
input.field {
font-size: 16px;
padding: 14px 16px;
}
}
.title {
display: none;
}
}
.device-status-error {
border-radius: 0;
margin: 0 -16px;
}
input.field {
font-size: 16px;
padding: 14px 16px;
}
.action-btn {
font-size: 16px;
margin-bottom: 8px;
padding: 11px 16px;
}
}
input::placeholder {
color: #040404;
}
}
#preview {

View File

@@ -65,6 +65,7 @@ $errorColor: #c61600;
// Popover colors
$popoverFontColor: #ffffff !important;
$popupSliderColor: #0376da;
// Toolbar
$toolbarBackground: rgba(0, 0, 0, 0.5);

View File

@@ -44,7 +44,12 @@ case "$1" in
fi
JVB_SECRET="$RET"
JICOFO_AUTH_USER="focus"
db_get jicofo/jicofo-authuser
if [ -z "$RET" ] ; then
db_input critical jicofo/jicofo-authuser || true
db_go
fi
JICOFO_AUTH_USER="$RET"
db_get jicofo/jicofo-authpassword
if [ -z "$RET" ] ; then

View File

@@ -13,6 +13,12 @@ Type: password
_Description: Jitsi Videobridge Component secret:
The secret used by Jitsi Videobridge to connect to xmpp server as component.
Template: jicofo/jicofo-authuser
Type: string
Default: focus
_Description: Jicofo username:
The jicofo needs an authenticated admin user to connect to xmpp server.
Template: jicofo/jicofo-authpassword
Type: password
_Description: Jicofo user password:

View File

@@ -1,6 +1,3 @@
doc/debian/jitsi-meet/jitsi-meet.example /usr/share/jitsi-meet-web-config/
doc/debian/jitsi-meet/jitsi-meet.example-apache /usr/share/jitsi-meet-web-config/
config.js /usr/share/jitsi-meet-web-config/
doc/jaas/nginx-jaas.conf /usr/share/jitsi-meet-web-config/
doc/jaas/index-jaas.html /usr/share/jitsi-meet-web-config/
doc/jaas/8x8.vc-config.js /usr/share/jitsi-meet-web-config/

View File

@@ -176,10 +176,9 @@ case "$1" in
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."
FIX_MSG="//Enables multi-stream, do not delete me"
if ! grep -q "^${FIX_MSG}" $JITSI_MEET_CONFIG; then
echo $FIX_MSG >> $JITSI_MEET_CONFIG
echo "config.flags = config.flags || {};" >> $JITSI_MEET_CONFIG
sed -i "s#config.flags.sourceNameSignaling#${FIX_MSG}\nconfig.flags = config.flags || {};\nconfig.flags.sourceNameSignaling#g" $JITSI_MEET_CONFIG
fi
if ! grep -q "^config.flags.sourceNameSignaling*" $JITSI_MEET_CONFIG; then
echo "config.flags.sourceNameSignaling = true;" >> $JITSI_MEET_CONFIG

View File

@@ -8,9 +8,8 @@ sounds /usr/share/jitsi-meet/
fonts /usr/share/jitsi-meet/
images /usr/share/jitsi-meet/
lang /usr/share/jitsi-meet/
connection_optimization /usr/share/jitsi-meet/
resources/robots.txt /usr/share/jitsi-meet/
resources/*.sh /usr/share/jitsi-meet/scripts/
pwa-worker.js /usr/share/jitsi-meet/
manifest.json /usr/share/jitsi-meet/
doc/jaas/move-to-jaas.sh /usr/share/jitsi-meet/scripts/
doc/jaas/update-asap-daily.sh /usr/share/jitsi-meet/scripts/

View File

@@ -78,18 +78,13 @@ Component "conference.jitmeet.example.com" "muc"
restrict_room_creation = true
storage = "memory"
modules_enabled = {
"muc_hide_all";
"muc_meeting_id";
"muc_domain_mapper";
"polls";
--"token_verification";
"muc_rate_limit";
"muc_password_whitelist";
}
admins = { "focusUser@auth.jitmeet.example.com" }
muc_password_whitelist = {
"focusUser@auth.jitmeet.example.com"
}
muc_room_locking = false
muc_room_default_public_jids = true
@@ -97,7 +92,6 @@ Component "breakout.jitmeet.example.com" "muc"
restrict_room_creation = true
storage = "memory"
modules_enabled = {
"muc_hide_all";
"muc_meeting_id";
"muc_domain_mapper";
"muc_rate_limit";
@@ -111,7 +105,6 @@ Component "breakout.jitmeet.example.com" "muc"
Component "internal.auth.jitmeet.example.com" "muc"
storage = "memory"
modules_enabled = {
"muc_hide_all";
"ping";
}
admins = { "focusUser@auth.jitmeet.example.com", "jvb@auth.jitmeet.example.com" }
@@ -146,7 +139,6 @@ Component "lobby.jitmeet.example.com" "muc"
muc_room_locking = false
muc_room_default_public_jids = true
modules_enabled = {
"muc_hide_all";
"muc_rate_limit";
"polls";
}

View File

@@ -58,8 +58,6 @@ server {
add_header Strict-Transport-Security "max-age=63072000" always;
set $prefix "";
set $custom_index "";
set $config_js_location /etc/jitsi/meet/jitsi-meet.example.com-config.js;
ssl_certificate /etc/jitsi/meet/jitsi-meet.example.com.crt;
ssl_certificate_key /etc/jitsi/meet/jitsi-meet.example.com.key;
@@ -79,10 +77,8 @@ server {
gzip_proxied no-cache no-store private expired auth;
gzip_min_length 512;
include /etc/jitsi/meet/jaas/*.conf;
location = /config.js {
alias $config_js_location;
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
}
location = /external_api.js {
@@ -96,13 +92,8 @@ server {
proxy_set_header Host $http_host;
}
location ~ ^/_api/public/(.*)$ {
autoindex off;
alias /etc/jitsi/meet/public/$1;
}
# ensure all static content can always be found first
location ~ ^/(libs|css|static|images|fonts|lang|sounds|.well-known)/(.*)$
location ~ ^/(libs|css|static|images|fonts|lang|sounds|connection_optimization|.well-known)/(.*)$
{
add_header 'Access-Control-Allow-Origin' '*';
alias /usr/share/jitsi-meet/$1/$2;
@@ -151,12 +142,11 @@ server {
#}
location ~ ^/([^/?&:'"]+)$ {
set $roomname "$1";
try_files $uri @root_path;
}
location @root_path {
rewrite ^/(.*)$ /$custom_index break;
rewrite ^/(.*)$ / break;
}
location ~ ^/([^/?&:'"]+)/config.js$
@@ -164,14 +154,7 @@ server {
set $subdomain "$1.";
set $subdir "$1/";
alias $config_js_location;
}
# Matches /(TENANT)/pwa-worker.js or /(TENANT)/manifest.json to rewrite to / and look for file
location ~ ^/([^/?&:'"]+)/(pwa-worker.js|manifest.json)$ {
set $subdomain "$1.";
set $subdir "$1/";
rewrite ^/([^/?&:'"]+)/(pwa-worker.js|manifest.json)$ /$2;
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
}
# BOSH for subdomains

View File

@@ -1,7 +0,0 @@
</script>
<script src="https://8x8.vc/<!--# echo var="subdir" default="" -->config.js" onload="{
config.p2p.disabledCodec='VP9';
config.videoQuality.disabledCodec='VP9';
config.e2ee = { externallyManagedKey: true };
}"/>
<script>

View File

@@ -1,22 +0,0 @@
## How to switch your deployment to [JaaS](https://jaas.8x8.vc) in one easy step
Note: By default it will have e2ee(end-to-end) encryption enabled that works only on chromium based browsers (Chrome, Edge, ...). If a participant joins from another browser or mobile the e2ee is turned off.
In order to use your deployment with JaaS you first need to login to your [JaaS Developer console](https://jaas.8x8.vc/#/apikeys) and generate a key pair.
Use `Add API key` button and then `Generate API key pair`. Make sure you download the generated private key from:
<img src="generated_key_dialog.png" height="250">
Make sure you transfer this downloaded private key to your server. Copy the key id from:
<img src="api_keys_kid.png" height="200">
Now on your server run the helper script passing the private key file and the key id:
```
sudo /usr/share/jitsi-meet/scripts/move-to-jaas.sh /my/path/test-key.pk <key_id>
```
More information about JaaS Api keys at: https://developer.8x8.com/jaas/docs/jaas-console-api-keys
If you want to adjust the enabled services you can do that in /etc/jits/meet/jaas/nginx-jaas.conf. The part after `proxy_set_body` is the jwt token content that will be used for the client tokens. More info about the JaaS tokens: https://developer.8x8.com/jaas/docs/api-keys-jwt

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -1,33 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<script src='external_api.js' async></script>
<style>html, body, #jaas-container { height: 100%; }</style>
<script type="text/javascript">
function getRoomName(pathname) {
const contextRootEndIndex = pathname.lastIndexOf('/');
return pathname.substring(contextRootEndIndex + 1);
}
window.onload = () => {
const jaasJwt = <!--#include virtual="/jaas-jwt" -->;
const api = new JitsiMeetExternalAPI(
window.location.host, {
roomName: `${jaasJwt.tenant}/${jaasJwt.confId}`,
parentNode: document.querySelector('#jaas-container'),
jwt: jaasJwt.token,
e2eeKey: jaasJwt.e2eeKey
});
api.addListener('videoConferenceJoined', () => {
if (jaasJwt.e2eeKey) {
console.info('Toggling e2ee on!')
api.executeCommand('toggleE2EE', true);
}
});
}
</script>
</head>
<body>
<div id="jaas-container" />
</body>
</html>

View File

@@ -1,59 +0,0 @@
#!/bin/bash
set -e
PRIVATE_KEY=$1
JAAS_KEY_ID=$2
if [ ! -f "${PRIVATE_KEY}" ] ; then
echo "You need to specify a correct path for the private key as a first argument."
exit 1;
fi
if [[ ! "${JAAS_KEY_ID}" =~ ^vpaas-magic-cookie-[0-9a-z]+/[0-9a-z]+$ ]]; then
echo "Invalid key id passed as a second argument."
exit 2;
fi
command -v node >/dev/null 2>&1 || { echo >&2 "You must install node first, go to https://nodejs.org. Aborting."; exit 4; }
NODE_VER=$(node -v);
NODE_MAJOR_VER=$(echo ${NODE_VER:1} | cut -d. -f1);
if [ "$NODE_MAJOR_VER" -lt "18" ]; then
echo "Please install latest LTS version of node (18+)";
exit 3;
fi
# we need this util for debconf-set-selections
sudo apt install debconf-utils
# Let's pre-set some settings for token-generator
cat << EOF | sudo debconf-set-selections
token-generator token-generator/private-key string ${PRIVATE_KEY}
token-generator token-generator/kid string ${JAAS_KEY_ID}
EOF
apt install token-generator
mkdir -p /etc/jitsi/meet/jaas
VPASS_COOKIE=$(echo -n ${JAAS_KEY_ID}| cut -d/ -f1)
cp /usr/share/jitsi-meet-web-config/nginx-jaas.conf /etc/jitsi/meet/jaas
sed -i "s/jaas_magic_cookie/${VPASS_COOKIE}/g" /etc/jitsi/meet/jaas/nginx-jaas.conf
cp /usr/share/jitsi-meet-web-config/8x8.vc-config.js /etc/jitsi/meet/jaas/
echo "set \$config_js_location /etc/jitsi/meet/jaas/8x8.vc-config.js;" >> /etc/jitsi/meet/jaas/jaas-vars
echo "set \$custom_index index-jaas.html;" >> /etc/jitsi/meet/jaas/jaas-vars
ln -s /usr/share/jitsi-meet-web-config/index-jaas.html /usr/share/jitsi-meet/index-jaas.html
# let's create the daily key now
/usr/share/jitsi-meet/scripts/update-asap-daily.sh
# let's add to cron daily the update of the asap key
if [ -d /etc/cron.daily ]; then
ln -s /usr/share/jitsi-meet/scripts/update-asap-daily.sh /etc/cron.daily/update-jaas-asap.sh
else
echo "No /etc/cron.daily. Please add to your cron jobs to execute as root daily the script: /usr/share/jitsi-meet/scripts/update-asap-daily.sh"
fi

View File

@@ -1,23 +0,0 @@
include /etc/jitsi/meet/jaas/jaas-vars;
location = /jaas-jwt {
include /etc/jitsi/token-generator/daily-key;
ssi on;
proxy_method POST;
proxy_set_header content-type "application/json";
proxy_set_header Accept-Encoding "";
proxy_set_header Authorization "Bearer $jaas_asap_key";
proxy_pass_request_body off;
proxy_set_body '{"sub":"jaas_magic_cookie","context":{"features":{"livestreaming":false,"outbound-call":false,"sip-outbound-call":false,"transcription":false,"recording":false},"user":{"moderator":true}},"room": "$roomname"}';
proxy_pass http://127.0.0.1:8017/generate/client?e2eeKey=true&confId=true;
}
location @magic_root_path {
rewrite ^/(.*)$ /index.html break;
}
# Anything that didn't match above, and isn't a real file, assume it's a room name and redirect to /
location ~ ^/jaas_magic_cookie/(.*)$ {
set $subdomain "jaas_magic_cookie.";
set $subdir "jaas_magic_cookie/";
try_files $1 @magic_root_path;
}

View File

@@ -1,9 +0,0 @@
JWT_KID=$(cat /etc/jitsi/token-generator/config | grep SYSTEM_ASAP_BASE_URL_MAPPINGS | cut -d= -f2- | jq -r .[].kid)
JWT_DATE=$(echo -n $JWT_KID | cut -d/ -f2-)
JWT_DATE=${JWT_DATE#jwt-}
KEY_FILE=/etc/jitsi/token-generator/daily-key
echo -n "set \$jaas_asap_key " > ${KEY_FILE}
ASAP_KEY=$(ASAP_SIGNING_KEY_FILE=/etc/jitsi/token-generator/asap-${JWT_DATE}.key ASAP_JWT_KID="${JWT_KID}" ASAP_EXPIRES_IN="1 day" node /usr/share/token-generator/jwt.js| tail -n1)
echo -n "${ASAP_KEY};" >> ${KEY_FILE}
service nginx reload

19
globals.d.ts vendored
View File

@@ -10,6 +10,12 @@ declare global {
API: any;
conference: any;
debugLogs: any;
keyboardshortcut: {
registerShortcut: Function;
unregisterShortcut: Function;
openDialog: Function;
enable: Function;
}
};
const interfaceConfig: any;
@@ -19,22 +25,9 @@ declare global {
interfaceConfig?: any;
JitsiMeetJS?: any;
JitsiMeetElectron?: any;
// selenium tests handler
_sharedVideoPlayer: any;
alwaysOnTop: { api: any };
}
interface Document {
mozCancelFullScreen?: Function;
webkitExitFullscreen?: Function;
}
const config: IConfig;
const JitsiMeetJS: any;
interface HTMLMediaElement {
setSinkId: (id: string) => Promise<undefined>;
stop: () => void;
}
}

1
globals.native.d.ts vendored
View File

@@ -30,7 +30,6 @@ interface IWindow {
setImmediate: typeof setImmediate;
clearImmediate: typeof clearImmediate;
addEventListener: Function;
removeEventListener: Function;
}
interface INavigator {

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