Compare commits
260 Commits
android-sd
...
3882
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb0d10fbe1 | ||
|
|
9a162c266c | ||
|
|
6654531112 | ||
|
|
b9cc6b6f96 | ||
|
|
6cbc04175c | ||
|
|
fb23aa54e8 | ||
|
|
b73d98f863 | ||
|
|
0289a93e5a | ||
|
|
401003b907 | ||
|
|
025e2b1ecb | ||
|
|
05a8591110 | ||
|
|
d2e6d57be8 | ||
|
|
41fd142977 | ||
|
|
04c0945930 | ||
|
|
5f2acb70de | ||
|
|
4dc10e82f1 | ||
|
|
582d4aff1c | ||
|
|
b13200ac92 | ||
|
|
53f937ba4e | ||
|
|
b9addaed71 | ||
|
|
fd8fb58eaf | ||
|
|
38d1032fec | ||
|
|
073fdc7b0e | ||
|
|
fedaa0ae6e | ||
|
|
f06fe69167 | ||
|
|
16c09c9645 | ||
|
|
24a1a60f04 | ||
|
|
d7ece58c6f | ||
|
|
aa11535db7 | ||
|
|
eea87be801 | ||
|
|
809ac42e4c | ||
|
|
cf27ad0dde | ||
|
|
2064fc8937 | ||
|
|
95eb551156 | ||
|
|
a7ac7790a8 | ||
|
|
94c48ec838 | ||
|
|
da68b9882d | ||
|
|
bd8a7edbd2 | ||
|
|
bde2343951 | ||
|
|
9b141816d6 | ||
|
|
6a7594cd9c | ||
|
|
cdbc5976a0 | ||
|
|
8fd0f56be7 | ||
|
|
5b0c6e088a | ||
|
|
202abf2a9a | ||
|
|
fb8ef366c6 | ||
|
|
7446e6165e | ||
|
|
5feaa421a8 | ||
|
|
a4333d3a80 | ||
|
|
02131f3346 | ||
|
|
8dbd1ba1b7 | ||
|
|
4c065f2de1 | ||
|
|
df895e5a61 | ||
|
|
bbf1927c70 | ||
|
|
86130c1478 | ||
|
|
15fb8a1525 | ||
|
|
64cbfb648f | ||
|
|
3e40bb19cd | ||
|
|
bbca0fc357 | ||
|
|
9bb789472e | ||
|
|
06fa175a6c | ||
|
|
5940f2890a | ||
|
|
7d09088186 | ||
|
|
34be081d87 | ||
|
|
3c36eece5e | ||
|
|
8eb0acada6 | ||
|
|
8307a8be2d | ||
|
|
d62190c644 | ||
|
|
58c4248da0 | ||
|
|
74af70a1d5 | ||
|
|
7b25b847ba | ||
|
|
18536cb14c | ||
|
|
01e36e1c56 | ||
|
|
5d96a226ed | ||
|
|
9855fac805 | ||
|
|
ed5351d250 | ||
|
|
b64260e554 | ||
|
|
7d67cb583e | ||
|
|
8e3c301d03 | ||
|
|
f9071b8b6b | ||
|
|
01abc4e8a8 | ||
|
|
c5bddda781 | ||
|
|
c2bc92ae05 | ||
|
|
a425e9c92e | ||
|
|
3bfa4744c8 | ||
|
|
bd5901d59c | ||
|
|
306c8ba8c2 | ||
|
|
5a6335207f | ||
|
|
97e8a6c3f3 | ||
|
|
523926d598 | ||
|
|
ab5627212d | ||
|
|
1b6c5a7141 | ||
|
|
a9767eda72 | ||
|
|
6c3a5793b4 | ||
|
|
c05b4a43e8 | ||
|
|
385669cbb8 | ||
|
|
5cfae2c419 | ||
|
|
92740707f0 | ||
|
|
775c531fe7 | ||
|
|
b8eda2b68d | ||
|
|
470c115e7c | ||
|
|
d9cf7aef3a | ||
|
|
c20c4bd5a3 | ||
|
|
74a5eb2d81 | ||
|
|
b25db3ce2e | ||
|
|
31d9fb12c8 | ||
|
|
ca9ca04d0f | ||
|
|
5a027f1585 | ||
|
|
5d86d202bd | ||
|
|
7ea7afebfb | ||
|
|
710307725b | ||
|
|
1cde7e63c7 | ||
|
|
a53d284bbe | ||
|
|
579e650a1d | ||
|
|
ee525ae569 | ||
|
|
63a411168e | ||
|
|
ad68a87dba | ||
|
|
45aafe5432 | ||
|
|
ac0f3979b0 | ||
|
|
d2c2919aef | ||
|
|
a18ed3a779 | ||
|
|
22871f15d0 | ||
|
|
91fb4665d6 | ||
|
|
f6c94fffc4 | ||
|
|
c8939a133d | ||
|
|
f810483ff6 | ||
|
|
c2cf09a2ca | ||
|
|
c73ba37202 | ||
|
|
659eb6b789 | ||
|
|
87821eb2c0 | ||
|
|
8291f4597c | ||
|
|
fddaf7c8a8 | ||
|
|
960ffa7e78 | ||
|
|
779ed6bb5e | ||
|
|
47984d3ec1 | ||
|
|
d9282f873c | ||
|
|
9344138303 | ||
|
|
6c3968a434 | ||
|
|
e8e70d9d27 | ||
|
|
0be68b59b7 | ||
|
|
d5ab3da9f2 | ||
|
|
5ef0f527f9 | ||
|
|
e60a14d5af | ||
|
|
03abb244df | ||
|
|
f9ea8b034f | ||
|
|
ee551ea0a0 | ||
|
|
0b22f8e449 | ||
|
|
e21f99d9d6 | ||
|
|
edb56200d5 | ||
|
|
04313a4aa3 | ||
|
|
cdb606e7ee | ||
|
|
28781965ed | ||
|
|
cbe396be55 | ||
|
|
a3befe336f | ||
|
|
63ea496bcd | ||
|
|
db6547f48d | ||
|
|
34e329a4f2 | ||
|
|
42b71fc4b9 | ||
|
|
dd99268461 | ||
|
|
692c6a47b4 | ||
|
|
b3983aa766 | ||
|
|
a79ae9b576 | ||
|
|
e69da98348 | ||
|
|
e01d891bba | ||
|
|
f904626f5e | ||
|
|
1333fd1975 | ||
|
|
af918f8dc5 | ||
|
|
b7baf8fe98 | ||
|
|
4ebab6f9c0 | ||
|
|
8f93acb464 | ||
|
|
9b60537e0f | ||
|
|
7b9abd34a0 | ||
|
|
ed82443ffa | ||
|
|
a3cd331369 | ||
|
|
9c77ab7f4d | ||
|
|
55983ff62a | ||
|
|
b4be1bcd05 | ||
|
|
2420a68be9 | ||
|
|
ebfc5a95ff | ||
|
|
68cad276bd | ||
|
|
e683d70a18 | ||
|
|
9645391180 | ||
|
|
851b1a76a9 | ||
|
|
4890390ea2 | ||
|
|
7828bf8d46 | ||
|
|
191da551e3 | ||
|
|
55f35933e8 | ||
|
|
b125bff7c7 | ||
|
|
c1d261445e | ||
|
|
c494d6c48b | ||
|
|
4134d47f6e | ||
|
|
0b25e62c5c | ||
|
|
4d0cbff5a1 | ||
|
|
c79463aaee | ||
|
|
339e1c5fab | ||
|
|
36455c24c8 | ||
|
|
a622a4c713 | ||
|
|
1aaaae24ee | ||
|
|
9191000da4 | ||
|
|
8eb93086bd | ||
|
|
b64294af6d | ||
|
|
bbf33a8895 | ||
|
|
bcc1289a23 | ||
|
|
58bd48c1ae | ||
|
|
1a3736bf98 | ||
|
|
0eec182df4 | ||
|
|
c526844eb2 | ||
|
|
d856c1f328 | ||
|
|
15e47a9eb3 | ||
|
|
da98d39b61 | ||
|
|
411bafb5a6 | ||
|
|
0a64bf2068 | ||
|
|
db6a2673de | ||
|
|
e11d4d3101 | ||
|
|
8fd3bb2302 | ||
|
|
fb3a832a52 | ||
|
|
9c146c1245 | ||
|
|
792f506425 | ||
|
|
6121e9fc65 | ||
|
|
955fa1f49f | ||
|
|
2544d0a084 | ||
|
|
8f0a12016a | ||
|
|
34ccd3524f | ||
|
|
563e99ecd3 | ||
|
|
70f14be50f | ||
|
|
8bd0da886e | ||
|
|
1fd326f980 | ||
|
|
d9cc664ea6 | ||
|
|
d65e241056 | ||
|
|
fe2b1f3d9f | ||
|
|
17c1f50fc3 | ||
|
|
5c1c022291 | ||
|
|
72435dee56 | ||
|
|
42f2eff02a | ||
|
|
0b68bef0be | ||
|
|
676e943d81 | ||
|
|
2b4307dee9 | ||
|
|
f3f936c196 | ||
|
|
eb900ddbe1 | ||
|
|
c2c323347a | ||
|
|
af6642b91b | ||
|
|
ffded8d82a | ||
|
|
00b57c7983 | ||
|
|
5d40a8992a | ||
|
|
e543625295 | ||
|
|
0b25ff649e | ||
|
|
63344ac62d | ||
|
|
2e60aafebf | ||
|
|
131e8f4aea | ||
|
|
53f01a39c9 | ||
|
|
50f4796144 | ||
|
|
5bdfae377f | ||
|
|
44970648ea | ||
|
|
3cd7f0b77d | ||
|
|
4d243f9b92 | ||
|
|
6b716f8f56 | ||
|
|
5b99219f29 | ||
|
|
f0dcb51915 | ||
|
|
3ff658a13b | ||
|
|
3a46513d4b |
6
.gitignore
vendored
@@ -61,14 +61,8 @@ buck-out/
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
# screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/
|
||||
|
||||
*/fastlane/report.xml
|
||||
*/fastlane/Preview.html
|
||||
*/fastlane/screenshots
|
||||
|
||||
# Build artifacts
|
||||
*.jsbundle
|
||||
|
||||
17
README.md
@@ -27,10 +27,25 @@ You can download Debian/Ubuntu binaries:
|
||||
You can download source archives (produced by ```make source-package```):
|
||||
* [source builds](https://download.jitsi.org/jitsi-meet/src/)
|
||||
|
||||
You can get our mobile versions from here:
|
||||
### Mobile apps
|
||||
|
||||
* [Android](https://play.google.com/store/apps/details?id=org.jitsi.meet)
|
||||
|
||||
[<img src="resources/img/google-play-badge.png" height="50">](https://play.google.com/store/apps/details?id=org.jitsi.meet)
|
||||
|
||||
* [Android (F-Droid)](https://f-droid.org/en/packages/org.jitsi.meet/)
|
||||
|
||||
[<img src="resources/img/f-droid-badge.png" height="50">](https://f-droid.org/en/packages/org.jitsi.meet/)
|
||||
|
||||
* [iOS](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
|
||||
|
||||
[<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
|
||||
|
||||
You can also sign up for our open beta testing here:
|
||||
|
||||
* [Android](https://play.google.com/apps/testing/org.jitsi.meet)
|
||||
* [iOS](https://testflight.apple.com/join/isy6ja7S)
|
||||
|
||||
## Development
|
||||
|
||||
For web development see [here](doc/development.md), and for mobile see [here](doc/mobile.md).
|
||||
|
||||
@@ -32,12 +32,23 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('debug.keystore')
|
||||
storePassword 'android'
|
||||
keyAlias 'androiddebugkey'
|
||||
keyPassword 'android'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
|
||||
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
|
||||
}
|
||||
release {
|
||||
// Uncomment the following line for singing a test release build.
|
||||
//signingConfig signingConfigs.debug
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-release.pro'
|
||||
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
|
||||
@@ -69,7 +80,9 @@ repositories {
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-5'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
||||
@@ -83,9 +96,6 @@ dependencies {
|
||||
}
|
||||
|
||||
implementation project(':sdk')
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
|
||||
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
|
||||
BIN
android/app/debug.keystore
Normal file
6
android/app/proguard-rules.pro
vendored
@@ -85,4 +85,8 @@
|
||||
# ^^^ We added the above when we switched minifyEnabled on.
|
||||
|
||||
# Rule to avoid build errors related to SVGs.
|
||||
-keep public class com.horcrux.svg.** {*;}
|
||||
-keep public class com.horcrux.svg.** {*;}
|
||||
|
||||
# Hermes
|
||||
-keep class com.facebook.hermes.unicode.** { *; }
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:name=".MainApplication"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
* Copyright @ 2017-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.
|
||||
@@ -91,6 +90,7 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
= new JitsiMeetConferenceOptions.Builder()
|
||||
.setWelcomePageEnabled(true)
|
||||
.setServerURL(buildURL("https://meet.jit.si"))
|
||||
.setFeatureFlag("call-integration.enabled", false)
|
||||
.build();
|
||||
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
|
||||
/**
|
||||
* Simple {@link Application} for hooking up LeakCanary:
|
||||
* https://github.com/square/leakcanary
|
||||
*/
|
||||
public class MainApplication extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
if (!LeakCanary.isInAnalyzerProcess(this)) {
|
||||
LeakCanary.install(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.2'
|
||||
classpath 'com.google.gms:google-services:4.2.0'
|
||||
classpath 'io.fabric.tools:gradle:1.27.0'
|
||||
classpath 'com.google.gms:google-services:4.3.3'
|
||||
classpath 'io.fabric.tools:gradle:1.28.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files.
|
||||
@@ -27,8 +27,6 @@ allprojects {
|
||||
jcenter()
|
||||
// React Native (JS, Obj-C sources, Android binaries) is installed from npm.
|
||||
maven { url "$rootDir/../node_modules/react-native/android" }
|
||||
// Android JSC is installed from npm.
|
||||
maven { url("$rootDir/../node_modules/jsc-android/dist") }
|
||||
}
|
||||
|
||||
// Make sure we use the react-native version in node_modules and not the one
|
||||
@@ -165,50 +163,6 @@ ext {
|
||||
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
|
||||
}
|
||||
|
||||
// If Android SDK is not installed, accept its license so that it
|
||||
// is automatically downloaded.
|
||||
afterEvaluate { project ->
|
||||
// Either the environment variable ANDROID_HOME or the property sdk.dir in
|
||||
// local.properties identifies where Android SDK is installed.
|
||||
def androidHome = System.env.ANDROID_HOME
|
||||
if (!androidHome) {
|
||||
// ANDROID_HOME is not set. Is sdk.dir set?
|
||||
def file = file("${project.rootDir}/local.properties")
|
||||
def props = new Properties()
|
||||
if (file.canRead()) {
|
||||
file.withInputStream {
|
||||
props.load(it)
|
||||
androidHome = props.'sdk.dir'
|
||||
}
|
||||
}
|
||||
if (!androidHome && (!file.exists() || file.canWrite())) {
|
||||
// Neither ANDROID_HOME nor sdk.dir is set. Set sdk.dir (because
|
||||
// environment variables cannot be set).
|
||||
props.'sdk.dir' = "${project.buildDir}/android-sdk".toString()
|
||||
file.withOutputStream {
|
||||
props.store(it, null)
|
||||
androidHome = props.'sdk.dir'
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the license is not accepted, accept it so that automatic downloading
|
||||
// kicks in.
|
||||
// The license hash can be taken from the accepted licenses, by doing this
|
||||
// on your local machine the file is
|
||||
// ${androidHome}/licenses/android-sdk-license
|
||||
if (androidHome) {
|
||||
def dir = file("${androidHome}/licenses")
|
||||
dir.mkdirs()
|
||||
def file = file("${dir.path}/android-sdk-license")
|
||||
if (!file.exists()) {
|
||||
file.withWriter {
|
||||
def hash = 'd56f5187479451eabf01fb78af6dfcb131a6481e'
|
||||
it.write(hash, 0, hash.length())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force the version of the Android build tools we have chosen on all
|
||||
// subprojects. The forcing was introduced for react-native and the third-party
|
||||
// modules that we utilize such as react-native-background-timer.
|
||||
|
||||
@@ -24,7 +24,7 @@ platform :android do
|
||||
|
||||
# Upload built artifact to the Closed Beta track
|
||||
upload_to_play_store(
|
||||
track: "Closed Beta",
|
||||
track: "beta",
|
||||
json_key: ENV["JITSI_JSON_KEY_FILE"],
|
||||
skip_upload_metadata: true,
|
||||
skip_upload_images: true,
|
||||
|
||||
BIN
android/fastlane/screenshots/Feature-Graphic-1024x500-1-1.png
Normal file
|
After Width: | Height: | Size: 342 KiB |
BIN
android/fastlane/screenshots/Feature-Graphic-1024x500-1.png
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
android/fastlane/screenshots/Feature-Graphic-1024x500-2.png
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
android/fastlane/screenshots/Feature-Graphic-1024x500-3.png
Normal file
|
After Width: | Height: | Size: 174 KiB |
BIN
android/fastlane/screenshots/GroupCall.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
android/fastlane/screenshots/GroupCall_framed.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
android/fastlane/screenshots/More Menu.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
android/fastlane/screenshots/More Menu_framed.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
android/fastlane/screenshots/Nexus 9 Body.png
Normal file
|
After Width: | Height: | Size: 6.0 MiB |
BIN
android/fastlane/screenshots/Nexus-9-Landscape.png
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
android/fastlane/screenshots/Nexus-9-Portrait.png
Normal file
|
After Width: | Height: | Size: 4.9 MiB |
BIN
android/fastlane/screenshots/Video-Call-1-1024x768.png
Normal file
|
After Width: | Height: | Size: 694 KiB |
BIN
android/fastlane/screenshots/Video-Call-1-1280x720.png
Normal file
|
After Width: | Height: | Size: 716 KiB |
BIN
android/fastlane/screenshots/Video-Call-2-1024x768.png
Normal file
|
After Width: | Height: | Size: 950 KiB |
BIN
android/fastlane/screenshots/Video-Call-2-1280x720.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
android/fastlane/screenshots/WelcomePage-Calendar.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
android/fastlane/screenshots/WelcomePage-Calendar_framed.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
android/fastlane/screenshots/WelcomeScreen-1024x768.png
Normal file
|
After Width: | Height: | Size: 1000 KiB |
BIN
android/fastlane/screenshots/WelcomeScreen-1280x720.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
@@ -20,5 +20,5 @@
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
appVersion=19.4.0
|
||||
sdkVersion=2.4.0
|
||||
appVersion=20.1.0
|
||||
sdkVersion=2.6.0
|
||||
|
||||
@@ -9,8 +9,8 @@ THE_MVN_REPO=${MVN_REPO:-${1:-$DEFAULT_MVN_REPO}}
|
||||
MVN_HTTP=0
|
||||
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
RN_VERSION=$(jq -r '.dependencies."react-native"' ${THIS_DIR}/../../package.json)
|
||||
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1 | cut -c 2-)
|
||||
RN_VERSION=$(jq -r '.version' ${THIS_DIR}/../../node_modules/react-native/package.json)
|
||||
HERMES_VERSION=$(jq -r '.dependencies."hermes-engine"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -c 2-)
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
if [[ $THE_MVN_REPO == http* ]]; then
|
||||
@@ -38,17 +38,19 @@ if [[ $MVN_HTTP == 1 ]]; then
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=react-native-${RN_VERSION}.pom || true
|
||||
popd
|
||||
# Push JSC
|
||||
echo "Pushing JSC ${JSC_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/jsc-android/dist/org/webkit/android-jsc/${JSC_VERSION}
|
||||
# Push Hermes
|
||||
echo "Pushing Hermes ${HERMES_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/hermes-engine/android/
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-DrepositoryId=${MVN_REPO_ID} \
|
||||
-Dfile=android-jsc-${JSC_VERSION}.aar \
|
||||
-Dfile=hermes-release.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=android-jsc-${JSC_VERSION}.pom || true
|
||||
-DgroupId=com.facebook \
|
||||
-DartifactId=hermes \
|
||||
-Dversion=${HERMES_VERSION} \
|
||||
-DgeneratePom=true || true
|
||||
popd
|
||||
else
|
||||
# Push React Native, if necessary
|
||||
@@ -65,17 +67,19 @@ else
|
||||
popd
|
||||
fi
|
||||
|
||||
# Push JSC, if necessary
|
||||
if [[ ! -d ${MVN_REPO}/org/webkit/android-jsc/${JSC_VERSION} ]]; then
|
||||
echo "Pushing JSC ${JSC_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/jsc-android/dist/org/webkit/android-jsc/${JSC_VERSION}
|
||||
# Push Hermes, if necessary
|
||||
if [[ ! -d ${MVN_REPO}/com/facebook/hermes/${HERMES_VERSION} ]]; then
|
||||
echo "Pushing Hermes ${HERMES_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/hermes-engine/android/
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-Dfile=android-jsc-${JSC_VERSION}.aar \
|
||||
-Dfile=hermes-release.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=android-jsc-${JSC_VERSION}.pom
|
||||
-DgroupId=com.facebook \
|
||||
-DartifactId=hermes \
|
||||
-Dversion=${HERMES_VERSION} \
|
||||
-DgeneratePom=true
|
||||
popd
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
@@ -31,17 +33,26 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst '**/libc++_shared.so'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.fragment:fragment:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.fragment:fragment:1.2.0'
|
||||
|
||||
//noinspection GradleDynamicVersion
|
||||
api 'com.facebook.react:react-native:+'
|
||||
implementation 'org.webkit:android-jsc:+'
|
||||
|
||||
// Hermes JS engine
|
||||
def hermesPath = "../../node_modules/hermes-engine/android/"
|
||||
debugImplementation files(hermesPath + "hermes-debug.aar")
|
||||
releaseImplementation files(hermesPath + "hermes-release.aar")
|
||||
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
@@ -213,6 +224,14 @@ publishing {
|
||||
dependency.appendNode('artifactId', artifactId)
|
||||
dependency.appendNode('version', it.moduleVersion)
|
||||
}
|
||||
|
||||
// Add Hermes dependency.
|
||||
def hermesPkg = new File("$rootDir/../node_modules/hermes-engine/package.json")
|
||||
def hermesVersion = new JsonSlurper().parseText(hermesPkg.text).version
|
||||
def hermesDependency = dependencies.appendNode('dependency')
|
||||
hermesDependency.appendNode('groupId', "com.facebook")
|
||||
hermesDependency.appendNode('artifactId', "hermes")
|
||||
hermesDependency.appendNode('version', hermesVersion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
@@ -49,8 +53,16 @@ class AmplitudeModule
|
||||
* @param apiKey The API_KEY of the Amplitude project.
|
||||
*/
|
||||
@ReactMethod
|
||||
@SuppressLint("HardwareIds")
|
||||
public void init(String instanceName, String apiKey) {
|
||||
Amplitude.getInstance(instanceName).initialize(getCurrentActivity(), apiKey);
|
||||
|
||||
// Set the device ID to something consistent.
|
||||
String android_id
|
||||
= Settings.Secure.getString(getReactApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
if (!TextUtils.isEmpty(android_id)) {
|
||||
Amplitude.getInstance(instanceName).setDeviceId(android_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.telecom.CallAudioState;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.HashSet;
|
||||
@@ -37,6 +39,11 @@ class AudioDeviceHandlerConnectionService implements
|
||||
|
||||
private final static String TAG = AudioDeviceHandlerConnectionService.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* {@link AudioManager} instance used to interact with the Android audio subsystem.
|
||||
*/
|
||||
private AudioManager audioManager;
|
||||
|
||||
/**
|
||||
* Reference to the main {@code AudioModeModule}.
|
||||
*/
|
||||
@@ -52,20 +59,20 @@ class AudioDeviceHandlerConnectionService implements
|
||||
*/
|
||||
private static int audioDeviceToRouteInt(String audioDevice) {
|
||||
if (audioDevice == null) {
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
return CallAudioState.ROUTE_SPEAKER;
|
||||
}
|
||||
switch (audioDevice) {
|
||||
case AudioModeModule.DEVICE_BLUETOOTH:
|
||||
return android.telecom.CallAudioState.ROUTE_BLUETOOTH;
|
||||
return CallAudioState.ROUTE_BLUETOOTH;
|
||||
case AudioModeModule.DEVICE_EARPIECE:
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
return CallAudioState.ROUTE_EARPIECE;
|
||||
case AudioModeModule.DEVICE_HEADPHONES:
|
||||
return android.telecom.CallAudioState.ROUTE_WIRED_HEADSET;
|
||||
return CallAudioState.ROUTE_WIRED_HEADSET;
|
||||
case AudioModeModule.DEVICE_SPEAKER:
|
||||
return android.telecom.CallAudioState.ROUTE_SPEAKER;
|
||||
return CallAudioState.ROUTE_SPEAKER;
|
||||
default:
|
||||
JitsiMeetLogger.e(TAG + " Unsupported device name: " + audioDevice);
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
return CallAudioState.ROUTE_SPEAKER;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,20 +85,16 @@ class AudioDeviceHandlerConnectionService implements
|
||||
*/
|
||||
private static Set<String> routesToDeviceNames(int supportedRouteMask) {
|
||||
Set<String> devices = new HashSet<>();
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_EARPIECE)
|
||||
== android.telecom.CallAudioState.ROUTE_EARPIECE) {
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_EARPIECE) == CallAudioState.ROUTE_EARPIECE) {
|
||||
devices.add(AudioModeModule.DEVICE_EARPIECE);
|
||||
}
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_BLUETOOTH)
|
||||
== android.telecom.CallAudioState.ROUTE_BLUETOOTH) {
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_BLUETOOTH) == CallAudioState.ROUTE_BLUETOOTH) {
|
||||
devices.add(AudioModeModule.DEVICE_BLUETOOTH);
|
||||
}
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_SPEAKER)
|
||||
== android.telecom.CallAudioState.ROUTE_SPEAKER) {
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_SPEAKER) == CallAudioState.ROUTE_SPEAKER) {
|
||||
devices.add(AudioModeModule.DEVICE_SPEAKER);
|
||||
}
|
||||
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_WIRED_HEADSET)
|
||||
== android.telecom.CallAudioState.ROUTE_WIRED_HEADSET) {
|
||||
if ((supportedRouteMask & CallAudioState.ROUTE_WIRED_HEADSET) == CallAudioState.ROUTE_WIRED_HEADSET) {
|
||||
devices.add(AudioModeModule.DEVICE_HEADPHONES);
|
||||
}
|
||||
return devices;
|
||||
@@ -105,17 +108,18 @@ class AudioDeviceHandlerConnectionService implements
|
||||
*/
|
||||
private int supportedRouteMask = -1;
|
||||
|
||||
public AudioDeviceHandlerConnectionService() {
|
||||
public AudioDeviceHandlerConnectionService(AudioManager audioManager) {
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallAudioStateChange(final android.telecom.CallAudioState callAudioState) {
|
||||
public void onCallAudioStateChange(final CallAudioState state) {
|
||||
module.runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean audioRouteChanged
|
||||
= audioDeviceToRouteInt(module.getSelectedDevice()) != callAudioState.getRoute();
|
||||
int newSupportedRoutes = callAudioState.getSupportedRouteMask();
|
||||
= audioDeviceToRouteInt(module.getSelectedDevice()) != state.getRoute();
|
||||
int newSupportedRoutes = state.getSupportedRouteMask();
|
||||
boolean audioDevicesChanged = supportedRouteMask != newSupportedRoutes;
|
||||
if (audioDevicesChanged) {
|
||||
supportedRouteMask = newSupportedRoutes;
|
||||
@@ -133,13 +137,16 @@ class AudioDeviceHandlerConnectionService implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Context context, AudioModeModule audioModeModule) {
|
||||
public void start(AudioModeModule audioModeModule) {
|
||||
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
|
||||
|
||||
module = audioModeModule;
|
||||
|
||||
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
|
||||
if (rcs != null) {
|
||||
rcs.setCallAudioStateListener(this);
|
||||
} else {
|
||||
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +155,8 @@ class AudioDeviceHandlerConnectionService implements
|
||||
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
|
||||
if (rcs != null) {
|
||||
rcs.setCallAudioStateListener(null);
|
||||
} else {
|
||||
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +168,16 @@ class AudioDeviceHandlerConnectionService implements
|
||||
|
||||
@Override
|
||||
public boolean setMode(int mode) {
|
||||
if (mode != AudioModeModule.DEFAULT) {
|
||||
// This shouldn't be needed when using ConnectionService, but some devices have been
|
||||
// observed not doing it.
|
||||
try {
|
||||
audioManager.setMicrophoneMute(false);
|
||||
} catch (Throwable tr) {
|
||||
JitsiMeetLogger.w(tr, TAG + " Failed to unmute the microphone");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,8 @@ class AudioDeviceHandlerGeneric implements
|
||||
}
|
||||
};
|
||||
|
||||
public AudioDeviceHandlerGeneric() {
|
||||
public AudioDeviceHandlerGeneric(AudioManager audioManager) {
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,11 +179,10 @@ class AudioDeviceHandlerGeneric implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Context context, AudioModeModule audioModeModule) {
|
||||
public void start(AudioModeModule audioModeModule) {
|
||||
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
|
||||
|
||||
module = audioModeModule;
|
||||
audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
|
||||
|
||||
// Setup runtime device change detection.
|
||||
audioManager.registerAudioDeviceCallback(audioDeviceCallback, null);
|
||||
@@ -198,11 +198,11 @@ class AudioDeviceHandlerGeneric implements
|
||||
|
||||
@Override
|
||||
public void setAudioRoute(String device) {
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
|
||||
// Turn speaker on / off
|
||||
audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
|
||||
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -59,7 +59,8 @@ class AudioDeviceHandlerLegacy implements
|
||||
*/
|
||||
private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
|
||||
|
||||
public AudioDeviceHandlerLegacy() {
|
||||
public AudioDeviceHandlerLegacy(AudioManager audioManager) {
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,11 +156,11 @@ class AudioDeviceHandlerLegacy implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Context context, AudioModeModule audioModeModule) {
|
||||
public void start(AudioModeModule audioModeModule) {
|
||||
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
|
||||
|
||||
module = audioModeModule;
|
||||
audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
|
||||
Context context = module.getContext();
|
||||
|
||||
// Setup runtime device change detection.
|
||||
//
|
||||
@@ -196,11 +197,11 @@ class AudioDeviceHandlerLegacy implements
|
||||
|
||||
@Override
|
||||
public void setAudioRoute(String device) {
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
|
||||
// Turn speaker on / off
|
||||
audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
|
||||
|
||||
// Turn bluetooth on / off
|
||||
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
@@ -85,6 +86,12 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
return supportsConnectionService && useConnectionService_;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AudioManager} instance used to interact with the Android audio
|
||||
* subsystem.
|
||||
*/
|
||||
private AudioManager audioManager;
|
||||
|
||||
private AudioDeviceHandlerInterface audioDeviceHandler;
|
||||
|
||||
/**
|
||||
@@ -137,7 +144,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
public AudioModeModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
setAudioDeviceHandler();
|
||||
audioManager = (AudioManager)reactContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,20 +200,35 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the audio device handler module. This function is called *after* all Catalyst
|
||||
* modules have been created, and that's why we use it, because {@link AudioDeviceHandlerConnectionService}
|
||||
* needs access to another Catalyst module, so doing this in the constructor would be too early.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setAudioDeviceHandler();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setAudioDeviceHandler() {
|
||||
if (audioDeviceHandler != null) {
|
||||
audioDeviceHandler.stop();
|
||||
}
|
||||
|
||||
if (useConnectionService()) {
|
||||
audioDeviceHandler = new AudioDeviceHandlerConnectionService();
|
||||
audioDeviceHandler = new AudioDeviceHandlerConnectionService(audioManager);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
audioDeviceHandler = new AudioDeviceHandlerGeneric();
|
||||
audioDeviceHandler = new AudioDeviceHandlerGeneric(audioManager);
|
||||
} else {
|
||||
audioDeviceHandler = new AudioDeviceHandlerLegacy();
|
||||
audioDeviceHandler = new AudioDeviceHandlerLegacy(audioManager);
|
||||
}
|
||||
|
||||
audioDeviceHandler.start(getReactApplicationContext(), this);
|
||||
audioDeviceHandler.start(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,9 +306,14 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
* @param use Boolean indicator of where it should be used or not.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setUseConnectionService(boolean use) {
|
||||
useConnectionService_ = use;
|
||||
setAudioDeviceHandler();
|
||||
public void setUseConnectionService(final boolean use) {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
useConnectionService_ = use;
|
||||
setAudioDeviceHandler();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -400,16 +427,24 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed on the legacy handler...
|
||||
*
|
||||
* @return Context for the application.
|
||||
*/
|
||||
Context getContext() {
|
||||
return getReactApplicationContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for the modules implementing the actual audio device management.
|
||||
*/
|
||||
interface AudioDeviceHandlerInterface {
|
||||
/**
|
||||
* Start detecting audio device changes.
|
||||
* @param context Android {@link Context} where detection should take place.
|
||||
* @param audioModeModule Reference to the main {@link AudioModeModule}.
|
||||
*/
|
||||
void start(Context context, AudioModeModule audioModeModule);
|
||||
void start(AudioModeModule audioModeModule);
|
||||
|
||||
/**
|
||||
* Stop audio device detection.
|
||||
|
||||
@@ -123,14 +123,17 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
* {@link android.telecom.Connection#STATE_ACTIVE}.
|
||||
*
|
||||
* @param callUUID the call UUID which identifies the connection.
|
||||
* @return Whether the connection was set as active or not.
|
||||
*/
|
||||
static void setConnectionActive(String callUUID) {
|
||||
static boolean setConnectionActive(String callUUID) {
|
||||
ConnectionImpl connection = connections.get(callUUID);
|
||||
|
||||
if (connection != null) {
|
||||
connection.setActive();
|
||||
return true;
|
||||
} else {
|
||||
JitsiMeetLogger.e("%s setConnectionActive - no connection for UUID: %s", TAG, callUUID);
|
||||
JitsiMeetLogger.w("%s setConnectionActive - no connection for UUID: %s", TAG, callUUID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -349,7 +349,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
urlProps.putString("jwt", token);
|
||||
}
|
||||
|
||||
if (token == null && userInfo != null) {
|
||||
if (userInfo != null) {
|
||||
props.putBundle("userInfo", userInfo.asBundle());
|
||||
}
|
||||
|
||||
|
||||
@@ -89,9 +89,17 @@ class RNConnectionService extends ReactContextBaseJavaModule {
|
||||
ReactApplicationContext ctx = getReactApplicationContext();
|
||||
|
||||
Uri address = Uri.fromParts(PhoneAccount.SCHEME_SIP, handle, null);
|
||||
PhoneAccountHandle accountHandle
|
||||
= ConnectionService.registerPhoneAccount(
|
||||
getReactApplicationContext(), address, callUUID);
|
||||
PhoneAccountHandle accountHandle;
|
||||
|
||||
try {
|
||||
accountHandle
|
||||
= ConnectionService.registerPhoneAccount(getReactApplicationContext(), address, callUUID);
|
||||
} catch (Throwable tr) {
|
||||
JitsiMeetLogger.e(tr, TAG + " error in startCall");
|
||||
|
||||
promise.reject(tr);
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putParcelable(
|
||||
@@ -105,15 +113,23 @@ class RNConnectionService extends ReactContextBaseJavaModule {
|
||||
|
||||
ConnectionService.registerStartCallPromise(callUUID, promise);
|
||||
|
||||
try {
|
||||
TelecomManager tm
|
||||
= (TelecomManager) ctx.getSystemService(
|
||||
Context.TELECOM_SERVICE);
|
||||
TelecomManager tm = null;
|
||||
|
||||
try {
|
||||
tm = (TelecomManager) ctx.getSystemService(Context.TELECOM_SERVICE);
|
||||
tm.placeCall(address, extras);
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable tr) {
|
||||
JitsiMeetLogger.e(tr, TAG + " error in startCall");
|
||||
if (tm != null) {
|
||||
try {
|
||||
tm.unregisterPhoneAccount(accountHandle);
|
||||
} catch (Throwable tr1) {
|
||||
// UnsupportedOperationException: System does not support feature android.software.connectionservice
|
||||
// was observed here. Ignore.
|
||||
}
|
||||
}
|
||||
ConnectionService.unregisterStartCallPromise(callUUID);
|
||||
promise.reject(e);
|
||||
promise.reject(tr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,8 +167,11 @@ class RNConnectionService extends ReactContextBaseJavaModule {
|
||||
@ReactMethod
|
||||
public void reportConnectedOutgoingCall(String callUUID, Promise promise) {
|
||||
JitsiMeetLogger.d(TAG + " reportConnectedOutgoingCall " + callUUID);
|
||||
ConnectionService.setConnectionActive(callUUID);
|
||||
promise.resolve(null);
|
||||
if (ConnectionService.setConnectionActive(callUUID)) {
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
promise.reject("CONNECTION_NOT_FOUND_ERROR", "Connection wasn't found.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
* Copyright @ 2017-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.
|
||||
@@ -18,8 +17,11 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
@@ -27,14 +29,16 @@ import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
import com.facebook.react.devsupport.DevInternalSettings;
|
||||
import com.facebook.react.jscexecutor.JSCExecutorFactory;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
import com.oney.WebRTCModule.EglUtils;
|
||||
import com.oney.WebRTCModule.RTCVideoViewManager;
|
||||
import com.oney.WebRTCModule.WebRTCModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
import org.webrtc.DefaultVideoDecoderFactory;
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.SoftwareVideoDecoderFactory;
|
||||
import org.webrtc.SoftwareVideoEncoderFactory;
|
||||
import org.webrtc.VideoDecoderFactory;
|
||||
@@ -85,10 +89,22 @@ class ReactInstanceManagerHolder {
|
||||
|
||||
AudioDeviceModule adm = JavaAudioDeviceModule.builder(reactContext)
|
||||
.createAudioDeviceModule();
|
||||
VideoDecoderFactory videoDecoderFactory = new SoftwareVideoDecoderFactory();
|
||||
VideoEncoderFactory videoEncoderFactory = new SoftwareVideoEncoderFactory();
|
||||
|
||||
options.setAudioDeviceModule(adm);
|
||||
|
||||
VideoEncoderFactory videoEncoderFactory = new SoftwareVideoEncoderFactory();
|
||||
VideoDecoderFactory videoDecoderFactory = new SoftwareVideoDecoderFactory();
|
||||
|
||||
// Initialize EGL context required for HW acceleration. We are only going to use it for
|
||||
// decoding.
|
||||
// NOTE: We are explicitly skipping Samsung devices because we have observed a high crash
|
||||
// count on them.
|
||||
if (!Build.MANUFACTURER.toLowerCase().contains("samsung")) {
|
||||
EglBase.Context eglContext = EglUtils.getRootEglBaseContext();
|
||||
if (eglContext != null) {
|
||||
videoDecoderFactory = new DefaultVideoDecoderFactory(eglContext);
|
||||
}
|
||||
}
|
||||
|
||||
options.setVideoDecoderFactory(videoDecoderFactory);
|
||||
options.setVideoEncoderFactory(videoEncoderFactory);
|
||||
|
||||
@@ -219,9 +235,8 @@ class ReactInstanceManagerHolder {
|
||||
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
||||
}
|
||||
|
||||
// Keep on using JSC, the jury is out on Hermes.
|
||||
JSCExecutorFactory jsFactory
|
||||
= new JSCExecutorFactory("", "");
|
||||
// Use the Hermes JavaScript engine.
|
||||
HermesExecutorFactory jsFactory = new HermesExecutorFactory();
|
||||
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
|
||||
@@ -24,11 +24,24 @@ public class JitsiMeetLogger {
|
||||
}
|
||||
|
||||
public static void addHandler(JitsiMeetBaseLogHandler handler) {
|
||||
Timber.plant(handler);
|
||||
if (!Timber.forest().contains(handler)) {
|
||||
try {
|
||||
Timber.plant(handler);
|
||||
} catch (Throwable t) {
|
||||
Timber.w(t, "Couldn't add log handler");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeHandler(JitsiMeetBaseLogHandler handler) {
|
||||
Timber.uproot(handler);
|
||||
if (Timber.forest().contains(handler)) {
|
||||
try {
|
||||
Timber.uproot(handler);
|
||||
} catch (Throwable t) {
|
||||
Timber.w(t, "Couldn't remove log handler");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void v(String message, Object... args) {
|
||||
|
||||
409
conference.js
@@ -41,6 +41,7 @@ import {
|
||||
conferenceJoined,
|
||||
conferenceLeft,
|
||||
conferenceSubjectChanged,
|
||||
conferenceTimestampChanged,
|
||||
conferenceWillJoin,
|
||||
conferenceWillLeave,
|
||||
dataChannelOpened,
|
||||
@@ -93,10 +94,15 @@ import {
|
||||
participantRoleChanged,
|
||||
participantUpdated
|
||||
} from './react/features/base/participants';
|
||||
import { updateSettings } from './react/features/base/settings';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
updateSettings
|
||||
} from './react/features/base/settings';
|
||||
import {
|
||||
createLocalPresenterTrack,
|
||||
createLocalTracksF,
|
||||
destroyLocalTracks,
|
||||
isLocalVideoTrackMuted,
|
||||
isLocalTrackMuted,
|
||||
isUserInteractionRequiredForUnmute,
|
||||
replaceLocalTrack,
|
||||
@@ -113,7 +119,10 @@ import {
|
||||
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
|
||||
import { suspendDetected } from './react/features/power-monitor';
|
||||
import { setSharedVideoStatus } from './react/features/shared-video';
|
||||
import { createPresenterEffect } from './react/features/stream-effects/presenter';
|
||||
import { endpointMessageReceived } from './react/features/subtitles';
|
||||
import { createRnnoiseProcessorPromise } from './react/features/rnnoise';
|
||||
import { toggleScreenshotCaptureEffect } from './react/features/screenshot-capture';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
@@ -122,6 +131,15 @@ const eventEmitter = new EventEmitter();
|
||||
let room;
|
||||
let connection;
|
||||
|
||||
/**
|
||||
* This promise is used for chaining mutePresenterVideo calls in order to avoid calling GUM multiple times if it takes
|
||||
* a while to finish.
|
||||
*
|
||||
* @type {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
let _prevMutePresenterVideo = Promise.resolve();
|
||||
|
||||
/*
|
||||
* Logic to open a desktop picker put on the window global for
|
||||
* lib-jitsi-meet to detect and invoke
|
||||
@@ -417,7 +435,6 @@ export default {
|
||||
* the tracks won't exist).
|
||||
*/
|
||||
_localTracksInitialized: false,
|
||||
isModerator: false,
|
||||
isSharingScreen: false,
|
||||
|
||||
/**
|
||||
@@ -437,6 +454,12 @@ export default {
|
||||
*/
|
||||
localAudio: null,
|
||||
|
||||
/**
|
||||
* The local presenter video track (if any).
|
||||
* @type {JitsiLocalTrack|null}
|
||||
*/
|
||||
localPresenterVideo: null,
|
||||
|
||||
/**
|
||||
* The local video track (if any).
|
||||
* FIXME tracks from redux store should be the single source of truth, but
|
||||
@@ -468,14 +491,18 @@ export default {
|
||||
audioOnlyError,
|
||||
screenSharingError,
|
||||
videoOnlyError;
|
||||
const initialDevices = [];
|
||||
let requestedAudio = false;
|
||||
const initialDevices = [ 'audio' ];
|
||||
const requestedAudio = true;
|
||||
let requestedVideo = false;
|
||||
|
||||
if (!options.startWithAudioMuted) {
|
||||
initialDevices.push('audio');
|
||||
requestedAudio = true;
|
||||
// Always get a handle on the audio input device so that we have statistics even if the user joins the
|
||||
// conference muted. Previous implementation would only acquire the handle when the user first unmuted,
|
||||
// which would results in statistics ( such as "No audio input" or "Are you trying to speak?") being available
|
||||
// only after that point.
|
||||
if (options.startWithAudioMuted) {
|
||||
this.muteAudio(true, true);
|
||||
}
|
||||
|
||||
if (!options.startWithVideoMuted
|
||||
&& !options.startAudioOnly
|
||||
&& !options.startScreenSharing) {
|
||||
@@ -722,9 +749,8 @@ export default {
|
||||
isLocalVideoMuted() {
|
||||
// If the tracks are not ready, read from base/media state
|
||||
return this._localTracksInitialized
|
||||
? isLocalTrackMuted(
|
||||
APP.store.getState()['features/base/tracks'],
|
||||
MEDIA_TYPE.VIDEO)
|
||||
? isLocalVideoTrackMuted(
|
||||
APP.store.getState()['features/base/tracks'])
|
||||
: isVideoMutedByUser(APP.store);
|
||||
},
|
||||
|
||||
@@ -798,6 +824,35 @@ export default {
|
||||
this.muteAudio(!this.isLocalAudioMuted(), showUI);
|
||||
},
|
||||
|
||||
/**
|
||||
* Simulates toolbar button click for presenter video mute. Used by
|
||||
* shortcuts and API.
|
||||
* @param mute true for mute and false for unmute.
|
||||
* @param {boolean} [showUI] when set to false will not display any error
|
||||
* dialogs in case of media permissions error.
|
||||
*/
|
||||
async mutePresenter(mute, showUI = true) {
|
||||
const maybeShowErrorDialog = error => {
|
||||
showUI && APP.store.dispatch(notifyCameraError(error));
|
||||
};
|
||||
|
||||
if (mute) {
|
||||
try {
|
||||
await this.localVideo.setEffect(undefined);
|
||||
} catch (err) {
|
||||
logger.error('Failed to remove the presenter effect', err);
|
||||
maybeShowErrorDialog(err);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await this.localVideo.setEffect(await this._createPresenterStreamEffect());
|
||||
} catch (err) {
|
||||
logger.error('Failed to apply the presenter effect', err);
|
||||
maybeShowErrorDialog(err);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Simulates toolbar button click for video mute. Used by shortcuts and API.
|
||||
* @param mute true for mute and false for unmute.
|
||||
@@ -812,6 +867,13 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isSharingScreen) {
|
||||
// Chain _mutePresenterVideo calls
|
||||
_prevMutePresenterVideo = _prevMutePresenterVideo.then(() => this._mutePresenterVideo(mute));
|
||||
|
||||
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
|
||||
@@ -863,14 +925,6 @@ export default {
|
||||
this.muteVideo(!this.isLocalVideoMuted(), showUI);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve list of conference participants (without local user).
|
||||
* @returns {JitsiParticipant[]}
|
||||
*/
|
||||
listMembers() {
|
||||
return room.getParticipants();
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve list of ids of conference participants (without local user).
|
||||
* @returns {string[]}
|
||||
@@ -890,6 +944,16 @@ export default {
|
||||
return user && user.isModerator();
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve list of conference participants (without local user).
|
||||
* @returns {JitsiParticipant[]}
|
||||
*
|
||||
* NOTE: Used by jitsi-meet-torture!
|
||||
*/
|
||||
listMembers() {
|
||||
return room.getParticipants();
|
||||
},
|
||||
|
||||
get membersCount() {
|
||||
return room.getParticipants().length + 1;
|
||||
},
|
||||
@@ -1205,17 +1269,22 @@ export default {
|
||||
|
||||
_getConferenceOptions() {
|
||||
const options = config;
|
||||
const { email, name: nick } = getLocalParticipant(APP.store.getState());
|
||||
|
||||
const nick = APP.store.getState()['features/base/settings'].displayName;
|
||||
const { locationURL } = APP.store.getState()['features/base/connection'];
|
||||
|
||||
if (nick) {
|
||||
options.displayName = nick;
|
||||
if (options.enableDisplayNameInStats && nick) {
|
||||
options.statisticsDisplayName = nick;
|
||||
}
|
||||
|
||||
if (options.enableEmailInStats && email) {
|
||||
options.statisticsId = email;
|
||||
}
|
||||
|
||||
options.applicationName = interfaceConfig.APP_NAME;
|
||||
options.getWiFiStatsMethod = this._getWiFiStatsMethod;
|
||||
options.confID = `${locationURL.host}${locationURL.pathname}`;
|
||||
options.createVADProcessor = createRnnoiseProcessorPromise;
|
||||
|
||||
return options;
|
||||
},
|
||||
@@ -1347,7 +1416,7 @@ export default {
|
||||
* in case it fails.
|
||||
* @private
|
||||
*/
|
||||
_turnScreenSharingOff(didHaveVideo, wasVideoMuted) {
|
||||
_turnScreenSharingOff(didHaveVideo) {
|
||||
this._untoggleScreenSharing = null;
|
||||
this.videoSwitchInProgress = true;
|
||||
const { receiver } = APP.remoteControl;
|
||||
@@ -1357,21 +1426,31 @@ export default {
|
||||
}
|
||||
|
||||
this._stopProxyConnection();
|
||||
if (config.enableScreenshotCapture) {
|
||||
APP.store.dispatch(toggleScreenshotCaptureEffect(false));
|
||||
}
|
||||
|
||||
let promise = null;
|
||||
// It can happen that presenter GUM is in progress while screensharing is being turned off. Here it needs to
|
||||
// wait for that GUM to be resolved in order to prevent leaking the presenter track(this.localPresenterVideo
|
||||
// will be null when SS is being turned off, but it will initialize once GUM resolves).
|
||||
let promise = _prevMutePresenterVideo = _prevMutePresenterVideo.then(() => {
|
||||
// mute the presenter track if it exists.
|
||||
if (this.localPresenterVideo) {
|
||||
APP.store.dispatch(setVideoMuted(true, MEDIA_TYPE.PRESENTER));
|
||||
|
||||
return this.localPresenterVideo.dispose().then(() => {
|
||||
APP.store.dispatch(trackRemoved(this.localPresenterVideo));
|
||||
this.localPresenterVideo = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (didHaveVideo) {
|
||||
promise = createLocalTracksF({ devices: [ 'video' ] })
|
||||
promise = promise.then(() => createLocalTracksF({ devices: [ 'video' ] }))
|
||||
.then(([ stream ]) => this.useVideoStream(stream))
|
||||
.then(() => {
|
||||
sendAnalytics(createScreenSharingEvent('stopped'));
|
||||
logger.log('Screen sharing stopped, switching to video.');
|
||||
|
||||
if (!this.localVideo && wasVideoMuted) {
|
||||
return Promise.reject('No local video to be muted!');
|
||||
} else if (wasVideoMuted && this.localVideo) {
|
||||
return this.localVideo.mute();
|
||||
}
|
||||
logger.log('Screen sharing stopped.');
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('failed to switch back to local video', error);
|
||||
@@ -1383,7 +1462,7 @@ export default {
|
||||
);
|
||||
});
|
||||
} else {
|
||||
promise = this.useVideoStream(null);
|
||||
promise = promise.then(() => this.useVideoStream(null));
|
||||
}
|
||||
|
||||
return promise.then(
|
||||
@@ -1411,7 +1490,7 @@ export default {
|
||||
* 'window', etc.).
|
||||
* @return {Promise.<T>}
|
||||
*/
|
||||
toggleScreenSharing(toggle = !this._untoggleScreenSharing, options = {}) {
|
||||
async toggleScreenSharing(toggle = !this._untoggleScreenSharing, options = {}) {
|
||||
if (this.videoSwitchInProgress) {
|
||||
return Promise.reject('Switch in progress.');
|
||||
}
|
||||
@@ -1425,7 +1504,15 @@ export default {
|
||||
}
|
||||
|
||||
if (toggle) {
|
||||
return this._switchToScreenSharing(options);
|
||||
try {
|
||||
await this._switchToScreenSharing(options);
|
||||
|
||||
return;
|
||||
} catch (err) {
|
||||
logger.error('Failed to switch to screensharing', err);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return this._untoggleScreenSharing
|
||||
@@ -1450,8 +1537,7 @@ export default {
|
||||
_createDesktopTrack(options = {}) {
|
||||
let externalInstallation = false;
|
||||
let DSExternalInstallationInProgress = false;
|
||||
const didHaveVideo = Boolean(this.localVideo);
|
||||
const wasVideoMuted = this.isLocalVideoMuted();
|
||||
const didHaveVideo = !this.isLocalVideoMuted();
|
||||
|
||||
const getDesktopStreamPromise = options.desktopStream
|
||||
? Promise.resolve([ options.desktopStream ])
|
||||
@@ -1502,8 +1588,7 @@ export default {
|
||||
// Stores the "untoggle" handler which remembers whether was
|
||||
// there any video before and whether was it muted.
|
||||
this._untoggleScreenSharing
|
||||
= this._turnScreenSharingOff
|
||||
.bind(this, didHaveVideo, wasVideoMuted);
|
||||
= this._turnScreenSharingOff.bind(this, didHaveVideo);
|
||||
desktopStream.on(
|
||||
JitsiTrackEvents.LOCAL_TRACK_STOPPED,
|
||||
() => {
|
||||
@@ -1528,6 +1613,126 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new instance of presenter effect. A new video track is created
|
||||
* using the new set of constraints that are calculated based on
|
||||
* the height of the desktop that is being currently shared.
|
||||
*
|
||||
* @param {number} height - The height of the desktop stream that is being
|
||||
* currently shared.
|
||||
* @param {string} cameraDeviceId - The device id of the camera to be used.
|
||||
* @return {Promise<JitsiStreamPresenterEffect>} - A promise resolved with
|
||||
* {@link JitsiStreamPresenterEffect} if it succeeds.
|
||||
*/
|
||||
async _createPresenterStreamEffect(height = null, cameraDeviceId = null) {
|
||||
if (!this.localPresenterVideo) {
|
||||
try {
|
||||
this.localPresenterVideo = await createLocalPresenterTrack({ cameraDeviceId }, height);
|
||||
} catch (err) {
|
||||
logger.error('Failed to create a camera track for presenter', err);
|
||||
|
||||
return;
|
||||
}
|
||||
APP.store.dispatch(trackAdded(this.localPresenterVideo));
|
||||
}
|
||||
try {
|
||||
const effect = await createPresenterEffect(this.localPresenterVideo.stream);
|
||||
|
||||
return effect;
|
||||
} catch (err) {
|
||||
logger.error('Failed to create the presenter effect', err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Tries to turn the presenter video track on or off. If a presenter track
|
||||
* doesn't exist, a new video track is created.
|
||||
*
|
||||
* @param mute - true for mute and false for unmute.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
async _mutePresenterVideo(mute) {
|
||||
const maybeShowErrorDialog = error => {
|
||||
APP.store.dispatch(notifyCameraError(error));
|
||||
};
|
||||
|
||||
// Check for NO-OP
|
||||
if (mute && (!this.localPresenterVideo || this.localPresenterVideo.isMuted())) {
|
||||
|
||||
return;
|
||||
} else if (!mute && this.localPresenterVideo && !this.localPresenterVideo.isMuted()) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new presenter track and apply the presenter effect.
|
||||
if (!this.localPresenterVideo && !mute) {
|
||||
let { aspectRatio, height } = this.localVideo.track.getSettings();
|
||||
const { width } = this.localVideo.track.getSettings();
|
||||
let desktopResizeConstraints = {};
|
||||
let resizeDesktopStream = false;
|
||||
const DESKTOP_STREAM_CAP = 720;
|
||||
|
||||
// Determine the constraints if the desktop track needs to be resized.
|
||||
// Resizing is needed when the resolution cannot be determined or when
|
||||
// the window is bigger than 720p.
|
||||
if (height && width) {
|
||||
aspectRatio = aspectRatio ?? (width / height).toPrecision(4);
|
||||
const advancedConstraints = [ { aspectRatio } ];
|
||||
const isPortrait = height >= width;
|
||||
|
||||
// Determine which dimension needs resizing and resize only that side
|
||||
// keeping the aspect ratio same as before.
|
||||
if (isPortrait && width > DESKTOP_STREAM_CAP) {
|
||||
resizeDesktopStream = true;
|
||||
advancedConstraints.push({ width: DESKTOP_STREAM_CAP });
|
||||
} else if (!isPortrait && height > DESKTOP_STREAM_CAP) {
|
||||
resizeDesktopStream = true;
|
||||
advancedConstraints.push({ height: DESKTOP_STREAM_CAP });
|
||||
}
|
||||
desktopResizeConstraints.advanced = advancedConstraints;
|
||||
} else {
|
||||
resizeDesktopStream = true;
|
||||
desktopResizeConstraints = {
|
||||
width: 1280,
|
||||
height: 720
|
||||
};
|
||||
}
|
||||
if (resizeDesktopStream) {
|
||||
try {
|
||||
await this.localVideo.track.applyConstraints(desktopResizeConstraints);
|
||||
} catch (err) {
|
||||
logger.error('Failed to apply constraints on the desktop stream for presenter mode', err);
|
||||
|
||||
return;
|
||||
}
|
||||
height = this.localVideo.track.getSettings().height ?? DESKTOP_STREAM_CAP;
|
||||
}
|
||||
const defaultCamera = getUserSelectedCameraDeviceId(APP.store.getState());
|
||||
let effect;
|
||||
|
||||
try {
|
||||
effect = await this._createPresenterStreamEffect(height,
|
||||
defaultCamera);
|
||||
} catch (err) {
|
||||
logger.error('Failed to unmute Presenter Video');
|
||||
maybeShowErrorDialog(err);
|
||||
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.localVideo.setEffect(effect);
|
||||
APP.store.dispatch(setVideoMuted(mute, MEDIA_TYPE.PRESENTER));
|
||||
this.setVideoMuteStatus(mute);
|
||||
} catch (err) {
|
||||
logger.error('Failed to apply the Presenter effect', err);
|
||||
}
|
||||
} else {
|
||||
APP.store.dispatch(setVideoMuted(mute, MEDIA_TYPE.PRESENTER));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Tries to switch to the screensharing mode by disposing camera stream and
|
||||
* replacing it with a desktop one.
|
||||
@@ -1552,6 +1757,9 @@ export default {
|
||||
.then(stream => this.useVideoStream(stream))
|
||||
.then(() => {
|
||||
this.videoSwitchInProgress = false;
|
||||
if (config.enableScreenshotCapture) {
|
||||
APP.store.dispatch(toggleScreenshotCaptureEffect(true));
|
||||
}
|
||||
sendAnalytics(createScreenSharingEvent('started'));
|
||||
logger.log('Screen sharing started');
|
||||
})
|
||||
@@ -1665,7 +1873,10 @@ export default {
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.CONFERENCE_LEFT,
|
||||
(...args) => APP.store.dispatch(conferenceLeft(room, ...args)));
|
||||
(...args) => {
|
||||
APP.store.dispatch(conferenceTimestampChanged(0));
|
||||
APP.store.dispatch(conferenceLeft(room, ...args));
|
||||
});
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.AUTH_STATUS_CHANGED,
|
||||
@@ -1684,9 +1895,6 @@ export default {
|
||||
|
||||
logger.log(`USER ${id} connnected:`, user);
|
||||
APP.UI.addUser(user);
|
||||
|
||||
// check the roles for the new user and reflect them
|
||||
APP.UI.updateUserRole(user);
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
|
||||
@@ -1717,19 +1925,8 @@ export default {
|
||||
logger.info(`My role changed, new role: ${role}`);
|
||||
|
||||
APP.store.dispatch(localParticipantRoleChanged(role));
|
||||
|
||||
if (this.isModerator !== room.isModerator()) {
|
||||
this.isModerator = room.isModerator();
|
||||
APP.UI.updateLocalRole(room.isModerator());
|
||||
}
|
||||
} else {
|
||||
APP.store.dispatch(participantRoleChanged(id, role));
|
||||
|
||||
const user = room.getParticipantById(id);
|
||||
|
||||
if (user) {
|
||||
APP.UI.updateUserRole(user);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1795,6 +1992,10 @@ export default {
|
||||
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
||||
id => APP.store.dispatch(dominantSpeakerChanged(id, room)));
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.CONFERENCE_CREATED_TIMESTAMP,
|
||||
conferenceTimestamp => APP.store.dispatch(conferenceTimestampChanged(conferenceTimestamp)));
|
||||
|
||||
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||
APP.store.dispatch(localParticipantConnectionStatusChanged(
|
||||
JitsiParticipantConnectionStatus.INTERRUPTED));
|
||||
@@ -1988,36 +2189,65 @@ export default {
|
||||
const videoWasMuted = this.isLocalVideoMuted();
|
||||
|
||||
sendAnalytics(createDeviceChangedEvent('video', 'input'));
|
||||
createLocalTracksF({
|
||||
devices: [ 'video' ],
|
||||
cameraDeviceId,
|
||||
micDeviceId: null
|
||||
})
|
||||
.then(([ stream ]) => {
|
||||
// if we are in audio only mode or video was muted before
|
||||
// changing device, then mute
|
||||
if (this.isAudioOnly() || videoWasMuted) {
|
||||
return stream.mute()
|
||||
.then(() => stream);
|
||||
}
|
||||
|
||||
return stream;
|
||||
})
|
||||
.then(stream => {
|
||||
// if we are screen sharing we do not want to stop it
|
||||
if (this.isSharingScreen) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
// If both screenshare and video are in progress, restart the
|
||||
// presenter mode with the new camera device.
|
||||
if (this.isSharingScreen && !videoWasMuted) {
|
||||
const { height } = this.localVideo.track.getSettings();
|
||||
|
||||
return this.useVideoStream(stream);
|
||||
})
|
||||
.then(() => {
|
||||
// dispose the existing presenter track and create a new
|
||||
// camera track.
|
||||
// FIXME JitsiLocalTrack.dispose is async and should be waited for
|
||||
this.localPresenterVideo && this.localPresenterVideo.dispose();
|
||||
this.localPresenterVideo = null;
|
||||
|
||||
return this._createPresenterStreamEffect(height, cameraDeviceId)
|
||||
.then(effect => this.localVideo.setEffect(effect))
|
||||
.then(() => {
|
||||
this.setVideoMuteStatus(false);
|
||||
logger.log('switched local video device');
|
||||
this._updateVideoDeviceId();
|
||||
})
|
||||
.catch(err => APP.store.dispatch(notifyCameraError(err)));
|
||||
|
||||
// If screenshare is in progress but video is muted, update the default device
|
||||
// id for video, dispose the existing presenter track and create a new effect
|
||||
// that can be applied on un-mute.
|
||||
} else if (this.isSharingScreen && videoWasMuted) {
|
||||
logger.log('switched local video device');
|
||||
const { height } = this.localVideo.track.getSettings();
|
||||
|
||||
this._updateVideoDeviceId();
|
||||
})
|
||||
.catch(err => {
|
||||
APP.store.dispatch(notifyCameraError(err));
|
||||
});
|
||||
|
||||
// FIXME JitsiLocalTrack.dispose is async and should be waited for
|
||||
this.localPresenterVideo && this.localPresenterVideo.dispose();
|
||||
this.localPresenterVideo = null;
|
||||
this._createPresenterStreamEffect(height, cameraDeviceId);
|
||||
|
||||
// if there is only video, switch to the new camera stream.
|
||||
} else {
|
||||
createLocalTracksF({
|
||||
devices: [ 'video' ],
|
||||
cameraDeviceId,
|
||||
micDeviceId: null
|
||||
})
|
||||
.then(([ stream ]) => {
|
||||
// if we are in audio only mode or video was muted before
|
||||
// changing device, then mute
|
||||
if (this.isAudioOnly() || videoWasMuted) {
|
||||
return stream.mute()
|
||||
.then(() => stream);
|
||||
}
|
||||
|
||||
return stream;
|
||||
})
|
||||
.then(stream => this.useVideoStream(stream))
|
||||
.then(() => {
|
||||
logger.log('switched local video device');
|
||||
this._updateVideoDeviceId();
|
||||
})
|
||||
.catch(err => APP.store.dispatch(notifyCameraError(err)));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2044,7 +2274,7 @@ export default {
|
||||
})
|
||||
.then(stream => this.useAudioStream(stream))
|
||||
.then(() => {
|
||||
logger.log('switched local audio device');
|
||||
logger.log(`switched local audio device: ${this.localAudio?.getDeviceId()}`);
|
||||
|
||||
this._updateAudioDeviceId();
|
||||
})
|
||||
@@ -2247,6 +2477,13 @@ export default {
|
||||
cameraDeviceId: this.localVideo.getDeviceId()
|
||||
}));
|
||||
}
|
||||
|
||||
// If screenshare is in progress, get the device id from the presenter track.
|
||||
if (this.localPresenterVideo) {
|
||||
APP.store.dispatch(updateSettings({
|
||||
cameraDeviceId: this.localPresenterVideo.getDeviceId()
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -2414,12 +2651,6 @@ export default {
|
||||
// audio devices detected or if the local audio stream already exists.
|
||||
const available = audioDeviceCount > 0 || Boolean(this.localAudio);
|
||||
|
||||
logger.debug(
|
||||
`Microphone button enabled: ${available}`,
|
||||
`local audio: ${this.localAudio}`,
|
||||
`audio devices: ${audioMediaDevices}`,
|
||||
`device count: ${audioDeviceCount}`);
|
||||
|
||||
APP.store.dispatch(setAudioAvailable(available));
|
||||
APP.API.notifyAudioAvailabilityChanged(available);
|
||||
},
|
||||
@@ -2440,12 +2671,6 @@ export default {
|
||||
// config).
|
||||
const available = videoDeviceCount > 0 || Boolean(this.localVideo);
|
||||
|
||||
logger.debug(
|
||||
`Camera button enabled: ${available}`,
|
||||
`local video: ${this.localVideo}`,
|
||||
`video devices: ${videoMediaDevices}`,
|
||||
`device count: ${videoDeviceCount}`);
|
||||
|
||||
APP.store.dispatch(setVideoAvailable(available));
|
||||
APP.API.notifyVideoAvailabilityChanged(available);
|
||||
},
|
||||
|
||||
40
config.js
@@ -30,6 +30,9 @@ var config = {
|
||||
// BOSH URL. FIXME: use XEP-0156 to discover it.
|
||||
bosh: '//jitsi-meet.example.com/http-bind',
|
||||
|
||||
// Websocket URL
|
||||
// websocket: 'wss://jitsi-meet.example.com/xmpp-websocket',
|
||||
|
||||
// The name of client node advertised in XEP-0115 'c' stanza
|
||||
clientNode: 'http://jitsi.org/jitsimeet',
|
||||
|
||||
@@ -72,6 +75,18 @@ var config = {
|
||||
|
||||
// Disable measuring of audio levels.
|
||||
// disableAudioLevels: false,
|
||||
// audioLevelsInterval: 200,
|
||||
|
||||
// Enabling this will run the lib-jitsi-meet no audio detection module which
|
||||
// will notify the user if the current selected microphone has no audio
|
||||
// input and will suggest another valid device if one is present.
|
||||
enableNoAudioDetection: true,
|
||||
|
||||
// Enabling this will run the lib-jitsi-meet noise detection module which will
|
||||
// notify the user if there is noise, other than voice, coming from the current
|
||||
// selected microphone. The purpose it to let the user know that the input could
|
||||
// be potentially unpleasant for other meeting participants.
|
||||
enableNoisyMicDetection: true,
|
||||
|
||||
// Start the conference in audio only mode (no video is being received nor
|
||||
// sent).
|
||||
@@ -287,18 +302,19 @@ var config = {
|
||||
// estimation tests.
|
||||
// gatherStats: false,
|
||||
|
||||
// The interval at which PeerConnection.getStats() is called. Defaults to 10000
|
||||
// pcStatsInterval: 10000,
|
||||
|
||||
// To enable sending statistics to callstats.io you must provide the
|
||||
// Application ID and Secret.
|
||||
// callStatsID: '',
|
||||
// callStatsSecret: '',
|
||||
|
||||
// enables callstatsUsername to be reported as statsId and used
|
||||
// by callstats as repoted remote id
|
||||
// enableStatsID: false
|
||||
|
||||
// enables sending participants display name to callstats
|
||||
// enableDisplayNameInStats: false
|
||||
|
||||
// enables sending participants email if available to callstats and other analytics
|
||||
// enableEmailInStats: false
|
||||
|
||||
// Privacy
|
||||
//
|
||||
@@ -326,6 +342,8 @@ var config = {
|
||||
|
||||
// The STUN servers that will be used in the peer to peer connections
|
||||
stunServers: [
|
||||
|
||||
// { urls: 'stun:jitsi-meet.example.com:443' },
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' },
|
||||
{ urls: 'stun:stun2.l.google.com:19302' }
|
||||
@@ -374,6 +392,20 @@ var config = {
|
||||
// userRegion: "asia"
|
||||
}
|
||||
|
||||
// Information for the chrome extension banner
|
||||
// chromeExtensionBanner: {
|
||||
// // The chrome extension to be installed address
|
||||
// url: 'https://chrome.google.com/webstore/detail/jitsi-meetings/kglhbbefdnlheedjiejgomgmfplipfeb',
|
||||
|
||||
// // Extensions info which allows checking if they are installed or not
|
||||
// chromeExtensionsInfo: [
|
||||
// {
|
||||
// id: 'kglhbbefdnlheedjiejgomgmfplipfeb',
|
||||
// path: 'jitsi-logo-48x48.png'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
|
||||
// Local Recording
|
||||
//
|
||||
|
||||
|
||||
@@ -15,6 +15,13 @@ import {
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* The feature announced so we can distinguish jibri participants.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri';
|
||||
|
||||
/**
|
||||
* Checks if we have data to use attach instead of connect. If we have the data
|
||||
* executes attach otherwise check if we have to wait for the data. If we have
|
||||
@@ -75,7 +82,15 @@ function connect(id, password, roomName) {
|
||||
const connectionConfig = Object.assign({}, config);
|
||||
const { issuer, jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
connectionConfig.bosh += `?room=${roomName}`;
|
||||
// Use Websocket URL for the web app if configured. Note that there is no 'isWeb' check, because there's assumption
|
||||
// that this code executes only on web browsers/electron. This needs to be changed when mobile and web are unified.
|
||||
let serviceUrl = connectionConfig.websocket || connectionConfig.bosh;
|
||||
|
||||
serviceUrl += `?room=${roomName}`;
|
||||
|
||||
// FIXME Remove deprecated 'bosh' option assignment at some point(LJM will be accepting only 'serviceUrl' option
|
||||
// in future). It's included for the time being for Jitsi Meet and lib-jitsi-meet versions interoperability.
|
||||
connectionConfig.serviceUrl = connectionConfig.bosh = serviceUrl;
|
||||
|
||||
const connection
|
||||
= new JitsiMeetJS.JitsiConnection(
|
||||
@@ -83,6 +98,10 @@ function connect(id, password, roomName) {
|
||||
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
|
||||
connectionConfig);
|
||||
|
||||
if (config.iAmRecorder) {
|
||||
connection.addFeature(DISCO_JIBRI_FEATURE);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
|
||||
@@ -17,10 +17,11 @@ import parseURLParams from '../react/features/base/config/parseURLParams';
|
||||
|
||||
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.externalConnectUrl;
|
||||
|| config.websocket ? undefined : config.externalConnectUrl;
|
||||
const isRecorder
|
||||
= parseURLParams(window.location, true, 'hash')['config.iAmRecorder'];
|
||||
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
.avatar {
|
||||
align-items: center;
|
||||
background-color: #AAA;
|
||||
display: flex;
|
||||
border-radius: 50%;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-weight: 100;
|
||||
justify-content: center;
|
||||
object-fit: cover;
|
||||
|
||||
&.avatar-small {
|
||||
height: 28px !important;
|
||||
width: 28px !important;
|
||||
}
|
||||
|
||||
&.avatar-xsmall {
|
||||
height: 16px !important;
|
||||
width: 16px !important;
|
||||
}
|
||||
|
||||
.jitsi-icon {
|
||||
transform: translateY(50%);
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-foreign {
|
||||
@@ -28,4 +39,28 @@
|
||||
|
||||
.defaultAvatar {
|
||||
opacity: 0.6
|
||||
}
|
||||
|
||||
.avatar-badge {
|
||||
position: relative;
|
||||
|
||||
&-available::after {
|
||||
@include avatarBadge;
|
||||
background-color: $presence-available;
|
||||
}
|
||||
|
||||
&-away::after {
|
||||
@include avatarBadge;
|
||||
background-color: $presence-away;
|
||||
}
|
||||
|
||||
&-busy::after {
|
||||
@include avatarBadge;
|
||||
background-color: $presence-busy;
|
||||
}
|
||||
|
||||
&-idle::after {
|
||||
@include avatarBadge;
|
||||
background-color: $presence-idle;
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,11 @@ body {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.jitsi-icon.gray svg {
|
||||
fill: #5E6D7A;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* AtlasKitThemeProvider sets a background color on an app-wrapping div, thereby
|
||||
* preventing transparency in filmstrip-only mode. The selector chosen to
|
||||
@@ -161,6 +166,7 @@ form {
|
||||
::-webkit-scrollbar {
|
||||
background: transparent;
|
||||
width: 7px;
|
||||
height: $scrollHeight;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button {
|
||||
|
||||
@@ -205,6 +205,10 @@
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
.usermessage {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-radius: 0px;
|
||||
|
||||
@@ -226,6 +230,8 @@
|
||||
|
||||
.messagecontent {
|
||||
margin: 5px 10px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
93
css/_chrome-extension-banner.scss
Normal file
@@ -0,0 +1,93 @@
|
||||
.chrome-extension-banner {
|
||||
position: fixed;
|
||||
width: 406px;
|
||||
height: $chromeExtensionBannerHeight;
|
||||
background: #FFF;
|
||||
box-shadow: 0px 2px 48px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 4px;
|
||||
z-index: 1000;
|
||||
float: right;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px 20px;
|
||||
top: $chromeExtensionBannerTop;
|
||||
right: $chromeExtensionBannerRight;
|
||||
&__pos_in_meeting {
|
||||
top: $chromeExtensionBannerTopInMeeting;
|
||||
right: $chromeExtensionBannerRightInMeeeting;
|
||||
}
|
||||
|
||||
&__container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__button-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__checkbox-container {
|
||||
display: $chromeExtensionBannerDontShowAgainDisplay;
|
||||
margin-left: 45px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&__checkbox-label {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
letter-spacing: -0.006em;
|
||||
color: #1C2025;
|
||||
}
|
||||
|
||||
&__icon-container {
|
||||
display: flex;
|
||||
background: url('../images/chromeLogo.svg');
|
||||
background-repeat: no-repeat;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
}
|
||||
|
||||
&__text-container {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
letter-spacing: -0.006em;
|
||||
color: #151531;
|
||||
width: 329px;
|
||||
}
|
||||
|
||||
&__close-container {
|
||||
display: flex;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
&__gray-close-icon {
|
||||
fill: #5E6D7A;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__button-open-url {
|
||||
background: #0A57EB;
|
||||
border-radius: 24px;
|
||||
margin-left: 45px;
|
||||
width: 236px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__button-text {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
letter-spacing: -0.006em;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
@@ -192,4 +192,17 @@
|
||||
*/
|
||||
@mixin transparentBg($color, $alpha) {
|
||||
background-color: rgba(red($color), green($color), blue($color), $alpha);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avatar status badge mixin
|
||||
*/
|
||||
@mixin avatarBadge {
|
||||
border-radius: 50%;
|
||||
content: '';
|
||||
display: block;
|
||||
height: 35%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
@@ -23,4 +23,10 @@
|
||||
&-text {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&-conference-timer {
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,13 +31,6 @@
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows as a list item
|
||||
**/
|
||||
.show-list-item {
|
||||
display: list-item !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a flex element.
|
||||
*/
|
||||
|
||||
@@ -29,11 +29,14 @@ $defaultSideBarFontColor: #44A5FF;
|
||||
$defaultSemiDarkColor: #ACACAC;
|
||||
$defaultDarkColor: #2b3d5c;
|
||||
$defaultWarningColor: rgb(215, 121, 118);
|
||||
$presence-available: rgb(110, 176, 5);
|
||||
$presence-away: rgb(250, 201, 20);
|
||||
$presence-busy: rgb(233, 0, 27);
|
||||
$presence-idle: rgb(172, 172, 172);
|
||||
|
||||
/**
|
||||
* Toolbar
|
||||
*/
|
||||
$defaultToolbarSize: 50px;
|
||||
$newToolbarBackgroundColor: rgba(22, 38, 55, 0.8);
|
||||
$newToolbarButtonHoverColor: rgba(255, 255, 255, 0.15);
|
||||
$newToolbarButtonToggleColor: rgba(255, 255, 255, 0.2);
|
||||
@@ -102,6 +105,7 @@ $defaultWatermarkLink: '../images/watermark.png';
|
||||
$popoverMenuPadding: 13px;
|
||||
$happySoftwareBackground: transparent;
|
||||
$desktopAppDragBarHeight: 25px;
|
||||
$scrollHeight: 7px;
|
||||
|
||||
/**
|
||||
* Z-indexes. TODO: Replace this by a function.
|
||||
@@ -256,3 +260,14 @@ $deepLinkingMobileButtonFontWeight: bold;
|
||||
$deepLinkingMobileButtonFontSize: inherit;
|
||||
|
||||
$primaryDeepLinkingMobileButtonBorderRadius: inherit;
|
||||
|
||||
/**
|
||||
* Chrome extension banner variables.
|
||||
*/
|
||||
$chromeExtensionBannerDontShowAgainDisplay: flex;
|
||||
$chromeExtensionBannerHeight: 128px;
|
||||
$chromeExtensionBannerTop: 80px;
|
||||
$chromeExtensionBannerRight: 16px;
|
||||
$chromeExtensionBannerTopInMeeting: 10px;
|
||||
$chromeExtensionBannerRightInMeeeting: 10px;
|
||||
|
||||
|
||||
@@ -173,18 +173,14 @@
|
||||
&__hoverOverlay {
|
||||
background: rgba(0,0,0,.6);
|
||||
border-radius: $borderRadius;
|
||||
position: relative;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
visibility: hidden;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
&.audio-only {
|
||||
.videoThumbnailProblemFilter {
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#localVideoWrapper {
|
||||
@@ -489,6 +485,8 @@
|
||||
height: 300px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#mixedstream {
|
||||
@@ -524,13 +522,16 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 50%;
|
||||
overflow: hidden;
|
||||
width: auto;
|
||||
overflow: hidden;
|
||||
|
||||
.userAvatar {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,6 +554,9 @@
|
||||
}
|
||||
|
||||
.sharedVideoAvatar {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
@@ -563,21 +567,6 @@
|
||||
filter: grayscale(.5) opacity(0.8);
|
||||
}
|
||||
|
||||
.remoteVideoProblemFilter {
|
||||
-webkit-filter: grayscale(100%);
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.videoProblemFilter {
|
||||
-webkit-filter: blur(10px) grayscale(.5) opacity(0.8);
|
||||
filter: blur(10px) grayscale(.5) opacity(0.8);
|
||||
}
|
||||
|
||||
.videoThumbnailProblemFilter {
|
||||
-webkit-filter: grayscale(100%);
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
#remotePresenceMessage,
|
||||
#remoteConnectionMessage {
|
||||
position: absolute;
|
||||
@@ -608,24 +597,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
#localConnectionMessage {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
top:50%;
|
||||
z-index: $zindex2;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: #FFF;
|
||||
opacity: .80;
|
||||
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 1px 1px rgba(0,0,0,0.3),
|
||||
1px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 0px 1px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.display-avatar-with-name {
|
||||
.avatar-container {
|
||||
visibility: visible;
|
||||
|
||||
@@ -13,9 +13,24 @@
|
||||
@extend %align-right;
|
||||
transition: bottom .3s;
|
||||
z-index: $filmstripVideosZ;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
|
||||
/*
|
||||
* Firefox sets flex items to min-height: auto and min-width: auto,
|
||||
* preventing flex children from shrinking like they do on other browsers.
|
||||
* Setting min-height and min-width 0 is a workaround for the issue so
|
||||
* Firefox behaves like other browsers.
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1043520
|
||||
*/
|
||||
@mixin minHWAutoFix() {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&.reduce-height {
|
||||
bottom: $newToolbarSizeWithPadding;
|
||||
bottom: calc(#{$newToolbarSizeWithPadding} + #{$scrollHeight});
|
||||
}
|
||||
|
||||
&__videos {
|
||||
@@ -29,8 +44,9 @@
|
||||
|
||||
&#remoteVideos {
|
||||
border: $thumbnailsBorder solid transparent;
|
||||
padding-left: $defaultToolbarSize + 5;
|
||||
transition: bottom 2s;
|
||||
flex-grow: 1;
|
||||
@include minHWAutoFix()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,6 +55,7 @@
|
||||
&#filmstripLocalVideo {
|
||||
align-self: flex-end;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
@@ -74,4 +91,52 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
#filmstripRemoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
width: auto;
|
||||
justify-content: flex-end;
|
||||
flex-direction: row;
|
||||
|
||||
#filmstripRemoteVideosContainer {
|
||||
flex-direction: row-reverse;
|
||||
/**
|
||||
* Add padding as a hack for Firefox not to show scrollbars when
|
||||
* unnecessary.
|
||||
*/
|
||||
padding: 1px 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
.videocontainer {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Workarounds for Edge and Firefox not handling scrolling properly with
|
||||
* flex-direction: row-reverse.
|
||||
*/
|
||||
@mixin undoRowReverseVideos() {
|
||||
.horizontal-filmstrip {
|
||||
#remoteVideos #filmstripRemoteVideos #filmstripRemoteVideosContainer {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Firefox detection hack **/
|
||||
@-moz-document url-prefix() {
|
||||
@include undoRowReverseVideos();
|
||||
}
|
||||
|
||||
/** Edge detection hack **/
|
||||
@supports (-ms-ime-align:auto) {
|
||||
@include undoRowReverseVideos();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.filmstrip__videos .videocontainer {
|
||||
display: none;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
background-size: contain;
|
||||
border: $thumbnailVideoBorder solid transparent;
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
#localConnectionMessage,
|
||||
#remoteConnectionMessage,
|
||||
.watermark {
|
||||
z-index: $filmstripVideosZ + 1;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: ($desktopAppDragBarHeight - 5px) 5px 10px;
|
||||
/**
|
||||
* fixed positioning is necessary for remote menus and tooltips to pop
|
||||
@@ -48,7 +49,6 @@
|
||||
.filmstrip__videos {
|
||||
@extend %align-right;
|
||||
bottom: 0;
|
||||
overflow: visible !important;
|
||||
padding: 0;
|
||||
position:relative;
|
||||
right: 0;
|
||||
@@ -67,6 +67,7 @@
|
||||
border: $thumbnailsBorder solid transparent;
|
||||
padding-left: 0;
|
||||
transition: right 2s;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +81,16 @@
|
||||
flex-direction: column-reverse;
|
||||
height: auto;
|
||||
justify-content: flex-start;
|
||||
|
||||
#filmstripLocalVideoThumbnail {
|
||||
width: calc(100% - 15px);
|
||||
|
||||
.videocontainer {
|
||||
height: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,18 +107,21 @@
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
flex-direction: column-reverse;
|
||||
height: auto;
|
||||
justify-content: flex-end;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
|
||||
#filmstripRemoteVideosContainer {
|
||||
@include minHWAutoFix();
|
||||
flex-direction: column-reverse;
|
||||
/**
|
||||
* Add padding as a hack for Firefox not to show scrollbars when
|
||||
* unnecessary.
|
||||
*/
|
||||
padding: 1px 0;
|
||||
overflow-x: hidden;
|
||||
overflow: visible;
|
||||
width: calc(100% - 8px); // 8px for margin + border of the thumbnails
|
||||
|
||||
.videocontainer {
|
||||
height: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +136,13 @@
|
||||
display: flex;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
.hide-scrollbar#filmstripRemoteVideos {
|
||||
margin-right: 7px; // Scrollbar size
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,9 +181,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FF does not include the scroll width when calculating the size of the content. That's why we need to include
|
||||
* ourselves the width of the scroll so that the remote videos are aligned with the local one.
|
||||
*/
|
||||
@mixin filmstripSizeWithoutScroll {
|
||||
.vertical-filmstrip {
|
||||
#remoteVideos #filmstripRemoteVideos {
|
||||
#filmstripRemoteVideosContainer {
|
||||
width: calc(100% - 15px) // 8 px - margins + border of the thumbnails; 7px - for the scroll
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Firefox detection hack **/
|
||||
@-moz-document url-prefix() {
|
||||
@include undoColumnReverseVideos();
|
||||
@include filmstripSizeWithoutScroll();
|
||||
}
|
||||
|
||||
/** Edge detection hack **/
|
||||
|
||||
@@ -56,6 +56,10 @@
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.indicator-icon-container {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.indicator-container {
|
||||
float: none;
|
||||
}
|
||||
|
||||
@@ -85,5 +85,6 @@ $flagsImagePath: "../images/";
|
||||
@import 'third-party-branding/microsoft';
|
||||
@import 'avatar';
|
||||
@import 'promotional-footer';
|
||||
@import 'chrome-extension-banner';
|
||||
|
||||
/* Modules END */
|
||||
|
||||
@@ -140,15 +140,13 @@
|
||||
margin-top: 20px;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 5px;
|
||||
border-collapse: collapse;
|
||||
|
||||
thead {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
tr {
|
||||
border-bottom: 1px solid #d1dbe8;
|
||||
}
|
||||
|
||||
|
||||
7
debian/control
vendored
@@ -21,7 +21,7 @@ Description: WebRTC JavaScript video conferences
|
||||
|
||||
Package: jitsi-meet-web-config
|
||||
Architecture: all
|
||||
Depends: openssl, openjdk-8-jre-headless | nginx | nginx-extras | apache2
|
||||
Depends: openssl, nginx | nginx-full | nginx-extras | apache2
|
||||
Description: Configuration for web serving of Jitsi Meet
|
||||
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||
Videobridge to provide high quality, scalable video conferences.
|
||||
@@ -37,6 +37,7 @@ Description: Configuration for web serving of Jitsi Meet
|
||||
Package: jitsi-meet-prosody
|
||||
Architecture: all
|
||||
Depends: openssl, prosody | prosody-trunk | prosody-0.11
|
||||
Replaces: jitsi-meet-tokens
|
||||
Description: Prosody configuration for Jitsi Meet
|
||||
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||
Videobridge to provide high quality, scalable video conferences.
|
||||
@@ -53,3 +54,7 @@ Architecture: all
|
||||
Depends: ${misc:Depends}, prosody-trunk (>= 1nightly747) | prosody-0.11 | prosody (>= 0.11.2), libssl-dev, luarocks, jitsi-meet-prosody
|
||||
Description: Prosody token authentication plugin for Jitsi Meet
|
||||
|
||||
Package: jitsi-meet-turnserver
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, nginx (>= 1.13.10) | nginx-full (>= 1.13.10) | nginx-extras (>= 1.13.10), jitsi-meet-prosody, coturn, dnsutils
|
||||
Description: Configures coturn to be used with Jitsi Meet
|
||||
|
||||
1
debian/jitsi-meet-prosody.docs
vendored
@@ -1,2 +1 @@
|
||||
doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example
|
||||
doc/debian/jitsi-meet-prosody/README
|
||||
|
||||
2
debian/jitsi-meet-prosody.install
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example /usr/share/jitsi-meet-prosody/
|
||||
resources/prosody-plugins/ /usr/share/jitsi-meet/
|
||||
29
debian/jitsi-meet-prosody.postinst
vendored
@@ -80,6 +80,15 @@ case "$1" in
|
||||
# stores the hostname so we will reuse it later, like in purge
|
||||
db_set jitsi-meet-prosody/jvb-hostname "$JVB_HOSTNAME"
|
||||
|
||||
db_get jitsi-meet-prosody/turn-secret
|
||||
if [ -z "$RET" ] ; then
|
||||
# 8-chars random secret used for the turnserver
|
||||
TURN_SECRET=`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1`
|
||||
db_set jitsi-meet-prosody/turn-secret "$TURN_SECRET"
|
||||
else
|
||||
TURN_SECRET="$RET"
|
||||
fi
|
||||
|
||||
# and we're done with debconf
|
||||
db_stop
|
||||
|
||||
@@ -93,11 +102,11 @@ case "$1" in
|
||||
PROSODY_CONFIG_PRESENT="false"
|
||||
mkdir -p /etc/prosody/conf.avail/
|
||||
mkdir -p /etc/prosody/conf.d/
|
||||
cp /usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example $PROSODY_HOST_CONFIG
|
||||
cp /usr/share/jitsi-meet-prosody/prosody.cfg.lua-jvb.example $PROSODY_HOST_CONFIG
|
||||
sed -i "s/jitmeet.example.com/$JVB_HOSTNAME/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/jitmeetSecret/$JVB_SECRET/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/focusSecret/$JICOFO_SECRET/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/focusUser/$JICOFO_AUTH_USER/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/__turnSecret__/$TURN_SECRET/g" $PROSODY_HOST_CONFIG
|
||||
if [ ! -f /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua ]; then
|
||||
ln -s $PROSODY_HOST_CONFIG /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua
|
||||
fi
|
||||
@@ -116,6 +125,20 @@ case "$1" in
|
||||
PROSODY_CONFIG_PRESENT="false"
|
||||
fi
|
||||
|
||||
USER_EXISTS_CHECK=`prosodyctl adduser jvb@$JICOFO_AUTH_DOMAIN < /dev/null || true`
|
||||
if [ ! "$USER_EXISTS_CHECK" = "That user already exists" ]; then
|
||||
prosodyctl register jvb $JICOFO_AUTH_DOMAIN $JVB_SECRET || true
|
||||
fi
|
||||
|
||||
# Check whether prosody config has the internal muc, if not add it,
|
||||
# as we are migrating configs
|
||||
if [ -f $PROSODY_HOST_CONFIG ] && ! grep -q "internal.auth.$JVB_HOSTNAME" $PROSODY_HOST_CONFIG; then
|
||||
echo -e "\nComponent \"internal.auth.$JVB_HOSTNAME\" \"muc\"" >> $PROSODY_HOST_CONFIG
|
||||
echo -e " storage = \"null\"" >> $PROSODY_HOST_CONFIG
|
||||
echo -e " modules_enabled = { \"ping\"; }" >> $PROSODY_HOST_CONFIG
|
||||
echo -e " admins = { \"focusUser@auth.jitmeet.example.com\", \"jvb@auth.jitmeet.example.com\" }" >> $PROSODY_HOST_CONFIG
|
||||
fi
|
||||
|
||||
if [ ! -f /var/lib/prosody/$JVB_HOSTNAME.crt ]; then
|
||||
# prosodyctl takes care for the permissions
|
||||
# echo for using all default values
|
||||
@@ -179,7 +202,7 @@ case "$1" in
|
||||
fi
|
||||
|
||||
if [ "$PROSODY_CONFIG_PRESENT" = "false" ]; then
|
||||
invoke-rc.d prosody restart
|
||||
invoke-rc.d prosody restart || true
|
||||
fi
|
||||
;;
|
||||
|
||||
|
||||
2
debian/jitsi-meet-prosody.postrm
vendored
@@ -25,7 +25,7 @@ set -e
|
||||
case "$1" in
|
||||
remove)
|
||||
if [ -x "/etc/init.d/prosody" ]; then
|
||||
invoke-rc.d prosody reload
|
||||
invoke-rc.d prosody reload || true
|
||||
fi
|
||||
;;
|
||||
|
||||
|
||||
5
debian/jitsi-meet-prosody.templates
vendored
@@ -28,3 +28,8 @@ Template: jicofo/jicofosecret
|
||||
Type: password
|
||||
_Description: Jicofo Component secret:
|
||||
The secret used to connect to xmpp server as component
|
||||
|
||||
Template: jitsi-meet-prosody/turn-secret
|
||||
Type: string
|
||||
_Description: The turn server secret
|
||||
The secret used to connect to turnserver server.
|
||||
|
||||
1
debian/jitsi-meet-tokens.install
vendored
@@ -1 +0,0 @@
|
||||
resources/prosody-plugins/ /usr/share/jitsi-meet/
|
||||
2
debian/jitsi-meet-tokens.postinst
vendored
@@ -78,7 +78,7 @@ case "$1" in
|
||||
fi
|
||||
|
||||
if [ -x "/etc/init.d/prosody" ]; then
|
||||
invoke-rc.d prosody restart
|
||||
invoke-rc.d prosody restart || true
|
||||
fi
|
||||
fi
|
||||
else
|
||||
|
||||
4
debian/jitsi-meet-tokens.postrm
vendored
@@ -41,10 +41,10 @@ case "$1" in
|
||||
sed -i 's/authentication = "token"/authentication = "anonymous"/g' $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ app_id=\"$APP_ID\"/ --app_id=\"example_app_id\"/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ app_secret=\"$APP_SECRET\"/ --app_secret=\"example_app_secret\"/g" $PROSODY_HOST_CONFIG
|
||||
sed -i 's/ modules_enabled = { "token_verification" }/ --modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/ -- "token_verification"/ "token_verification"/g' $PROSODY_HOST_CONFIG
|
||||
|
||||
if [ -x "/etc/init.d/prosody" ]; then
|
||||
invoke-rc.d prosody restart
|
||||
invoke-rc.d prosody restart || true
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
2
debian/jitsi-meet-turnserver.install
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
doc/debian/jitsi-meet-turn/turnserver.conf /usr/share/jitsi-meet-turnserver/
|
||||
doc/debian/jitsi-meet/jitsi-meet.conf /usr/share/jitsi-meet-turnserver/
|
||||
1
debian/jitsi-meet-turnserver.links
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/usr/share/jitsi-meet-turnserver/jitsi-meet.conf /etc/nginx/modules-enabled/60-jitsi-meet.conf
|
||||
127
debian/jitsi-meet-turnserver.postinst
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
# postinst script for jitsi-meet-turnserver
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <postinst> `configure' <most-recently-configured-version>
|
||||
# * <old-postinst> `abort-upgrade' <new version>
|
||||
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
|
||||
# <new-version>
|
||||
# * <postinst> `abort-remove'
|
||||
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
|
||||
# <failed-install-package> <version> `removing'
|
||||
# <conflicting-package> <version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
# loading debconf
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
# try to get host from jitsi-videobridge
|
||||
db_get jitsi-videobridge/jvb-hostname
|
||||
if [ -z "$RET" ] ; then
|
||||
# server hostname
|
||||
db_set jitsi-videobridge/jvb-hostname "localhost"
|
||||
db_input critical jitsi-videobridge/jvb-hostname || true
|
||||
db_go
|
||||
fi
|
||||
JVB_HOSTNAME="$RET"
|
||||
|
||||
TURN_CONFIG="/etc/turnserver.conf"
|
||||
NGINX_CONFIG="/etc/nginx/sites-available/$JVB_HOSTNAME.conf"
|
||||
JITSI_MEET_CONFIG="/etc/jitsi/meet/$JVB_HOSTNAME-config.js"
|
||||
|
||||
# detect dpkg-reconfigure, just delete old links
|
||||
db_get jitsi-meet-turnserver/jvb-hostname
|
||||
JVB_HOSTNAME_OLD=$RET
|
||||
if [ -n "$RET" ] && [ ! "$JVB_HOSTNAME_OLD" = "$JVB_HOSTNAME" ] ; then
|
||||
rm -f $TURN_CONFIG
|
||||
fi
|
||||
|
||||
# this detect only old installations with no nginx
|
||||
db_get jitsi-meet/jvb-serve || true
|
||||
if [ ! -f $NGINX_CONFIG -o "$RET" = "true" ] ; then
|
||||
# nothing to do
|
||||
echo ""
|
||||
echo "turnserver not configured as no nginx found to multiplex traffic"
|
||||
echo ""
|
||||
db_stop
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# stores the hostname so we will reuse it later, like in purge
|
||||
db_set jitsi-meet-turnserver/jvb-hostname "$JVB_HOSTNAME"
|
||||
|
||||
# try to get turnserver password
|
||||
db_get jitsi-meet-prosody/turn-secret
|
||||
if [ -z "$RET" ] ; then
|
||||
db_input critical jitsi-meet-prosody/turn-secret || true
|
||||
db_go
|
||||
fi
|
||||
TURN_SECRET="$RET"
|
||||
|
||||
if [[ -f $TURN_CONFIG ]] && ! grep -q "jitsi-meet coturn config" "$TURN_CONFIG" ; then
|
||||
PUBLIC_IP=$(dig +short myip.opendns.com @resolver1.opendns.com)
|
||||
cp /usr/share/jitsi-meet-turnserver/turnserver.conf $TURN_CONFIG
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" $TURN_CONFIG
|
||||
sed -i "s/__turnSecret__/$TURN_SECRET/g" $TURN_CONFIG
|
||||
sed -i "s/__external_ip_address__/$JVB_HOSTNAME/g" $TURN_CONFIG
|
||||
|
||||
# SSL for nginx
|
||||
db_get jitsi-meet/cert-choice
|
||||
CERT_CHOICE="$RET"
|
||||
|
||||
if [ "$CERT_CHOICE" = "I want to use my own certificate" ] ; then
|
||||
db_get jitsi-meet/cert-path-key
|
||||
CERT_KEY="$RET"
|
||||
db_get jitsi-meet/cert-path-crt
|
||||
CERT_CRT="$RET"
|
||||
|
||||
# replace self-signed certificate paths with user provided ones
|
||||
CERT_KEY_ESC=$(echo $CERT_KEY | sed 's/\./\\\./g')
|
||||
CERT_KEY_ESC=$(echo $CERT_KEY_ESC | sed 's/\//\\\//g')
|
||||
sed -i "s/pkey=\/etc\/jitsi\/meet\/.*key/pkey=$CERT_KEY_ESC/g" $TURN_CONFIG
|
||||
CERT_CRT_ESC=$(echo $CERT_CRT | sed 's/\./\\\./g')
|
||||
CERT_CRT_ESC=$(echo $CERT_CRT_ESC | sed 's/\//\\\//g')
|
||||
sed -i "s/cert=\/etc\/jitsi\/meet\/.*crt/cert=$CERT_CRT_ESC/g" $TURN_CONFIG
|
||||
fi
|
||||
|
||||
sed -i "s/#TURNSERVER_ENABLED/TURNSERVER_ENABLED/g" /etc/default/coturn
|
||||
invoke-rc.d coturn restart || true
|
||||
|
||||
NGINX_STREAM_CONFIG="/etc/nginx/modules-enabled/60-jitsi-meet.conf"
|
||||
if [ -f $NGINX_STREAM_CONFIG ] && [ -f $NGINX_CONFIG ] ; then
|
||||
sed -i "s/listen 443 ssl/listen 4444 ssl http2/g" $NGINX_CONFIG
|
||||
invoke-rc.d nginx reload || true
|
||||
fi
|
||||
|
||||
# Enable turn server in config.js
|
||||
if [ -f $JITSI_MEET_CONFIG ] ; then
|
||||
sed -i "s/\/\/ useStunTurn: true/useStunTurn: true/g" $JITSI_MEET_CONFIG
|
||||
fi
|
||||
fi
|
||||
|
||||
# and we're done with debconf
|
||||
db_stop
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
||||
9
debian/jitsi-meet-turnserver.templates
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Template: jitsi-meet-turnserver/jvb-hostname
|
||||
Type: string
|
||||
_Description: The hostname of the current installation:
|
||||
The value for the hostname that is set in Jitsi Videobridge installation.
|
||||
|
||||
Template: jitsi-videobridge/jvb-hostname
|
||||
Type: string
|
||||
_Description: The hostname of the current installation:
|
||||
The value for the hostname that is set in Jitsi Videobridge installation.
|
||||
3
debian/jitsi-meet-web-config.docs
vendored
@@ -1,4 +1 @@
|
||||
doc/debian/jitsi-meet/jitsi-meet.example
|
||||
doc/debian/jitsi-meet/jitsi-meet.example-apache
|
||||
doc/debian/jitsi-meet/README
|
||||
config.js
|
||||
|
||||
3
debian/jitsi-meet-web-config.install
vendored
Normal file
@@ -0,0 +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/
|
||||
95
debian/jitsi-meet-web-config.postinst
vendored
@@ -43,7 +43,8 @@ case "$1" in
|
||||
fi
|
||||
|
||||
JVB_SERVE="false"
|
||||
db_get jitsi-meet/jvb-serve
|
||||
# this detect only old installations
|
||||
db_get jitsi-meet/jvb-serve || true
|
||||
if [ -n "$RET" ] && [ "$RET" = "true" ] ; then
|
||||
JVB_SERVE="true"
|
||||
fi
|
||||
@@ -52,9 +53,12 @@ case "$1" in
|
||||
db_set jitsi-meet/jvb-hostname $JVB_HOSTNAME
|
||||
|
||||
NGINX_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx' 2>/dev/null | awk '{print $3}' || true)"
|
||||
NGINX_FULL_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx-full' 2>/dev/null | awk '{print $3}' || true)"
|
||||
NGINX_EXTRAS_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx-extras' 2>/dev/null | awk '{print $3}' || true)"
|
||||
if [ "$NGINX_INSTALL_CHECK" = "installed" ] \
|
||||
|| [ "$NGINX_INSTALL_CHECK" = "unpacked" ] \
|
||||
|| [ "$NGINX_FULL_INSTALL_CHECK" = "installed" ] \
|
||||
|| [ "$NGINX_FULL_INSTALL_CHECK" = "unpacked" ] \
|
||||
|| [ "$NGINX_EXTRAS_INSTALL_CHECK" = "installed" ] \
|
||||
|| [ "$NGINX_EXTRAS_INSTALL_CHECK" = "unpacked" ] ; then
|
||||
FORCE_NGINX="true"
|
||||
@@ -98,77 +102,27 @@ case "$1" in
|
||||
# jitsi meet
|
||||
JITSI_MEET_CONFIG="/etc/jitsi/meet/$JVB_HOSTNAME-config.js"
|
||||
if [ ! -f $JITSI_MEET_CONFIG ] ; then
|
||||
cp /usr/share/doc/jitsi-meet-web-config/config.js $JITSI_MEET_CONFIG
|
||||
cp /usr/share/jitsi-meet-web-config/config.js $JITSI_MEET_CONFIG
|
||||
# replaces needed config for multidomain as it works only with nginx
|
||||
sed -i "s/conference.jitsi-meet.example.com/conference.<\!--# echo var=\"subdomain\" default=\"\" -->jitsi-meet.example.com/g" $JITSI_MEET_CONFIG
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" $JITSI_MEET_CONFIG
|
||||
fi
|
||||
|
||||
# this is new install let's configure jvb to serve meet
|
||||
# no-nginx, no-apache installed on machine, this is new install or reconfiguring old one which have jvb_serve set
|
||||
if [[ -z "$FORCE_NGINX" && -z "$FORCE_APACHE" && ( -z "$JVB_HOSTNAME_OLD" || ( "$JVB_SERVE" = "true" && "$RECONFIGURING" = "true" )) ]] ; then
|
||||
|
||||
JVB_ETC_CONFIG="/etc/jitsi/videobridge/config"
|
||||
JVB_CONFIG="/etc/jitsi/videobridge/sip-communicator.properties"
|
||||
|
||||
# this is a reconfigure, lets just delete old links
|
||||
if [ "$RECONFIGURING" = "true" ] ; then
|
||||
rm -f $JVB_CONFIG
|
||||
fi
|
||||
|
||||
# we will write to the file if missing create it
|
||||
if [ ! -f $JVB_CONFIG ] ; then
|
||||
touch $JVB_CONFIG
|
||||
fi
|
||||
|
||||
# configure jvb
|
||||
echo "AUTHBIND=yes" >> $JVB_ETC_CONFIG
|
||||
sed -i "s/JVB_OPTS=.*/JVB_OPTS=--apis=rest,xmpp/g" $JVB_ETC_CONFIG
|
||||
|
||||
echo "org.jitsi.videobridge.rest.jetty.host=::" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.port=443" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ProxyServlet.hostHeader=$JVB_HOSTNAME" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ProxyServlet.pathSpec=/http-bind" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ProxyServlet.proxyTo=http://localhost:5280/http-bind" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.resourceBase=/usr/share/jitsi-meet" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./config.js=/etc/jitsi/meet/$JVB_HOSTNAME-config.js" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./interface_config.js=/usr/share/jitsi-meet/interface_config.js" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./logging_config.js=/usr/share/jitsi-meet/logging_config.js" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./external_api.js=/usr/share/jitsi-meet/libs/external_api.min.js" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.regex=^/([a-zA-Z0-9]+)$" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.replacement=/" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.SSIResourceHandler.paths=/" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.tls.port=443" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.TCP_HARVESTER_PORT=443" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.sslContextFactory.keyStorePath=/etc/jitsi/videobridge/$JVB_HOSTNAME.jks" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.sslContextFactory.keyStorePassword=changeit" >> $JVB_CONFIG
|
||||
|
||||
# configure authbind to allow jvb to bind to privileged ports
|
||||
OWNER=$(stat -c '%U' /usr/share/jitsi-videobridge)
|
||||
GROUP=$(stat -c '%G' /usr/share/jitsi-videobridge)
|
||||
JVB_UID="`id -u $OWNER`"
|
||||
if [ ! -f "/etc/authbind/byport/443" ] ; then
|
||||
if [ ! -d "/etc/authbind/byport" ] ; then
|
||||
mkdir -p /etc/authbind/byport
|
||||
chmod 755 /etc/authbind
|
||||
chmod 755 /etc/authbind/byport
|
||||
fi
|
||||
touch /etc/authbind/byport/443
|
||||
chown $OWNER /etc/authbind/byport/443
|
||||
chmod 755 /etc/authbind/byport/443
|
||||
fi
|
||||
|
||||
CERT_P12="/etc/jitsi/videobridge/$JVB_HOSTNAME.p12"
|
||||
CERT_JKS="/etc/jitsi/videobridge/$JVB_HOSTNAME.jks"
|
||||
# create jks from certs
|
||||
openssl pkcs12 -export \
|
||||
-in $CERT_CRT -inkey $CERT_KEY -passout pass:changeit > $CERT_P12
|
||||
keytool -importkeystore -destkeystore $CERT_JKS \
|
||||
-srckeystore $CERT_P12 -srcstoretype pkcs12 \
|
||||
-noprompt -storepass changeit -srcstorepass changeit
|
||||
|
||||
db_set jitsi-meet/jvb-serve "true"
|
||||
|
||||
invoke-rc.d jitsi-videobridge restart
|
||||
if [[ "$JVB_SERVE" = "true" ]] ; then
|
||||
echo ""
|
||||
echo "------------------------------------------------"
|
||||
echo "You are using jetty to serve jitsi-meet, it is recommended to uninstall(purge) and use default installation that comes with nginx!"
|
||||
echo ""
|
||||
echo "When using the following command, any custom config will be LOST, please backup /etc/jitsi !!!"
|
||||
echo ""
|
||||
echo "You can purge your installation using the following command:"
|
||||
echo "apt-get purge jitsi-meet jitsi-meet-web-config jitsi-meet-prosody jitsi-meet-web jicofo jitsi-videobridge"
|
||||
echo "------------------------------------------------"
|
||||
echo ""
|
||||
elif [[ "$FORCE_NGINX" = "true" && ( -z "$JVB_HOSTNAME_OLD" || "$RECONFIGURING" = "true" ) ]] ; then
|
||||
|
||||
# this is a reconfigure, lets just delete old links
|
||||
if [ "$RECONFIGURING" = "true" ] ; then
|
||||
rm -f /etc/nginx/sites-enabled/$JVB_HOSTNAME_OLD.conf
|
||||
@@ -177,7 +131,7 @@ case "$1" in
|
||||
|
||||
# nginx conf
|
||||
if [ ! -f /etc/nginx/sites-available/$JVB_HOSTNAME.conf ] ; then
|
||||
cp /usr/share/doc/jitsi-meet-web-config/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
cp /usr/share/jitsi-meet-web-config/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
if [ ! -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf ] ; then
|
||||
ln -s /etc/nginx/sites-available/$JVB_HOSTNAME.conf /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
@@ -196,8 +150,9 @@ case "$1" in
|
||||
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
|
||||
invoke-rc.d nginx reload
|
||||
invoke-rc.d nginx reload || true
|
||||
elif [[ "$FORCE_APACHE" = "true" && ( -z "$JVB_HOSTNAME_OLD" || "$RECONFIGURING" = "true" ) ]] ; then
|
||||
|
||||
# this is a reconfigure, lets just delete old links
|
||||
if [ "$RECONFIGURING" = "true" ] ; then
|
||||
a2dissite $JVB_HOSTNAME_OLD.conf
|
||||
@@ -208,7 +163,7 @@ case "$1" in
|
||||
if [ ! -f /etc/apache2/sites-available/$JVB_HOSTNAME.conf ] ; then
|
||||
# when creating new config, make sure all needed modules are enabled
|
||||
a2enmod rewrite ssl headers proxy_http include
|
||||
cp /usr/share/doc/jitsi-meet-web-config/jitsi-meet.example-apache /etc/apache2/sites-available/$JVB_HOSTNAME.conf
|
||||
cp /usr/share/jitsi-meet-web-config/jitsi-meet.example-apache /etc/apache2/sites-available/$JVB_HOSTNAME.conf
|
||||
a2ensite $JVB_HOSTNAME.conf
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/apache2/sites-available/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
@@ -225,7 +180,7 @@ case "$1" in
|
||||
/etc/apache2/sites-available/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
|
||||
invoke-rc.d apache2 reload
|
||||
invoke-rc.d apache2 reload || true
|
||||
fi
|
||||
|
||||
echo "----------------"
|
||||
|
||||
6
debian/jitsi-meet-web-config.postrm
vendored
@@ -25,10 +25,10 @@ set -e
|
||||
case "$1" in
|
||||
remove)
|
||||
if [ -x "/etc/init.d/nginx" ]; then
|
||||
invoke-rc.d nginx reload
|
||||
invoke-rc.d nginx reload || true
|
||||
fi
|
||||
if [ -x "/etc/init.d/apache2" ]; then
|
||||
invoke-rc.d apache2 reload
|
||||
invoke-rc.d apache2 reload || true
|
||||
fi
|
||||
;;
|
||||
purge)
|
||||
@@ -40,8 +40,6 @@ case "$1" in
|
||||
rm -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
|
||||
rm -f /etc/apache2/sites-available/$JVB_HOSTNAME.conf
|
||||
rm -f /etc/apache2/sites-enabled/$JVB_HOSTNAME.conf
|
||||
rm -f /etc/jitsi/videobridge/$JVB_HOSTNAME.jks
|
||||
rm -f /etc/jitsi/videobridge/$JVB_HOSTNAME.p12
|
||||
rm -f /etc/jitsi/meet/$JVB_HOSTNAME.key
|
||||
rm -f /etc/jitsi/meet/$JVB_HOSTNAME.crt
|
||||
fi
|
||||
|
||||
6
debian/jitsi-meet-web-config.templates
vendored
@@ -26,12 +26,6 @@ Type: string
|
||||
_Description: The hostname of the current installation:
|
||||
The value for the hostname that is set in Jitsi Videobridge installation.
|
||||
|
||||
Template: jitsi-meet/jvb-serve
|
||||
Type: boolean
|
||||
Default: false
|
||||
_Description: for internal use
|
||||
for internal use.
|
||||
|
||||
Template: jitsi-videobridge/jvb-hostname
|
||||
Type: string
|
||||
_Description: Hostname:
|
||||
|
||||
2
debian/rules
vendored
@@ -14,7 +14,7 @@ override_dh_auto_build:
|
||||
|
||||
override_dh_install: $(LANGUAGES)
|
||||
dh_installdirs
|
||||
dh_install -X/config.js -X/package.json
|
||||
dh_install
|
||||
|
||||
$(LANGUAGES):
|
||||
LOCALE=$$(echo $@ | cut -c1-2) ; \
|
||||
|
||||
14
doc/api.md
@@ -30,6 +30,7 @@ Its constructor gets a number of options:
|
||||
* **onload**: (optional) handler for the iframe onload event.
|
||||
* **invitees**: (optional) Array of objects containing information about new participants that will be invited in the call.
|
||||
* **devices**: (optional) A map containing information about the initial devices that will be used in the call.
|
||||
* **userInfo**: (optional) JS object containing information about the participant opening the meeting, such as `email`.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -84,6 +85,19 @@ const options = {
|
||||
const api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
You can set the userInfo(email) for the call:
|
||||
|
||||
```javascript
|
||||
var domain = "meet.jit.si";
|
||||
var options = {
|
||||
...
|
||||
userInfo: {
|
||||
email: 'email@jitsiexamplemail.com'
|
||||
}
|
||||
}
|
||||
var api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
### Controlling the embedded Jitsi Meet Conference
|
||||
|
||||
Device management `JitsiMeetExternalAPI` methods:
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
-- Plugins path gets uncommented during jitsi-meet-tokens package install - that's where token plugin is located
|
||||
--plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }
|
||||
plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }
|
||||
|
||||
-- domain mapper options, must at least have domain base set to use the mapper
|
||||
muc_mapper_domain_base = "jitmeet.example.com";
|
||||
|
||||
turncredentials_secret = "__turnSecret__";
|
||||
|
||||
turncredentials = {
|
||||
{ type = "stun", host = "jitmeet.example.com", port = "443" },
|
||||
{ type = "turn", host = "jitmeet.example.com", port = "443", transport = "udp" },
|
||||
{ type = "turns", host = "jitmeet.example.com", port = "443", transport = "tcp" }
|
||||
};
|
||||
|
||||
cross_domain_bosh = false;
|
||||
consider_bosh_secure = true;
|
||||
|
||||
VirtualHost "jitmeet.example.com"
|
||||
-- enabled = false -- Remove this line to enable this host
|
||||
@@ -16,25 +29,44 @@ VirtualHost "jitmeet.example.com"
|
||||
key = "/etc/prosody/certs/jitmeet.example.com.key";
|
||||
certificate = "/etc/prosody/certs/jitmeet.example.com.crt";
|
||||
}
|
||||
speakerstats_component = "speakerstats.jitmeet.example.com"
|
||||
conference_duration_component = "conferenceduration.jitmeet.example.com"
|
||||
-- we need bosh
|
||||
modules_enabled = {
|
||||
"bosh";
|
||||
"pubsub";
|
||||
"ping"; -- Enable mod_ping
|
||||
"speakerstats";
|
||||
"turncredentials";
|
||||
"conference_duration";
|
||||
}
|
||||
|
||||
c2s_require_encryption = false
|
||||
|
||||
Component "conference.jitmeet.example.com" "muc"
|
||||
storage = "null"
|
||||
--modules_enabled = { "token_verification" }
|
||||
admins = { "focusUser@auth.jitmeet.example.com" }
|
||||
modules_enabled = {
|
||||
"muc_meeting_id";
|
||||
"muc_domain_mapper";
|
||||
-- "token_verification";
|
||||
}
|
||||
admins = { "focusUser@auth.jitmeet.example.com" }
|
||||
|
||||
Component "jitsi-videobridge.jitmeet.example.com"
|
||||
component_secret = "jitmeetSecret"
|
||||
-- internal muc component
|
||||
Component "internal.auth.jitmeet.example.com" "muc"
|
||||
storage = "null"
|
||||
modules_enabled = {
|
||||
"ping";
|
||||
}
|
||||
admins = { "focusUser@auth.jitmeet.example.com", "jvb@auth.jitmeet.example.com" }
|
||||
|
||||
VirtualHost "auth.jitmeet.example.com"
|
||||
authentication = "internal_plain"
|
||||
|
||||
Component "focus.jitmeet.example.com"
|
||||
component_secret = "focusSecret"
|
||||
|
||||
Component "speakerstats.jitmeet.example.com" "speakerstats_component"
|
||||
muc_component = "conference.jitmeet.example.com"
|
||||
|
||||
Component "conferenceduration.jitmeet.example.com" "conference_duration_component"
|
||||
muc_component = "conference.jitmeet.example.com"
|
||||
|
||||
1
doc/debian/jitsi-meet-turn/RREADME
Normal file
@@ -0,0 +1 @@
|
||||
Coturn configuration for Jitsi Meet
|
||||
13
doc/debian/jitsi-meet-turn/turnserver.conf
Normal file
@@ -0,0 +1,13 @@
|
||||
# jitsi-meet coturn config. Do not modify this line
|
||||
lt-cred-mech
|
||||
use-auth-secret
|
||||
keep-address-family
|
||||
static-auth-secret=__turnSecret__
|
||||
realm=jitsi-meet.example.com
|
||||
cert=/etc/jitsi/meet/jitsi-meet.example.com.crt
|
||||
pkey=/etc/jitsi/meet/jitsi-meet.example.com.key
|
||||
|
||||
no-tcp
|
||||
listening-port=443
|
||||
tls-listening-port=4445
|
||||
external-ip=__external_ip_address__
|
||||
30
doc/debian/jitsi-meet/jitsi-meet.conf
Normal file
@@ -0,0 +1,30 @@
|
||||
# this is jitsi-meet nginx module configuration
|
||||
# this forward all http traffic to the nginx virtual host port
|
||||
# and the rest to the turn server
|
||||
|
||||
stream {
|
||||
upstream web {
|
||||
server 127.0.0.1:4444;
|
||||
}
|
||||
upstream turn {
|
||||
server 127.0.0.1:4445;
|
||||
}
|
||||
# since 1.13.10
|
||||
map $ssl_preread_alpn_protocols $upstream {
|
||||
"h2" web;
|
||||
"http/1.1" web;
|
||||
"h2,http/1.1" web;
|
||||
default turn;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
|
||||
# since 1.11.5
|
||||
ssl_preread on;
|
||||
proxy_pass $upstream;
|
||||
|
||||
# Increase buffer to serve video
|
||||
proxy_buffer_size 10m;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,17 @@ server_names_hash_bucket_size 64;
|
||||
server {
|
||||
listen 80;
|
||||
server_name jitsi-meet.example.com;
|
||||
return 301 https://$host$request_uri;
|
||||
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
default_type "text/plain";
|
||||
root /usr/share/jitsi-meet;
|
||||
}
|
||||
location = /.well-known/acme-challenge/ {
|
||||
return 404;
|
||||
}
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 443 ssl;
|
||||
@@ -19,7 +29,11 @@ server {
|
||||
ssl_certificate_key /etc/jitsi/meet/jitsi-meet.example.com.key;
|
||||
|
||||
root /usr/share/jitsi-meet;
|
||||
|
||||
# ssi on with javascript for multidomain variables in config.js
|
||||
ssi on;
|
||||
ssi_types application/x-javascript application/javascript;
|
||||
|
||||
index index.html index.htm;
|
||||
error_page 404 /static/404.html;
|
||||
|
||||
@@ -45,11 +59,54 @@ server {
|
||||
proxy_set_header Host $http_host;
|
||||
}
|
||||
|
||||
location ~ ^/([^?&:’“]+)$ {
|
||||
# xmpp websockets
|
||||
location = /xmpp-websocket {
|
||||
proxy_pass http://127.0.0.1:5280/xmpp-websocket?prefix=$prefix&$args;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $http_host;
|
||||
tcp_nodelay on;
|
||||
}
|
||||
|
||||
location ~ ^/([^/?&:'"]+)$ {
|
||||
try_files $uri @root_path;
|
||||
}
|
||||
|
||||
location @root_path {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
|
||||
location ~ ^/([^/?&:'"]+)/config.js$
|
||||
{
|
||||
set $subdomain "$1.";
|
||||
set $subdir "$1/";
|
||||
|
||||
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
|
||||
}
|
||||
|
||||
#Anything that didn't match above, and isn't a real file, assume it's a room name and redirect to /
|
||||
location ~ ^/([^/?&:'"]+)/(.*)$ {
|
||||
set $subdomain "$1.";
|
||||
set $subdir "$1/";
|
||||
rewrite ^/([^/?&:'"]+)/(.*)$ /$2;
|
||||
}
|
||||
|
||||
# BOSH for subdomains
|
||||
location ~ ^/([^/?&:'"]+)/http-bind {
|
||||
set $subdomain "$1.";
|
||||
set $subdir "$1/";
|
||||
set $prefix "$1";
|
||||
|
||||
rewrite ^/(.*)$ /http-bind;
|
||||
}
|
||||
|
||||
# websockets for subdomains
|
||||
location ~ ^/([^/?&:'"]+)/xmpp-websocket {
|
||||
set $subdomain "$1.";
|
||||
set $subdir "$1/";
|
||||
set $prefix "$1";
|
||||
|
||||
rewrite ^/(.*)$ /xmpp-websocket;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,6 @@ var config = {
|
||||
focus: 'focus.jitsi.example.com',
|
||||
},
|
||||
useNicks: false,
|
||||
bosh: '//jitsi.example.com/http-bind' // FIXME: use xep-0156 for that
|
||||
bosh: '//jitsi.example.com/http-bind', // FIXME: use xep-0156 for that
|
||||
websocket: 'wss://jitsi.example.com/xmpp-websocket'
|
||||
};
|
||||
|
||||
@@ -36,10 +36,6 @@ server {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
|
||||
location / {
|
||||
ssi on;
|
||||
}
|
||||
|
||||
location ~ ^/([^/?&:'"]+)/config.js$
|
||||
{
|
||||
set $subdomain "$1.";
|
||||
@@ -64,4 +60,12 @@ server {
|
||||
rewrite ^/(.*)$ /http-bind;
|
||||
}
|
||||
|
||||
}
|
||||
# websockets for subdomains
|
||||
location ~ ^/([^/?&:'"]+)/xmpp-websocket {
|
||||
set $subdomain "$1.";
|
||||
set $subdir "$1/";
|
||||
set $prefix "$1";
|
||||
|
||||
rewrite ^/(.*)$ /xmpp-websocket;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +83,6 @@ modules_enabled = {
|
||||
"adhoc";
|
||||
"websocket";
|
||||
"http_altconnect";
|
||||
-- include domain mapper as global level module
|
||||
"muc_domain_mapper";
|
||||
}
|
||||
|
||||
-- domain mapper options, must at least have domain base set to use the mapper
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
# Server Installation for Jitsi Meet
|
||||
|
||||
|
||||
:warning: **WARNING:** Manual installation is not recommended. We recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document. The current document describes the steps that are needed to install a working deployment, but steps are easy to mess up, and the debian packages are more up-to-date, where this document sometimes is not updated to latest changes.
|
||||
|
||||
|
||||
:warning: **WARNING:** Manual installation is not recommended. We recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document. The current document describes the steps that are needed to install a working deployment, but steps are easy to mess up, and the debian packages are more up-to-date, where this document is sometimes not updated to reflect latest changes.
|
||||
|
||||
This describes configuring a server `jitsi.example.com` running Debian or a Debian Derivative. You will need to
|
||||
change references to that to match your host, and generate some passwords for
|
||||
@@ -22,7 +19,7 @@ This is how the network looks:
|
||||
443 |
|
||||
+-------+ |
|
||||
| | |
|
||||
| NginX | |
|
||||
| Nginx | |
|
||||
| | |
|
||||
+--+-+--+ |
|
||||
| | |
|
||||
@@ -31,7 +28,7 @@ This is how the network looks:
|
||||
| jitsi-meet +<---+ +--->+ prosody/xmpp | |
|
||||
| |files 5280 | | |
|
||||
+------------+ +--------------+ v
|
||||
5222,5347^ ^5347 4443
|
||||
5222,5347^ ^5347 4443,10000
|
||||
+--------+ | | +-------------+
|
||||
| | | | | |
|
||||
| jicofo +----^ ^----+ videobridge |
|
||||
@@ -112,7 +109,7 @@ Restart prosody XMPP server with the new config
|
||||
prosodyctl restart
|
||||
```
|
||||
|
||||
## Install nginx
|
||||
## Install Nginx
|
||||
```sh
|
||||
apt-get install nginx
|
||||
```
|
||||
@@ -122,12 +119,13 @@ Add a new file `jitsi.example.com` in `/etc/nginx/sites-available` (see also the
|
||||
server_names_hash_bucket_size 64;
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
listen 0.0.0.0:443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
# tls configuration that is not covered in this guide
|
||||
# we recommend the use of https://certbot.eff.org/
|
||||
server_name jitsi.example.com;
|
||||
# set the root
|
||||
root /srv/jitsi.example.com;
|
||||
root /srv/jitsi-meet;
|
||||
index index.html;
|
||||
location ~ ^/([a-zA-Z0-9=\?]+)$ {
|
||||
rewrite ^/(.*)$ / break;
|
||||
@@ -152,6 +150,7 @@ ln -s ../sites-available/jitsi.example.com jitsi.example.com
|
||||
```
|
||||
|
||||
## Install Jitsi Videobridge
|
||||
Visit https://download.jitsi.org/jitsi-videobridge/linux to determine the current build number, download and unzip it:
|
||||
```sh
|
||||
wget https://download.jitsi.org/jitsi-videobridge/linux/jitsi-videobridge-linux-{arch-buildnum}.zip
|
||||
unzip jitsi-videobridge-linux-{arch-buildnum}.zip
|
||||
@@ -164,9 +163,10 @@ apt-get install openjdk-8-jre
|
||||
|
||||
_NOTE: When installing on older Debian releases keep in mind that you need JRE >= 1.7._
|
||||
|
||||
In the user home that will be starting Jitsi Videobridge create `.sip-communicator` folder and add the file `sip-communicator.properties` with one line in it:
|
||||
```
|
||||
org.jitsi.impl.neomedia.transform.srtp.SRTPCryptoContext.checkReplay=false
|
||||
Create `~/.sip-communicator/sip-communicator.properties` in the home folder of the user that will be starting Jitsi Videobridge:
|
||||
```sh
|
||||
mkdir -p ~/.sip-communicator
|
||||
echo "org.jitsi.impl.neomedia.transform.srtp.SRTPCryptoContext.checkReplay=false" > ~/.sip-communicator/sip-communicator.properties
|
||||
```
|
||||
|
||||
Start the videobridge with:
|
||||
@@ -191,7 +191,7 @@ Clone source from Github repo:
|
||||
```sh
|
||||
git clone https://github.com/jitsi/jicofo.git
|
||||
```
|
||||
Build distribution package. Replace {os-name} with one of: 'lin', 'lin64', 'macosx', 'win', 'win64'.
|
||||
Build the package.
|
||||
```sh
|
||||
cd jicofo
|
||||
mvn package -DskipTests -Dassembly.skipAssembly=false
|
||||
@@ -199,8 +199,8 @@ mvn package -DskipTests -Dassembly.skipAssembly=false
|
||||
Run jicofo:
|
||||
```sh
|
||||
=======
|
||||
unzip target/jicofo-{os-name}-1.0-SNAPSHOT.zip
|
||||
cd jicofo-{os-name}-1.0-SNAPSHOT'
|
||||
unzip target/jicofo-1.1-SNAPSHOT-archive.zip
|
||||
cd jicofo-1.1-SNAPSHOT-archive'
|
||||
./jicofo.sh --host=localhost --domain=jitsi.example.com --secret=YOURSECRET2 --user_domain=auth.jitsi.example.com --user_name=focus --user_password=YOURSECRET3
|
||||
```
|
||||
|
||||
@@ -209,13 +209,12 @@ Checkout and configure Jitsi Meet:
|
||||
```sh
|
||||
cd /srv
|
||||
git clone https://github.com/jitsi/jitsi-meet.git
|
||||
mv jitsi-meet/ jitsi.example.com
|
||||
cd jitsi.example.com
|
||||
cd jitsi-meet
|
||||
npm install
|
||||
make
|
||||
```
|
||||
|
||||
Edit host names in `/srv/jitsi.example.com/config.js` (see also the example config file):
|
||||
Edit host names in `/srv/jitsi-meet/config.js` (see also the example config file):
|
||||
```
|
||||
var config = {
|
||||
hosts: {
|
||||
@@ -231,15 +230,19 @@ var config = {
|
||||
};
|
||||
```
|
||||
|
||||
Restart nginx to get the new configuration:
|
||||
Verify that nginx config is valid and reload nginx:
|
||||
```sh
|
||||
invoke-rc.d nginx restart
|
||||
nginx -t && nginx -s reload
|
||||
```
|
||||
|
||||
## Running behind NAT
|
||||
Jitsi-Videobridge can run behind a NAT, provided that all required ports are routed (forwarded) to the machine that it runs on. By default these ports are (TCP/443 or TCP/4443 and UDP 10000).
|
||||
Jitsi Videobridge can run behind a NAT, provided that both required ports are routed (forwarded) to the machine that it runs on. By default these ports are `TCP/4443` and `UDP/10000`.
|
||||
|
||||
The following extra lines need to be added the file `~/.sip-communicator/sip-communicator.properties` (in the home directory of the user running the videobridge):
|
||||
If you do not route these two ports, Jitsi Meet will only work with video for two people, breaking upon 3 or more people trying to show video.
|
||||
|
||||
`TCP/443` is required for the webserver which can be running on another machine than the Jitsi Videobrige is running on.
|
||||
|
||||
The following extra lines need to be added to the file `~/.sip-communicator/sip-communicator.properties` (in the home directory of the user running the videobridge):
|
||||
```
|
||||
org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS=<Local.IP.Address>
|
||||
org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=<Public.IP.Address>
|
||||
@@ -248,6 +251,5 @@ org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=<Public.IP.Address>
|
||||
# Hold your first conference
|
||||
You are now all set and ready to have your first meet by going to http://jitsi.example.com
|
||||
|
||||
|
||||
## Enabling recording
|
||||
[Jibri](https://github.com/jitsi/jibri)is a set of tools for recording and/or streaming a Jitsi Meet conference.
|
||||
[Jibri](https://github.com/jitsi/jibri) is a set of tools for recording and/or streaming a Jitsi Meet conference.
|
||||
|
||||
@@ -6,7 +6,7 @@ Debian Wheezy and other older systems may require additional things to be done.
|
||||
|
||||
Also note that a recent default Ubuntu installation has only the `main` repository enabled, and Jitsi Meet needs packages from `universe`. Check your `/etc/apt/sources.list` file, and if `universe` is not present refer to [Ubuntu's documentation](https://help.ubuntu.com/community/Repositories/Ubuntu) on how to enable it. (Usually it amounts to copying the `main` lines and changing to `universe`.)
|
||||
|
||||
N.B.:
|
||||
N.B.:
|
||||
|
||||
a.) All commands are supposed to be run by root. If you are logged in as a regular user with sudo rights, please prepend ___sudo___ to each of the commands.
|
||||
|
||||
@@ -46,7 +46,7 @@ During the installation, you will be asked to enter the hostname of the Jitsi Me
|
||||
|
||||
This hostname (or IP address) will be used for virtualhost configuration inside the Jitsi Meet and also, you and your correspondents will be using it to access the web conferences.
|
||||
|
||||
### Generate a Let's Encrypt certificate
|
||||
### Generate a Let's Encrypt certificate
|
||||
|
||||
Simply run the following in your shell
|
||||
|
||||
@@ -109,7 +109,7 @@ Enjoy!
|
||||
## Uninstall
|
||||
|
||||
```sh
|
||||
apt-get purge jigasi jitsi-meet jitsi-meet-web-config jitsi-meet-prosody jitsi-meet-web jicofo jitsi-videobridge
|
||||
apt-get purge jigasi jitsi-meet jitsi-meet-web-config jitsi-meet-prosody jitsi-meet-turnserver jitsi-meet-web jicofo jitsi-videobridge
|
||||
```
|
||||
|
||||
Sometimes the following packages will fail to uninstall properly:
|
||||
@@ -124,7 +124,7 @@ The reason for failure is that sometimes, the uninstall script is faster than th
|
||||
#### Systemd details
|
||||
To reload the systemd changes on a running system execute `systemctl daemon-reload` and `service jitsi-videobridge restart`.
|
||||
To check the tasks part execute `service jitsi-videobridge status` and you should see `Tasks: XX (limit: 65000)`.
|
||||
To check the files and process part execute ```cat /proc/`cat /var/run/jitsi-videobridge.pid`/limits``` and you should see:
|
||||
To check the files and process part execute ```cat /proc/`cat /var/run/jitsi-videobridge/jitsi-videobridge.pid`/limits``` and you should see:
|
||||
```
|
||||
Max processes 65000 65000 processes
|
||||
Max open files 65000 65000 files
|
||||
|
||||
BIN
images/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 8.1 KiB |
9
images/chromeLogo.svg
Normal file
|
After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="324px" height="63.8px" viewBox="0 0 324 63.8" style="enable-background:new 0 0 324 63.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0061FF;}
|
||||
.st1{display:none;}
|
||||
.st2{display:inline;}
|
||||
.st3{fill:none;}
|
||||
</style>
|
||||
<path class="st0" d="M37.6,12L18.8,24l18.8,12L18.8,48L0,35.9l18.8-12L0,12L18.8,0L37.6,12z M18.7,51.8l18.8-12l18.8,12l-18.8,12
|
||||
L18.7,51.8z M37.6,35.9l18.8-12L37.6,12L56.3,0l18.8,12L56.3,24l18.8,12L56.3,48L37.6,35.9z"/>
|
||||
<path d="M89.8,12H105c9.7,0,17.7,5.6,17.7,18.4v2.7c0,12.9-7.5,18.7-17.4,18.7H89.8V12z M98.3,19.2v25.3h6.5c5.5,0,9.2-3.6,9.2-11.6
|
||||
v-2.1c0-8-3.9-11.6-9.5-11.6H98.3z M127.2,19.6h6.8l1.1,7.5c1.3-5.1,4.6-7.8,10.6-7.8h2.1v8.6h-3.5c-6.9,0-8.6,2.4-8.6,9.2v14.8
|
||||
h-8.4V19.6H127.2z M149.5,36.4v-0.9c0-10.8,6.9-16.7,16.3-16.7c9.6,0,16.3,5.9,16.3,16.7v0.9c0,10.6-6.5,16.3-16.3,16.3
|
||||
C155.4,52.6,149.5,47,149.5,36.4z M173.5,36.3v-0.8c0-6-3-9.6-7.8-9.6c-4.7,0-7.8,3.3-7.8,9.6v0.8c0,5.8,3,9.1,7.8,9.1
|
||||
C170.5,45.3,173.5,42.1,173.5,36.3z M186.5,19.6h7l0.8,6.1c1.7-4.1,5.3-6.9,10.6-6.9c8.2,0,13.6,5.9,13.6,16.8v0.9
|
||||
c0,10.6-6,16.2-13.6,16.2c-5.1,0-8.6-2.3-10.3-6V63h-8.2L186.5,19.6L186.5,19.6z M210,36.3v-0.7c0-6.4-3.3-9.6-7.7-9.6
|
||||
c-4.7,0-7.8,3.6-7.8,9.6v0.6c0,5.7,3,9.3,7.7,9.3C207,45.4,210,42.3,210,36.3z M230.9,45.9l-0.7,5.9H223v-43h8.2v16.5
|
||||
c1.8-4.2,5.4-6.5,10.5-6.5c7.7,0.1,13.4,5.4,13.4,16.1v1c0,10.7-5.4,16.8-13.6,16.8C236.1,52.6,232.6,50.1,230.9,45.9z M246.5,35.9
|
||||
v-0.8c0-5.9-3.2-9.2-7.7-9.2c-4.6,0-7.8,3.7-7.8,9.3v0.7c0,6,3.1,9.5,7.7,9.5C243.6,45.4,246.5,42.3,246.5,35.9z M258.7,36.4v-0.9
|
||||
c0-10.8,6.9-16.7,16.3-16.7c9.6,0,16.3,5.9,16.3,16.7v0.9c0,10.6-6.6,16.3-16.3,16.3C264.6,52.6,258.7,47,258.7,36.4z M282.8,36.3
|
||||
v-0.8c0-6-3-9.6-7.8-9.6c-4.7,0-7.8,3.3-7.8,9.6v0.8c0,5.8,3,9.1,7.8,9.1C279.8,45.3,282.8,42.1,282.8,36.3z M302.3,35.1L291,19.6
|
||||
h9.7l6.5,9.7l6.6-9.7h9.6L311.9,35L324,51.8h-9.5l-7.4-10.7l-7.2,10.7H290L302.3,35.1z"/>
|
||||
<g id="Editble" class="st1">
|
||||
<g class="st2">
|
||||
<rect x="-105" y="5" class="st3" width="506" height="71.8"/>
|
||||
<path d="M0.2,13.6h16.3c10.4,0,19,6.1,19,19.8v2.9c0,13.8-8,20-18.7,20H0.2V13.6z M9.4,21.3v27.2h7c5.9,0,9.9-3.9,9.9-12.5v-2.2
|
||||
c0-8.6-4.1-12.5-10.2-12.5H9.4z M40.4,21.8h7.3l1.1,8c1.4-5.5,4.9-8.3,11.3-8.3h2.2v9.2h-3.7c-7.4,0-9.2,2.6-9.2,9.9v15.8h-9
|
||||
C40.4,56.4,40.4,21.8,40.4,21.8z M64.3,39.8v-1c0-11.6,7.4-17.9,17.5-17.9c10.3,0,17.5,6.4,17.5,17.9v1c0,11.4-7,17.5-17.5,17.5
|
||||
C70.6,57.3,64.3,51.2,64.3,39.8z M90.1,39.7v-0.8c0-6.5-3.2-10.3-8.3-10.3c-5,0-8.4,3.5-8.4,10.3v0.8c0,6.2,3.2,9.7,8.3,9.7
|
||||
C86.9,49.4,90.1,46,90.1,39.7z M104,21.8h7.6l0.9,6.6c1.9-4.4,5.7-7.4,11.4-7.4c8.8,0,14.6,6.4,14.6,18v1
|
||||
c0,11.4-6.4,17.3-14.6,17.3c-5.5,0-9.2-2.5-11-6.5v17.5H104V21.8z M129.3,39.8V39c0-6.9-3.5-10.3-8.3-10.3c-5,0-8.4,3.8-8.4,10.3
|
||||
v0.7c0,6.1,3.2,10,8.2,10C126,49.5,129.3,46.1,129.3,39.8z M151.7,50.1l-0.7,6.3h-7.8V10.2h8.8V28c1.9-4.5,5.8-7,11.2-7
|
||||
c8.2,0.1,14.3,5.8,14.3,17.3v1c0,11.5-5.8,18-14.6,18C157.3,57.3,153.5,54.5,151.7,50.1z M168.5,39.3v-0.8c0-6.4-3.5-9.8-8.3-9.8
|
||||
c-5,0-8.4,4-8.4,10v0.7c0,6.5,3.3,10.2,8.3,10.2C165.3,49.5,168.5,46.1,168.5,39.3z M181.6,39.8v-1c0-11.6,7.4-17.9,17.5-17.9
|
||||
c10.3,0,17.5,6.4,17.5,17.9v1c0,11.4-7.1,17.5-17.5,17.5C187.9,57.3,181.6,51.2,181.6,39.8z M207.4,39.7v-0.8
|
||||
c0-6.5-3.2-10.3-8.3-10.3c-5,0-8.4,3.5-8.4,10.3v0.8c0,6.2,3.2,9.7,8.3,9.7C204.2,49.4,207.4,46,207.4,39.7z M228.3,38.4
|
||||
l-12.1-16.7h10.4l7,10.4l7.1-10.4H251l-12.3,16.6l13,18h-10.2l-8-11.5l-7.7,11.5h-10.6L228.3,38.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |